Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2f03415c8 | ||
|
|
69371b5949 | ||
|
|
bbd8f7f832 | ||
|
|
17792ec995 | ||
|
|
40b48a2080 | ||
|
|
7eb39e5896 | ||
|
|
28cc88b4b8 | ||
|
|
c9925cd03c | ||
|
|
40f9465929 | ||
|
|
359805b713 | ||
|
|
31565b9170 | ||
|
|
9c2a0541df | ||
|
|
38d7d587fa | ||
|
|
f19ab9455a | ||
|
|
cadffe40d6 | ||
|
|
5a195e2822 | ||
|
|
63592b91a9 | ||
|
|
88345c58a7 | ||
|
|
7124b2b61c | ||
|
|
f93e6a7d7c | ||
|
|
5d46401b85 | ||
|
|
97867080d9 | ||
|
|
93a4b44143 | ||
|
|
5e3e5628d2 | ||
|
|
bddaad0193 | ||
|
|
4a6349cd9c | ||
|
|
e9c998fec8 | ||
|
|
290b8918fc | ||
|
|
0e6de5d0f0 | ||
|
|
98d30a326e | ||
|
|
5f64499984 | ||
|
|
6e32c740ae | ||
|
|
e0721715d0 | ||
|
|
10edc00973 | ||
|
|
f8829ce82c | ||
|
|
ac91b712a3 | ||
|
|
4bccbbac33 | ||
|
|
5c8b7290ba | ||
|
|
288713ce5b | ||
|
|
ff3b5a320c | ||
|
|
49eb28056e | ||
|
|
ddb682dff6 | ||
|
|
a2e0f6a517 | ||
|
|
7356e5dd30 | ||
|
|
933c6c1952 | ||
|
|
d8fd4488e2 | ||
|
|
89c6e8bb29 | ||
|
|
d4c596a6fe | ||
|
|
fef2a2e979 | ||
|
|
f001dffb68 | ||
|
|
2818644b7f | ||
|
|
86d93fd702 | ||
|
|
593cc0c91b | ||
|
|
d15fa684b7 | ||
|
|
9879094661 | ||
|
|
5ce5478bf4 | ||
|
|
5fb36e7fbe | ||
|
|
0dbea26a2e | ||
|
|
b0e0dc75f1 | ||
|
|
318f6a4668 | ||
|
|
fdb5cd23e7 | ||
|
|
e064c1aee5 | ||
|
|
32a316589b | ||
|
|
d4bc9bdf75 | ||
|
|
3325ba935b | ||
|
|
345f24996f | ||
|
|
e0bc481ae2 | ||
|
|
4040b2456c | ||
|
|
8e77d31a5a | ||
|
|
081e6e7322 | ||
|
|
6e029beafc | ||
|
|
f5a7cd5984 | ||
|
|
d4d5e26a2b | ||
|
|
92990948ad | ||
|
|
a0d8b9ea43 | ||
|
|
d97620ad56 | ||
|
|
bf6d40f398 | ||
|
|
6d4dc3a22b | ||
|
|
74d23386a7 | ||
|
|
03299db99a | ||
|
|
a0886d900e | ||
|
|
9f46bcd853 | ||
|
|
97fe038d9b | ||
|
|
bcc9dd1a0d | ||
|
|
fc40552d68 | ||
|
|
f41050fe5c | ||
|
|
97f487c0af | ||
|
|
499e16975f | ||
|
|
75f3c07cd9 | ||
|
|
0a0c00d877 | ||
|
|
8d2c18b6af | ||
|
|
2d2f83436c | ||
|
|
71914fdf57 | ||
|
|
58e3da8942 | ||
|
|
2d7409debb | ||
|
|
beece68498 | ||
|
|
b7c3793a66 | ||
|
|
92fb653676 | ||
|
|
2498466a51 | ||
|
|
ef36eba039 | ||
|
|
d8e9d786d9 | ||
|
|
c40e28ed0f | ||
|
|
b21846a513 | ||
|
|
7ef305e724 | ||
|
|
5da607865c | ||
|
|
1f5ce72fb2 | ||
|
|
b6be245764 | ||
|
|
3532f75c2b | ||
|
|
46d0f52b3e | ||
|
|
80f0b181d1 | ||
|
|
9161c54e18 | ||
|
|
64d9e456b7 | ||
|
|
846aaeadf0 | ||
|
|
eaf8d7364a | ||
|
|
54bfff9623 | ||
|
|
237364a4e2 | ||
|
|
c3288b44bb | ||
|
|
70128bbdd1 | ||
|
|
a88a164ead | ||
|
|
d6aeb5df21 | ||
|
|
32db5795da | ||
|
|
a81a641d41 | ||
|
|
6c59919972 | ||
|
|
77d56b8bec | ||
|
|
53da35d959 | ||
|
|
0f1d1b0797 | ||
|
|
6586a7b191 | ||
|
|
b7aab9f263 | ||
|
|
ef34fecc31 | ||
|
|
e9aa980c88 | ||
|
|
ab495efc65 | ||
|
|
14e13d6624 | ||
|
|
8db099c5c7 | ||
|
|
b503e54210 | ||
|
|
68e4dfb598 | ||
|
|
7ef7bc13dc | ||
|
|
a1d85274b9 | ||
|
|
63dfab6654 | ||
|
|
707577521f | ||
|
|
3458a5df9f | ||
|
|
e19eeedcc3 | ||
|
|
c95de5e606 | ||
|
|
e7b9bc92a2 | ||
|
|
7d8e414fe2 | ||
|
|
476bcc9c55 | ||
|
|
ae006f52f4 | ||
|
|
3583a93ae0 | ||
|
|
212a00a43c | ||
|
|
53f7f380ae | ||
|
|
2028d84554 | ||
|
|
da28bae6c5 | ||
|
|
944f99f14b | ||
|
|
8978cc3482 | ||
|
|
f08d4b1a37 | ||
|
|
824fbe506e | ||
|
|
e5dbba362a | ||
|
|
bdc09addfa | ||
|
|
deb84ca331 | ||
|
|
51b3987a4a | ||
|
|
e7290fa81b | ||
|
|
5b778af745 | ||
|
|
776d9ab610 | ||
|
|
587e5412d5 | ||
|
|
b5432498c6 | ||
|
|
ef5430ae2c | ||
|
|
059d7203a9 | ||
|
|
82b6cbfd72 | ||
|
|
c170dbc46c | ||
|
|
890dbd9d32 | ||
|
|
f17b395c4c | ||
|
|
807592eab2 | ||
|
|
394c0779fc | ||
|
|
5b3f777100 | ||
|
|
8b36905842 | ||
|
|
a44dddb51b | ||
|
|
b203e8e062 | ||
|
|
4d4df1bde2 | ||
|
|
876780a349 | ||
|
|
0fdb362313 | ||
|
|
50fa4f7bd8 | ||
|
|
9309aa9d2d | ||
|
|
dd31ffc5a2 | ||
|
|
3d467c9620 | ||
|
|
f95c646ef3 | ||
|
|
c0000bb332 | ||
|
|
cdbcb2b539 | ||
|
|
3b83377f3c | ||
|
|
e64c45b621 | ||
|
|
70dae9ab1b | ||
|
|
0bb3c5bc65 | ||
|
|
54f5c7c548 | ||
|
|
56217186ff | ||
|
|
82fb2bcc48 | ||
|
|
252ac257fe | ||
|
|
afe0217655 | ||
|
|
516efb64e8 | ||
|
|
4cad251e57 | ||
|
|
48e9c14525 | ||
|
|
db62af6f71 | ||
|
|
b468a1eb70 | ||
|
|
b9ab202e3f | ||
|
|
129dde1c57 | ||
|
|
ff2d3926b1 | ||
|
|
fefc6ebc4f | ||
|
|
0076faf03a | ||
|
|
bbdd18e8ef | ||
|
|
87d31c134e | ||
|
|
ce04f5fd1e | ||
|
|
435ccd1f2d | ||
|
|
74c28d40a6 | ||
|
|
7b2c276643 | ||
|
|
0e1264ca4f | ||
|
|
defcb3af84 | ||
|
|
6dc42b637a | ||
|
|
5cf0a0ec76 | ||
|
|
1c44590def | ||
|
|
53732744f4 | ||
|
|
e6d12713e3 | ||
|
|
9456cf32fc | ||
|
|
5e95c286a9 | ||
|
|
6175e8a03e | ||
|
|
8e3d784e92 | ||
|
|
698e300690 | ||
|
|
e3fbe59585 | ||
|
|
0c7fecb123 | ||
|
|
9b1fb4e7d2 | ||
|
|
78eed7af13 | ||
|
|
6f5743a030 | ||
|
|
d027946fb2 | ||
|
|
37ea83d42a | ||
|
|
ae39f33131 | ||
|
|
291f80e904 | ||
|
|
61baaeeffb | ||
|
|
68aabb343d | ||
|
|
f2d6e07931 | ||
|
|
123259c2ef | ||
|
|
816d7663b5 | ||
|
|
9b9202c739 | ||
|
|
57f82415bd | ||
|
|
36c93a0c93 | ||
|
|
3cd1dbab49 | ||
|
|
4e3da95b1d | ||
|
|
0d0c4f913e | ||
|
|
600f86aeb4 | ||
|
|
c9bb222bff | ||
|
|
456559ffe2 | ||
|
|
6d58c09104 | ||
|
|
bc87d6add1 | ||
|
|
3fe49b70d0 | ||
|
|
5287f30636 | ||
|
|
67433ae325 | ||
|
|
d27ce9d1d8 | ||
|
|
c8ea9bd774 | ||
|
|
885c8ea502 | ||
|
|
c8b32f9877 | ||
|
|
cf61fd8d09 | ||
|
|
cac20d4f24 | ||
|
|
965443a8f9 | ||
|
|
02be084faf | ||
|
|
bb2e83b6ce | ||
|
|
c09c62f077 | ||
|
|
7a2e93a995 | ||
|
|
4a94c946d1 | ||
|
|
5327c29641 | ||
|
|
be865102a8 | ||
|
|
2dca5d2905 | ||
|
|
b5ae352af5 | ||
|
|
868215705c | ||
|
|
37f1b43b93 | ||
|
|
16f0775783 | ||
|
|
f2c4285b4c | ||
|
|
ee95f0e89e | ||
|
|
1306977954 | ||
|
|
476fa81bc2 | ||
|
|
6d16d9dff4 | ||
|
|
b60ab881b1 | ||
|
|
f2d76a0d67 | ||
|
|
5dcc82d81e | ||
|
|
a01e47f205 | ||
|
|
c87563b7ed | ||
|
|
246b411114 | ||
|
|
e8f1f0ac43 | ||
|
|
cfbc2d96a1 | ||
|
|
1adf24ce77 | ||
|
|
a61d130baa | ||
|
|
0a1760cee6 | ||
|
|
3e094af325 | ||
|
|
762e4d70a0 | ||
|
|
6c4f863b96 | ||
|
|
b87f82f74d | ||
|
|
ffad53a331 | ||
|
|
b2ca1c2ab7 | ||
|
|
2cbfaec0c2 | ||
|
|
95dd648b90 | ||
|
|
662a218e41 | ||
|
|
0501ae9bec | ||
|
|
7fd38eec00 | ||
|
|
4bab4580ad | ||
|
|
21d2e6ad20 | ||
|
|
d0a8f369ef | ||
|
|
fef2e1a40c | ||
|
|
20c2cb7ea4 | ||
|
|
333a8a0970 | ||
|
|
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 | ||
|
|
5357eb9735 | ||
|
|
0946212c4b | ||
|
|
d6ab44d18e | ||
|
|
1db1a543c7 | ||
|
|
3e92a0c40d | ||
|
|
f3c461cbd9 | ||
|
|
012408d19e | ||
|
|
f7e563e71a | ||
|
|
81c6d39c1a | ||
|
|
d682443e1d | ||
|
|
c065b19ef8 | ||
|
|
b20a4c3c3d | ||
|
|
d66e75c183 | ||
|
|
31b4c47379 | ||
|
|
71bba499e0 | ||
|
|
7a08fa42d9 | ||
|
|
18a008462a | ||
|
|
d81da59398 | ||
|
|
3ab1eabe75 | ||
|
|
cf4f860db8 | ||
|
|
c68e3337ef | ||
|
|
db1ae613d6 | ||
|
|
a800816cda | ||
|
|
d4ef06c7c1 | ||
|
|
95d1d231c9 | ||
|
|
10b278e26f | ||
|
|
0550eef2eb | ||
|
|
73eb9919eb | ||
|
|
9116009b0b | ||
|
|
4e4ceb5074 | ||
|
|
d79e2ff187 | ||
|
|
d4a2594239 | ||
|
|
b666162bdf | ||
|
|
d705accaed | ||
|
|
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 | ||
|
|
68b62a5630 | ||
|
|
990d44dd07 | ||
|
|
819ae41c00 | ||
|
|
bf95b7f7e6 | ||
|
|
861b78b928 | ||
|
|
08c414a66e | ||
|
|
57a80d00aa | ||
|
|
70eae5f7d2 | ||
|
|
e2a65e2cdd | ||
|
|
f03b74614e | ||
|
|
532b3fd7fd | ||
|
|
64df4129d4 | ||
|
|
f6e35e2045 | ||
|
|
a97717a624 | ||
|
|
3f650de52f | ||
|
|
1f4bda7976 | ||
|
|
a4423e45f8 | ||
|
|
1386c592bf | ||
|
|
016f3fc73c | ||
|
|
a42c46f4d2 | ||
|
|
fb09cf254e | ||
|
|
315b913fd0 | ||
|
|
0383dc18ae | ||
|
|
f16856d23b | ||
|
|
e640fa1bc0 | ||
|
|
df3b3b604d | ||
|
|
21e6c410e2 | ||
|
|
19045329dc | ||
|
|
4586ef79f7 | ||
|
|
f97d4ce3c5 | ||
|
|
cd4f626218 | ||
|
|
c1eb2cf447 | ||
|
|
5fec95cc4d | ||
|
|
46b943c800 | ||
|
|
fa8fe508a7 | ||
|
|
2a926a8d73 | ||
|
|
d222842313 | ||
|
|
2942b6e442 | ||
|
|
926cade437 | ||
|
|
13b4413b36 | ||
|
|
f516db0e48 | ||
|
|
8744e1d73e | ||
|
|
6113d9f4cd | ||
|
|
1dd92cb089 | ||
|
|
8f6255c2cd | ||
|
|
6c4f6e269c | ||
|
|
82b0837d8c | ||
|
|
137489cc1f | ||
|
|
4dc4dfdda0 | ||
|
|
ae15311471 | ||
|
|
dbcdace348 | ||
|
|
9375827949 | ||
|
|
739f401fac | ||
|
|
5f5a47679d | ||
|
|
26d1d63edb | ||
|
|
381bc37d13 | ||
|
|
936bfc753a | ||
|
|
f0ccf1f4f7 | ||
|
|
3a3c4d9d73 | ||
|
|
9c69f2ce6d | ||
|
|
e7a27c798a | ||
|
|
ddc612328e | ||
|
|
d05d1e6914 | ||
|
|
8bcf776813 | ||
|
|
589e591d3b | ||
|
|
e2e8c5ec9e | ||
|
|
d9aea9eb3c | ||
|
|
4cddd43f5a | ||
|
|
becc1696cb | ||
|
|
cf4957551f | ||
|
|
794b06d55a | ||
|
|
f51f742e00 | ||
|
|
40cc188815 | ||
|
|
303f6bb35b | ||
|
|
9043b6e2a3 | ||
|
|
0608b9175e | ||
|
|
ea7fda6b28 | ||
|
|
090326b75c | ||
|
|
78e6f7e5a7 | ||
|
|
8173eb8de9 | ||
|
|
591cd8950c | ||
|
|
3a78d6d6f7 | ||
|
|
bbf85a6048 | ||
|
|
6dfa876809 | ||
|
|
aff86a2ffa | ||
|
|
1eb925f3dd | ||
|
|
701d76240e | ||
|
|
37ea9e9083 | ||
|
|
6457b3ad25 | ||
|
|
cba5b948de | ||
|
|
4ba232a7e4 | ||
|
|
05f3f0fc7e | ||
|
|
89a5d78ced | ||
|
|
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 | ||
|
|
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
@@ -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
|
||||
|
||||
@@ -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,6 +24,13 @@ stages:
|
||||
- antivirus
|
||||
- release
|
||||
|
||||
#########################
|
||||
# PYINSTALLER VARIABLES #
|
||||
#########################
|
||||
|
||||
variables:
|
||||
HIDDEN_IMPORTS: "--hiddenimport rasterio --collect-submodules rasterio"
|
||||
|
||||
#############
|
||||
# DOWNLOADS #
|
||||
#############
|
||||
@@ -131,7 +138,6 @@ dl-rubar-windows:
|
||||
- cd rubar-windows
|
||||
- curl -L -o rubar.tgz https://forge.inrae.fr/river-hydraulics/rubarbe/-/releases/$RUBAR_VERSION/downloads/packages/rubar_windows.tgz
|
||||
- tar xf rubar.tgz
|
||||
|
||||
artifacts:
|
||||
paths:
|
||||
- rubar-windows/rubarbe.exe
|
||||
@@ -174,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
|
||||
@@ -192,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
|
||||
@@ -270,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
|
||||
@@ -320,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
|
||||
@@ -434,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
|
||||
@@ -613,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:
|
||||
@@ -632,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
|
||||
@@ -18,6 +18,7 @@ modelling of rivers.
|
||||
+ Define river sedimentary layers
|
||||
+ Save the study in a unique file
|
||||
+ Run Mage 8 on a study and visualize results
|
||||
+ Create and edit study alternative scenario tree
|
||||
|
||||
Let see the
|
||||
[documentation](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home)
|
||||
|
||||
@@ -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
|
||||
@@ -1069,6 +1069,12 @@ gitlab-ci.
|
||||
sudo python3 -m pip install pyinstaller
|
||||
#+end_src
|
||||
|
||||
In addition, you must install Glab[fn:glab] to allow the runner to
|
||||
proceed with the publication.
|
||||
|
||||
[fn:glab] The Gitlab cli releases page:
|
||||
https://gitlab.com/gitlab-org/cli/-/releases
|
||||
|
||||
*** Windows (Wine) {{{windows}}}
|
||||
|
||||
The ci-runner environment for Wine need at least wine version 8, let
|
||||
|
||||
@@ -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
|
||||
@@ -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 ?
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 196 KiB |
@@ -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
|
||||
|
||||
|
||||
@@ -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.10.8
|
||||
numpy>=1.24.2
|
||||
colorama>=0.4.3
|
||||
pyinstaller>=5.11.0
|
||||
@@ -14,3 +14,4 @@ platformdirs>=4.2.0
|
||||
pyshp>=2.3.1
|
||||
rasterio==1.3.11
|
||||
#fortranformat==2.0.3
|
||||
psutil>=7.2.2
|
||||
@@ -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"))
|
||||
@@ -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.10.8
|
||||
numpy>=1.24.2
|
||||
colorama>=0.4.3
|
||||
pyinstaller>=5.11.0
|
||||
@@ -14,3 +14,4 @@ platformdirs>=4.2.0
|
||||
pyshp>=2.3.1
|
||||
rasterio==1.3.11
|
||||
#fortranformat==2.0.3
|
||||
psutil>=7.2.2
|
||||
@@ -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,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
|
||||
@@ -229,7 +229,11 @@ for each reach"
|
||||
gls = []
|
||||
|
||||
for edge in edges:
|
||||
comp, incomp = edge.reach.compute_guidelines()
|
||||
profiles = edge.reach.enabled_profiles
|
||||
comp, incomp = edge.reach.compute_guidelines(
|
||||
profiles=profiles,
|
||||
update_cache=False
|
||||
)
|
||||
if len(incomp) != 0:
|
||||
self._status = STATUS.WARNING
|
||||
self._summary = "incomplete_guideline"
|
||||
@@ -237,7 +241,6 @@ for each reach"
|
||||
|
||||
gls.append(comp)
|
||||
|
||||
profiles = edge.reach.profiles
|
||||
for profile in profiles:
|
||||
if not profile.has_standard_named_points():
|
||||
self._status = STATUS.WARNING
|
||||
|
||||
@@ -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
|
||||
@@ -87,7 +87,7 @@ class StudyGeometryChecker(AbstractModelChecker):
|
||||
return False
|
||||
|
||||
for edge in edges:
|
||||
if len(edge.reach.profiles) < 2:
|
||||
if len(edge.reach.enabled_profiles) < 2:
|
||||
summary = "no_geometry_defined"
|
||||
status = STATUS.ERROR
|
||||
ok = False
|
||||
@@ -132,8 +132,12 @@ class StudyInitialConditionsChecker(AbstractModelChecker):
|
||||
return ok
|
||||
|
||||
ic = river.initial_conditions[reach]
|
||||
len_ic = len(ic)
|
||||
len_reach = len(reach)
|
||||
enabled_profiles = set(reach.enabled_profiles)
|
||||
len_ic = sum(
|
||||
data["section"] in enabled_profiles
|
||||
for data in ic.data
|
||||
)
|
||||
len_reach = len(enabled_profiles)
|
||||
|
||||
if len_ic < len_reach:
|
||||
self._summary = "initial_condition_missing_profile"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Internal.py -- Pamhyr
|
||||
# Copyright (C) 2023-2025 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,7 +17,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from numpy import sign
|
||||
from numpy import sign, interp
|
||||
from copy import deepcopy
|
||||
|
||||
from tools import logger_color_red, logger_color_reset, logger_exception
|
||||
@@ -40,36 +40,45 @@ class InternalMeshing(AMeshingTool):
|
||||
step: float = 50,
|
||||
limites=[-1, -1],
|
||||
linear: bool = True):
|
||||
if reach is None or len(reach.profiles) == 0:
|
||||
return reach
|
||||
|
||||
if limites[0] > limites[1]:
|
||||
lim = limites[1]
|
||||
limites[1] = limites[0]
|
||||
limites[0] = lim
|
||||
elif limites[0] == limites[1]:
|
||||
return reach
|
||||
# pre process
|
||||
|
||||
self.st_to_m(reach, limites)
|
||||
self.interpolate_transversal_step(reach,
|
||||
limites,
|
||||
step)
|
||||
self.purge_aligned_points(reach)
|
||||
profiles = self.get_profiles(reach, limites)
|
||||
if profiles is None:
|
||||
return []
|
||||
|
||||
return reach
|
||||
for p in profiles:
|
||||
if len(p) < 1:
|
||||
return []
|
||||
|
||||
def st_to_m(self, reach, limites):
|
||||
guide_list = self.get_guide_list(reach, profiles)
|
||||
if guide_list is None:
|
||||
return []
|
||||
|
||||
gl = reach.compute_guidelines()
|
||||
profiles = reach.profiles[limites[0]:limites[1]+1]
|
||||
# we never modify original profiles, we work on the copies
|
||||
m_profiles = self.st_to_m(profiles, guide_list)
|
||||
|
||||
# we make sure that the lines are in the left-to-right order
|
||||
guide_list = [
|
||||
x.name
|
||||
for x in profiles[0].named_points()
|
||||
if x.name in gl[0]
|
||||
]
|
||||
# Adapt the spacing to the kilometre-point direction. The value from
|
||||
# the dialog is a distance, so only its magnitude is significant.
|
||||
step = self.signed_step(
|
||||
step, m_profiles[0].rk, m_profiles[-1].rk
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def signed_step(step, begin_rk, end_rk):
|
||||
if end_rk < begin_rk:
|
||||
return -abs(step)
|
||||
return abs(step)
|
||||
|
||||
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)
|
||||
@@ -95,47 +104,48 @@ class InternalMeshing(AMeshingTool):
|
||||
guide_list[i],
|
||||
guide_list[i+1])
|
||||
|
||||
return profiles
|
||||
|
||||
def interpolate_transversal_step(self,
|
||||
reach,
|
||||
limites,
|
||||
profiles,
|
||||
step):
|
||||
# calcul number of intermediate profiles
|
||||
np = []
|
||||
for i in range(limites[0], limites[1]):
|
||||
np.append(int(abs(reach.profiles[i+1].rk -
|
||||
reach.profiles[i].rk) / step) - 1)
|
||||
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
|
||||
|
||||
d = [] # ratios
|
||||
p = [] # new profiles
|
||||
ptr = int(limites[0])
|
||||
for i in range(limites[1] - limites[0]):
|
||||
new_profiles = []
|
||||
for i in range(len(profiles)-1):
|
||||
new_profiles2 = []
|
||||
d = 1.0/float(np[i]+1)
|
||||
ptr0 = ptr
|
||||
for j in range(np[i]):
|
||||
p = ProfileXYZ(reach=reach, status=reach._status)
|
||||
p = profiles[i].copy()
|
||||
# RATIO entre les deux sections initiales
|
||||
dj = float(j+1)*d
|
||||
ptr += 1 # next profile, original
|
||||
for k in range(len(reach.profiles[ptr0].points)):
|
||||
p.add()
|
||||
p.points[k].name = reach.profiles[ptr0].points[k].name
|
||||
p.points[k].x = reach.profiles[ptr0].points[k].x + \
|
||||
dj*(reach.profiles[ptr].points[k].x -
|
||||
reach.profiles[ptr0].points[k].x)
|
||||
p.points[k].y = reach.profiles[ptr0].points[k].y + \
|
||||
dj*(reach.profiles[ptr].points[k].y -
|
||||
reach.profiles[ptr0].points[k].y)
|
||||
p.points[k].z = reach.profiles[ptr0].points[k].z + \
|
||||
dj*(reach.profiles[ptr].points[k].z -
|
||||
reach.profiles[ptr0].points[k].z)
|
||||
p.rk = reach.profiles[ptr0].rk + \
|
||||
dj*(reach.profiles[ptr].rk -
|
||||
reach.profiles[ptr0].rk)
|
||||
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 = round(profiles[i].rk +
|
||||
dj*(profiles[i+1].rk -
|
||||
profiles[i].rk), 2)
|
||||
p.name = f'interpol{p.rk}'
|
||||
reach.insert_profile(ptr, p)
|
||||
ptr += 1 # next profile, original
|
||||
p.interpolated = True
|
||||
new_profiles2.append(p)
|
||||
new_profiles.append(new_profiles2)
|
||||
|
||||
return new_profiles
|
||||
|
||||
def compl_sect(self, sect1, sect2, tag1, tag2):
|
||||
# sect1: reference section
|
||||
@@ -155,30 +165,24 @@ class InternalMeshing(AMeshingTool):
|
||||
len2 = end2 - start2
|
||||
|
||||
if len1 < len2:
|
||||
self.compl_sect(sect2, sect1, start2, start1,
|
||||
len2, len1, tag1, tag2)
|
||||
self.compl_sect(sect2, sect1, tag1, tag2)
|
||||
return
|
||||
elif len1 == len2:
|
||||
return
|
||||
|
||||
ltot1 = 0.0
|
||||
ltot2 = 0.0
|
||||
alpha = []
|
||||
beta = []
|
||||
alpha = [0.0]
|
||||
beta = [0.0]
|
||||
# remove names
|
||||
sect2.points[start2].name = ''
|
||||
sect2.points[end2].name = ''
|
||||
for i in range(len1):
|
||||
ltot1 += sect1.point(start1+i).dist(sect1.point(start1+i+1))
|
||||
alpha.append(ltot1)
|
||||
alpha = list(map(lambda x: x/ltot1, alpha)) # target ratios
|
||||
for i in range(len2):
|
||||
ltot2 += sect2.point(start2+i).dist(sect2.point(start2+i+1))
|
||||
beta.append(ltot2)
|
||||
beta = list(map(lambda x: x/ltot2, beta)) # current ratios
|
||||
for i in range(len1 - len2):
|
||||
beta.append(1.0)
|
||||
beta.append(0.0) # beta[-1]
|
||||
if len2 < 1 or ltot2 < 0.0001:
|
||||
# unique point (copy len1 times)
|
||||
# we are at an edge of the profile
|
||||
@@ -190,71 +194,113 @@ class InternalMeshing(AMeshingTool):
|
||||
sect2.add_npoints(len1-len2)
|
||||
len2 = len1
|
||||
else: # regular case
|
||||
# loop on the points to insert
|
||||
for k in range(len1-len2):
|
||||
trouve = False
|
||||
for j0 in range(len2):
|
||||
if trouve:
|
||||
break
|
||||
undeplus = True
|
||||
for j1 in range(j0, len2):
|
||||
if undeplus:
|
||||
if beta[j1] <= alpha[j1]:
|
||||
undeplus = False
|
||||
break
|
||||
if undeplus:
|
||||
if j0 == len2:
|
||||
trouve = True
|
||||
else:
|
||||
j1 = j0+1
|
||||
if beta[j0]-alpha[j0] > alpha[j1]-beta[j0]:
|
||||
trouve = True
|
||||
alpha = list(map(lambda x: x/ltot1, alpha)) # target ratios
|
||||
beta = list(map(lambda x: x/ltot2, beta)) # current ratios
|
||||
match = self._match_indices(alpha, beta)
|
||||
|
||||
if trouve:
|
||||
len2 += 1
|
||||
for i in reversed(range(j0, len2)):
|
||||
beta[i+1] = beta[i]
|
||||
if (beta[j0] == beta[j0-1]):
|
||||
# rien
|
||||
pass
|
||||
else:
|
||||
ratio = (alpha[j0] - beta[j0-1]) \
|
||||
/ (beta[j0] - beta[j0-1])
|
||||
if ratio < 0.0:
|
||||
print(f"ratio négatif {ratio}")
|
||||
# on double le point a gauche
|
||||
p = sect2.point(start2+j0-1).copy()
|
||||
sect2.insert_point(start2+j0-1, p)
|
||||
sect2.point(start2+j0-1).name = ''
|
||||
beta[j0] = beta[j0-1]
|
||||
else:
|
||||
p = sect2.point(start2+j0).copy()
|
||||
sect2.insert_point(start2+j0, p)
|
||||
sect2.point(start2+j0+1).name = ''
|
||||
sect2.point(start2+j0+1).x = \
|
||||
(1.0-ratio) * sect2.point(start2+j0).x + \
|
||||
ratio * sect2.point(start2+j0+2).x
|
||||
sect2.point(start2+j0+1).y = \
|
||||
(1.0-ratio) * sect2.point(start2+j0).y + \
|
||||
ratio * sect2.point(start2+j0+2).y
|
||||
sect2.point(start2+j0+1).z = \
|
||||
(1.0-ratio) * sect2.point(start2+j0).z + \
|
||||
ratio * sect2.point(start2+j0+2).z
|
||||
beta[j0] = (1.-ratio)*beta[j0-1] \
|
||||
+ ratio*beta[j0+1]
|
||||
insertions_per_segment = [] # liste de (index, [points])
|
||||
for j in range(len2):
|
||||
i_start = match[j]
|
||||
i_end = match[j + 1]
|
||||
q_start = sect2.point(start2 + j)
|
||||
q_end = sect2.point(start2 + j + 1)
|
||||
|
||||
new_points = []
|
||||
if i_end - i_start > 1:
|
||||
u_start, u_end = alpha[i_start], alpha[i_end]
|
||||
span = u_end - u_start
|
||||
for k in range(i_start + 1, i_end):
|
||||
ratio = (alpha[k] - u_start) / span # dans [0,1]
|
||||
p = q_start.copy()
|
||||
p.name = ''
|
||||
p.x = (1.0-ratio) * q_start.x + ratio * q_end.x
|
||||
p.y = (1.0-ratio) * q_start.y + ratio * q_end.y
|
||||
p.z = (1.0-ratio) * q_start.z + ratio * q_end.z
|
||||
new_points.append(p)
|
||||
|
||||
# à insérer après l'indice j
|
||||
insertions_per_segment.append((j, new_points))
|
||||
|
||||
# Insertion en ordre décroissant de j
|
||||
# pour ne pas perturber les indices
|
||||
for j, new_points in reversed(insertions_per_segment):
|
||||
for offset, p in enumerate(new_points):
|
||||
sect2.insert_point(start2 + j + 1 + offset, p)
|
||||
len2 += 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,
|
||||
def _match_indices(self, u_ref, v_target):
|
||||
# appariement optimal (ordre croissant) par programmation dynamique
|
||||
"""
|
||||
Trouve, pour chaque v_target[j], un indice i_j dans u_ref tel que
|
||||
i_0 < i_1 < ... < i_{m-1} et
|
||||
Sum |u_ref[i_j] - v_target[j]| soit minimal.
|
||||
|
||||
Retourne la liste des indices i_j (longueur m),
|
||||
avec i_0 = 0 et i_{m-1} = n-1 imposés
|
||||
(les extrémités se correspondent forcément).
|
||||
|
||||
"""
|
||||
n = len(u_ref)
|
||||
m = len(v_target)
|
||||
|
||||
# Les extrémités sont fixées.
|
||||
fixed_first, fixed_last = 0, n - 1
|
||||
|
||||
# DP sur les indices disponibles entre fixed_first+1 et fixed_last-1
|
||||
# dp[j][i] = coût minimal pour apparier v_target[1..j] aux indices
|
||||
# se terminant à l'indice i (i croissant strict), j de 1 à m-2.
|
||||
if m == 2:
|
||||
return [fixed_first, fixed_last]
|
||||
|
||||
# indices internes possibles
|
||||
candidates = list(range(fixed_first + 1, fixed_last))
|
||||
inner_targets = v_target[1:-1] # cibles internes (j = 1..m-2)
|
||||
|
||||
INF = float("inf")
|
||||
# dp[j] : dict indice_candidat -> (coût_min, provenance)
|
||||
dp = [{} for _ in range(len(inner_targets))]
|
||||
|
||||
for j, vt in enumerate(inner_targets):
|
||||
for idx_pos, i in enumerate(candidates):
|
||||
cost_here = abs(u_ref[i] - vt)
|
||||
if j == 0:
|
||||
# doit être > fixed_first
|
||||
dp[j][i] = (cost_here, fixed_first)
|
||||
else:
|
||||
best = (INF, None)
|
||||
for i_prev in candidates[:idx_pos]:
|
||||
if i_prev in dp[j - 1]:
|
||||
c = dp[j - 1][i_prev][0] + cost_here
|
||||
if c < best[0]:
|
||||
best = (c, i_prev)
|
||||
if best[1] is not None:
|
||||
dp[j][i] = best
|
||||
|
||||
# Reconstruction : on part du dernier niveau, on choisit le
|
||||
# meilleur indice tel que la suite jusqu'à fixed_last
|
||||
# reste croissante et valide.
|
||||
last_level = dp[-1]
|
||||
valid = {i: c for i, c in last_level.items() if i < fixed_last}
|
||||
if not valid:
|
||||
raise RuntimeError("Alignement impossible avec ces données.")
|
||||
best_i = min(valid, key=lambda i: valid[i][0])
|
||||
|
||||
chain = [best_i]
|
||||
for j in range(len(inner_targets) - 1, 0, -1):
|
||||
prev_i = dp[j][chain[-1]][1]
|
||||
chain.append(prev_i)
|
||||
chain.reverse()
|
||||
|
||||
return [fixed_first] + chain + [fixed_last]
|
||||
|
||||
def update_rk(self, reach, begin_rk, end_rk,
|
||||
step: float = 50,
|
||||
limites=[-1, -1],
|
||||
origin=0,
|
||||
@@ -265,29 +311,40 @@ class InternalMeshing(AMeshingTool):
|
||||
origin_value=0.0,
|
||||
orientation=0):
|
||||
if reach is None or len(reach.profiles) == 0:
|
||||
return reach
|
||||
return []
|
||||
|
||||
nprof = reach.number_profiles
|
||||
if origin >= nprof or origin < 0:
|
||||
logger.warning(
|
||||
f"Origin outside reach"
|
||||
)
|
||||
return 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 = sign(reach.profiles[-1].rk - reach.profiles[0].rk)
|
||||
if abs(sgn) < 0.5:
|
||||
sgn = 1.0
|
||||
sgn = old_orientation
|
||||
if abs(sgn) < 0.5:
|
||||
sgn = 1.0
|
||||
|
||||
reach.profiles[origin].rk = origin_value
|
||||
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):
|
||||
@@ -298,8 +355,7 @@ class InternalMeshing(AMeshingTool):
|
||||
d2 = reach.profiles[i-1].named_point(
|
||||
directrices[1]
|
||||
).dist_2d(reach.profiles[i].named_point(directrices[1]))
|
||||
reach.profiles[i].rk = reach.profiles[i-1].rk \
|
||||
+ (sgn * (d1 + d2) / 2)
|
||||
new_rk[i] = new_rk[i-1] + (sgn * (d1 + d2) / 2)
|
||||
if origin > 0:
|
||||
for i in reversed(range(0, origin)):
|
||||
# 2D
|
||||
@@ -309,9 +365,50 @@ class InternalMeshing(AMeshingTool):
|
||||
d2 = reach.profiles[i+1].named_point(
|
||||
directrices[1]
|
||||
).dist_2d(reach.profiles[i].named_point(directrices[1]))
|
||||
reach.profiles[i].rk = reach.profiles[i+1].rk \
|
||||
- (sgn * (d1 + d2) / 2)
|
||||
new_rk[i] = new_rk[i+1] - (sgn * (d1 + d2) / 2)
|
||||
|
||||
def purge_aligned_points(self, reach):
|
||||
for p in reach.profiles:
|
||||
p.purge_aligned_points()
|
||||
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
|
||||
@@ -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
|
||||
@@ -344,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(
|
||||
@@ -353,7 +355,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
|
||||
f"{logger_color_reset()}"
|
||||
)
|
||||
outputs = proc.readAllStandardOutput()
|
||||
logger.debug(outputs)
|
||||
logger.warning(outputs)
|
||||
|
||||
errors = proc.readAllStandardError()
|
||||
logger.error(
|
||||
@@ -419,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(
|
||||
@@ -428,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(self.id + 1, 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,38 +222,79 @@ class BoundaryCondition(SQLSubModel):
|
||||
self._header = []
|
||||
self._types = [float, float]
|
||||
|
||||
BoundaryCondition._id_cnt = max(BoundaryCondition._id_cnt + 1,
|
||||
self.id + 1)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
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):
|
||||
@@ -111,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):
|
||||
@@ -212,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):
|
||||
@@ -232,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:
|
||||
@@ -277,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 = []
|
||||
@@ -330,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)
|
||||
@@ -365,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 + 1)
|
||||
|
||||
@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,11 +496,16 @@ 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):
|
||||
@@ -247,36 +531,42 @@ class BoundaryConditionAdisTS(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, types=self._types, 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)
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
self._data = 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.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 index(self, bc):
|
||||
self._data.index(bc)
|
||||
@@ -291,10 +581,10 @@ class BoundaryConditionAdisTS(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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,481 @@
|
||||
# BoundaryConditionsTemperature.py -- Pamhyr
|
||||
# Copyright (C) 2023-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from tools import (
|
||||
trace, timer,
|
||||
old_pamhyr_date_to_timestamp,
|
||||
date_iso_to_timestamp,
|
||||
date_dmy_to_timestamp,
|
||||
)
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Data(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self,
|
||||
data0, data1,
|
||||
id: int = -1,
|
||||
types=[float, float],
|
||||
status=None,
|
||||
owner_scenario=-1):
|
||||
super(Data, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._types = types
|
||||
self._data = [data0, data1]
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE boundary_condition_data_temperature{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_temperature(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 or
|
||||
(int(minor) == 2 and int(release) <= 7)):
|
||||
if not cls.is_table_exists(execute,
|
||||
"boundary_condition_data_temperature"):
|
||||
cls._db_create(execute)
|
||||
created = True
|
||||
|
||||
if not created:
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
status = data['status']
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
bca = data["bca"]
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, data0, data1, scenario FROM " +
|
||||
"boundary_condition_data_temperature " +
|
||||
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_temperature(" +
|
||||
"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 BoundaryConditionTemperature(SQLSubModel):
|
||||
_sub_classes = [Data]
|
||||
|
||||
def __init__(self, id: int = -1, status=None, owner_scenario=-1):
|
||||
super(BoundaryConditionTemperature, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._status = status
|
||||
|
||||
self._name = ""
|
||||
self._node = None
|
||||
self._data = []
|
||||
self._header = []
|
||||
self._types = [self.time_convert, float]
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE boundary_condition_temperature{ext}(
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
node INTEGER,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(node) REFERENCES river_node(pamhyr_id)
|
||||
)
|
||||
""")
|
||||
|
||||
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 or (
|
||||
int(minor) == 2 and int(release) <= 7)):
|
||||
if not cls.is_table_exists(execute,
|
||||
"boundary_condition_temperature"):
|
||||
cls._db_create(execute)
|
||||
created = True
|
||||
|
||||
if not created:
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
status = data['status']
|
||||
nodes = data['nodes']
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
sql = (
|
||||
"SELECT pamhyr_id, deleted, name, node, scenario "
|
||||
"FROM boundary_condition_temperature " +
|
||||
f"WHERE scenario = {scenario.id} "
|
||||
)
|
||||
|
||||
table = execute(sql)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
it = iter(row)
|
||||
|
||||
pid = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
bc_name = next(it)
|
||||
node = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
bc = cls(
|
||||
id=pid,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
bc.set_as_deleted()
|
||||
|
||||
bc.name = bc_name
|
||||
|
||||
bc.node = 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
|
||||
|
||||
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(
|
||||
"DELETE FROM boundary_condition_temperature " +
|
||||
f"WHERE pamhyr_id = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id} "
|
||||
)
|
||||
execute(
|
||||
"DELETE FROM boundary_condition_data_temperature " +
|
||||
f"WHERE bca = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id} "
|
||||
)
|
||||
|
||||
node = -1
|
||||
if self._node is not None:
|
||||
node = self._node
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"boundary_condition_temperature(" +
|
||||
"pamhyr_id, deleted, name, " +
|
||||
"node, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', {node}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
data["bca"] = self
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
data["ind"] = ind
|
||||
|
||||
d._db_save(execute, data)
|
||||
|
||||
ind += 1
|
||||
|
||||
return True
|
||||
|
||||
def _data_traversal(self,
|
||||
predicate=lambda obj, data: True,
|
||||
modifier=lambda obj, data: None,
|
||||
data={}):
|
||||
if predicate(self, data):
|
||||
modifier(self, data)
|
||||
|
||||
for d in self._data:
|
||||
d._data_traversal(predicate, modifier, data)
|
||||
|
||||
def __len__(self):
|
||||
return len(
|
||||
list(
|
||||
filter(
|
||||
lambda el: el is not None and not el.is_deleted(),
|
||||
self._data
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def time_convert(cls, data):
|
||||
if type(data) is str:
|
||||
if data.count("-") == 2:
|
||||
return date_iso_to_timestamp(data)
|
||||
if data.count("/") == 2:
|
||||
return date_dmy_to_timestamp(data)
|
||||
if data.count(":") == 3:
|
||||
return old_pamhyr_date_to_timestamp(data)
|
||||
if data.count(":") == 2:
|
||||
return old_pamhyr_date_to_timestamp("00:" + data)
|
||||
if data.count(".") == 1:
|
||||
return round(float(data))
|
||||
|
||||
return int(data)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
return self._node
|
||||
|
||||
@node.setter
|
||||
def node(self, node):
|
||||
self._node = node
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
return self._header.copy()
|
||||
|
||||
@header.setter
|
||||
def header(self, header):
|
||||
self._header = header
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
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 Data(new_0, new_1, types=self._types, status=self._status)
|
||||
|
||||
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):
|
||||
if value in self._data:
|
||||
value.set_as_not_deleted()
|
||||
else:
|
||||
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)
|
||||
@@ -0,0 +1,71 @@
|
||||
# BoundaryConditionsTemperatureList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
from Model.BoundaryConditionsTemperature.BoundaryConditionTemperature \
|
||||
import BoundaryConditionTemperature
|
||||
|
||||
|
||||
class BoundaryConditionsTemperatureList(PamhyrModelList):
|
||||
_sub_classes = [
|
||||
BoundaryConditionTemperature,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
new._lst = BoundaryConditionTemperature._db_load(execute, data)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(
|
||||
"DELETE FROM boundary_condition_temperature " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
execute(
|
||||
"DELETE FROM boundary_condition_data_temperature " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for bca in self._lst:
|
||||
bca._db_save(execute, data=data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, index):
|
||||
n = BoundaryConditionTemperature(status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
@property
|
||||
def BCs_Temperature_List(self):
|
||||
return self.lst
|
||||
@@ -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 + 1
|
||||
)
|
||||
|
||||
@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,86 @@ 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()
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
split_rk = profile.rk
|
||||
reach_rks = reach.reach.get_rk()
|
||||
if len(reach_rks) == 0:
|
||||
return
|
||||
|
||||
parts = [
|
||||
(reach1.id, min(reach_rks), split_rk),
|
||||
(reach2.id, split_rk, max(reach_rks)),
|
||||
]
|
||||
specifications = [
|
||||
specification
|
||||
for specification in self._data
|
||||
if (not specification.is_deleted() and
|
||||
specification.reach == reach.id)
|
||||
]
|
||||
|
||||
for specification in specifications:
|
||||
begin = specification.start_rk
|
||||
end = specification.end_rk
|
||||
if begin is None or end is None:
|
||||
continue
|
||||
|
||||
lower, upper = sorted((begin, end))
|
||||
reverse = begin > end
|
||||
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
start_rk = clipped_upper if reverse else clipped_lower
|
||||
end_rk = clipped_lower if reverse else clipped_upper
|
||||
self._data.append(specification.cloned_for(
|
||||
new_reach, start_rk, end_rk
|
||||
))
|
||||
|
||||
if len(specifications) != 0:
|
||||
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 = {}
|
||||
@@ -58,6 +61,10 @@ class D90AdisTSList(PamhyrModelList):
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
for d90 in self.lst:
|
||||
d90.split_reach(reach, profile, reach1, reach2)
|
||||
|
||||
@property
|
||||
def D90_AdisTS_List(self):
|
||||
return self.lst
|
||||
@@ -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 + 1)
|
||||
|
||||
@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,14 @@ class D90AdisTSSpec(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def cloned_for(self, reach, start_rk, end_rk):
|
||||
new = D90AdisTSSpec(name=self._name_section, status=self._status)
|
||||
new._reach = reach
|
||||
new._start_rk = start_rk
|
||||
new._end_rk = end_rk
|
||||
new._d90 = self._d90
|
||||
new._enabled = self._enabled
|
||||
new.modified()
|
||||
return new
|
||||
@@ -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 + 1
|
||||
)
|
||||
|
||||
@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,86 @@ 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()
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
split_rk = profile.rk
|
||||
reach_rks = reach.reach.get_rk()
|
||||
if len(reach_rks) == 0:
|
||||
return
|
||||
|
||||
parts = [
|
||||
(reach1.id, min(reach_rks), split_rk),
|
||||
(reach2.id, split_rk, max(reach_rks)),
|
||||
]
|
||||
specifications = [
|
||||
specification
|
||||
for specification in self._data
|
||||
if (not specification.is_deleted() and
|
||||
specification.reach == reach.id)
|
||||
]
|
||||
|
||||
for specification in specifications:
|
||||
begin = specification.start_rk
|
||||
end = specification.end_rk
|
||||
if begin is None or end is None:
|
||||
continue
|
||||
|
||||
lower, upper = sorted((begin, end))
|
||||
reverse = begin > end
|
||||
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
start_rk = clipped_upper if reverse else clipped_lower
|
||||
end_rk = clipped_lower if reverse else clipped_upper
|
||||
self._data.append(specification.cloned_for(
|
||||
new_reach, start_rk, end_rk
|
||||
))
|
||||
|
||||
if len(specifications) != 0:
|
||||
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 = {}
|
||||
@@ -58,6 +61,10 @@ class DIFAdisTSList(PamhyrModelList):
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
for dif in self.lst:
|
||||
dif.split_reach(reach, profile, reach1, reach2)
|
||||
|
||||
@property
|
||||
def DIF_AdisTS_List(self):
|
||||
return self.lst
|
||||
@@ -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 + 1)
|
||||
|
||||
@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)
|
||||
@@ -219,3 +293,15 @@ class DIFAdisTSSpec(SQLSubModel):
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
|
||||
def cloned_for(self, reach, start_rk, end_rk):
|
||||
new = DIFAdisTSSpec(method=self._method, status=self._status)
|
||||
new._reach = reach
|
||||
new._start_rk = start_rk
|
||||
new._end_rk = end_rk
|
||||
new._dif = self._dif
|
||||
new._b = self._b
|
||||
new._c = self._c
|
||||
new._enabled = self._enabled
|
||||
new.modified()
|
||||
return new
|
||||
@@ -0,0 +1,307 @@
|
||||
# Distribution.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 Distribution(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
name: str = "", script: str = "",
|
||||
status=None):
|
||||
super(Distribution, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=0
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._type = "generic"
|
||||
self._script = script
|
||||
self._labels = []
|
||||
self._default_values = []
|
||||
self._params_checkers = []
|
||||
self._params_tips = []
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def labels(self):
|
||||
return self._labels
|
||||
|
||||
@property
|
||||
def default_values(self):
|
||||
return self._default_values
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE ensemble_distribution{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_distribution " +
|
||||
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_distribution = cls(
|
||||
id, name=name, script=script,
|
||||
status=data["status"]
|
||||
)
|
||||
if deleted:
|
||||
new_distribution.set_as_deleted()
|
||||
|
||||
loaded.add(id)
|
||||
new.append(new_distribution)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
# Only save custom distribution
|
||||
if self._type != "custom":
|
||||
return True
|
||||
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"ensemble_distribution(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 is_valid_parameters(self, parameters):
|
||||
valid = True
|
||||
tips = []
|
||||
|
||||
for i, checker in enumerate(self._params_checkers):
|
||||
ok = checker(parameters)
|
||||
if not ok:
|
||||
tips.append(self._params_tips[i])
|
||||
valid = False
|
||||
|
||||
return (valid, tips)
|
||||
|
||||
def check_params(self, params):
|
||||
return True
|
||||
|
||||
def random(self, r):
|
||||
raise NotImplementedMethodeError(cls, cls._load)
|
||||
|
||||
|
||||
###############################
|
||||
# DISTRIBUTION IMPLEMENTATION #
|
||||
###############################
|
||||
|
||||
class Uniform(Distribution):
|
||||
_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
|
||||
)
|
||||
|
||||
self._labels = ["low", "high"]
|
||||
self._default_values = [20.0, 40.0]
|
||||
self._params_checkers = [self.check_params, self.check_params]
|
||||
self._params_tips = ["uniform_low_tips", "uniform_high_tips"]
|
||||
|
||||
def check_params(self, params):
|
||||
return (params[0] < params[1])
|
||||
|
||||
def random(self, params, nb):
|
||||
lst = params.copy()
|
||||
lst.append(nb)
|
||||
|
||||
return np.random.uniform(*lst)
|
||||
|
||||
|
||||
class Normal(Distribution):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
name: str = "normal", script: str = "",
|
||||
status=None):
|
||||
super(Normal, self).__init__(
|
||||
id=id, status=status,
|
||||
name=name, script=script
|
||||
)
|
||||
|
||||
self._labels = ["loc", "scale"]
|
||||
self._default_values = [30.0, 5.0]
|
||||
self._params_checkers = [self.check_params, self.check_scale]
|
||||
self._params_tips = ["normal_loc_tips", "normal_scale_tips"]
|
||||
|
||||
def check_scale(self, params):
|
||||
return 0 <= params[1]
|
||||
|
||||
def random(self, params, nb):
|
||||
lst = params.copy()
|
||||
lst.append(nb)
|
||||
|
||||
return np.random.normal(*lst)
|
||||
|
||||
|
||||
class Binomial(Distribution):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
name: str = "binomial", script: str = "",
|
||||
status=None):
|
||||
super(Binomial, self).__init__(
|
||||
id=id, status=status,
|
||||
name=name, script=script
|
||||
)
|
||||
|
||||
self._labels = ["binomial_n", "binomial_p"]
|
||||
self._default_values = [100.0, 0.3]
|
||||
self._params_checkers = [self.check_n, self.check_p]
|
||||
self._params_tips = ["binomial_n_tips", "binomial_p_tips"]
|
||||
|
||||
def check_n(self, params):
|
||||
n = params[0]
|
||||
return (0 <= n) and (n == round(n))
|
||||
|
||||
def check_p(self, params):
|
||||
p = params[1]
|
||||
return 0 <= p <= 1
|
||||
|
||||
def random(self, params, nb):
|
||||
lst = params.copy()
|
||||
lst.append(nb)
|
||||
|
||||
return np.random.binomial(*lst)
|
||||
|
||||
|
||||
class Poisson(Distribution):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
name: str = "poisson", script: str = "",
|
||||
status=None):
|
||||
super(Poisson, self).__init__(
|
||||
id=id, status=status,
|
||||
name=name, script=script
|
||||
)
|
||||
|
||||
self._labels = ["poisson_lam"]
|
||||
self._default_values = [5.0]
|
||||
self._params_checkers = [self.check_lam]
|
||||
self._params_tips = ["poisson_lam_tips"]
|
||||
|
||||
def check_lam(self, params):
|
||||
lam = params[0]
|
||||
return 0 <= lam
|
||||
|
||||
def random(self, params, nb):
|
||||
lst = params.copy()
|
||||
lst.append(nb)
|
||||
|
||||
return np.random.poisson(*lst)
|
||||
|
||||
|
||||
class Custom(Distribution):
|
||||
_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._labels = ["custom"]
|
||||
self._type = "custom"
|
||||
self._default_values = [""]
|
||||
self._params_checkers = [self.check_params]
|
||||
self._params_tips = ["custom_params_tips"]
|
||||
|
||||
def random(self, params, nb):
|
||||
try:
|
||||
# Run script to (re)define the sample distribution
|
||||
f = eval(self._script)
|
||||
return f(params, nb)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
return None # TODO: Raise helpful exception
|
||||
@@ -0,0 +1,62 @@
|
||||
# DistributionList.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.Distribution import *
|
||||
|
||||
|
||||
class DistributionList(PamhyrModelList):
|
||||
_sub_classes = [Distribution]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
# Default distributions
|
||||
d = {"status": data['status']}
|
||||
new._lst = [
|
||||
Uniform(**d),
|
||||
Normal(**d),
|
||||
Binomial(**d),
|
||||
Poisson(**d),
|
||||
]
|
||||
|
||||
# DB custom distributions
|
||||
new._lst += Distribution._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data={}):
|
||||
execute(
|
||||
"DELETE FROM ensemble_distribution"
|
||||
)
|
||||
|
||||
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
|
||||
@@ -0,0 +1,279 @@
|
||||
# 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.Distribution import Uniform
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Ensemble(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
distribution=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._distribution = distribution
|
||||
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,
|
||||
distribution INTEGER NOT NULL,
|
||||
distribution_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"]
|
||||
distributions = data["ens_distributions"]
|
||||
stricklers = data["stricklers"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, name, type, " +
|
||||
"distribution, distribution_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
|
||||
|
||||
distribution = next(
|
||||
filter(fn, distributions.lst),
|
||||
None
|
||||
)
|
||||
|
||||
target_data = next(
|
||||
filter(
|
||||
lambda s: s._pamhyr_id == data_pid,
|
||||
stricklers.lst
|
||||
),
|
||||
None
|
||||
)
|
||||
|
||||
rdata = list(cls._decode_params(params, params_len))
|
||||
|
||||
new_ensemble = cls(
|
||||
id, name=name,
|
||||
distribution=distribution,
|
||||
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._distribution is None:
|
||||
fid = -1
|
||||
fname = "NULL"
|
||||
else:
|
||||
fid = self._distribution._pamhyr_id
|
||||
fname = self._distribution._name
|
||||
|
||||
if self._distribution._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, " +
|
||||
" distribution, distribution_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 random(self, nb):
|
||||
return np.nditer(
|
||||
self._distribution.random(
|
||||
self._params, nb
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def distribution(self):
|
||||
return self._distribution
|
||||
|
||||
@distribution.setter
|
||||
def distribution(self, distribution):
|
||||
self._distribution = distribution
|
||||
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()
|
||||
|
||||
def get_data_dict(self, use_ids=False):
|
||||
target_data = self.target_data
|
||||
distribution = self.distribution
|
||||
|
||||
if use_ids:
|
||||
if self.target_data:
|
||||
target_data = self.target_data.pamhyr_id
|
||||
if self.distribution:
|
||||
distribution = self.distribution.name
|
||||
|
||||
return {
|
||||
"name": self.name,
|
||||
"data_type": self.data_type,
|
||||
"target_data": target_data,
|
||||
"distribution": distribution,
|
||||
"params": self.params,
|
||||
}
|
||||
|
||||
def set_data_dict(self, data):
|
||||
self.name = data["name"]
|
||||
self.data_type = data["data_type"]
|
||||
self.target_data = data["target_data"]
|
||||
self.distribution = data["distribution"]
|
||||
self.params = data["params"]
|
||||
@@ -0,0 +1,62 @@
|
||||
# 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
|
||||
|
||||
def new_with_data(self, index, data):
|
||||
r = Ensemble(status=self._status)
|
||||
|
||||
r.set_data_dict(data)
|
||||
|
||||
self._lst.insert(index, r)
|
||||
self._status.modified()
|
||||
return r
|
||||
@@ -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
|
||||
|
||||
@@ -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 + 1, 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,44 @@ 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))
|
||||
)
|
||||
|
||||
for friction in self.lst:
|
||||
lower, upper = sorted((friction.begin_rk, friction.end_rk))
|
||||
clipped_lower = max(lower, rk_min)
|
||||
clipped_upper = min(upper, rk_max)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
n = friction.cloned_for(reach)
|
||||
if friction.begin_rk > friction.end_rk:
|
||||
n.begin_rk = clipped_upper
|
||||
n.end_rk = clipped_lower
|
||||
else:
|
||||
n.begin_rk = clipped_lower
|
||||
n.end_rk = clipped_upper
|
||||
|
||||
new._lst.append(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
|
||||
@@ -1,5 +1,5 @@
|
||||
# OutputRKListAdists.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# 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
|
||||
@@ -19,18 +19,18 @@
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.OutputRKAdists.OutputRKAdists import OutputRKAdists
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.GeoTIFF.GeoTIFF import GeoTIFF
|
||||
|
||||
|
||||
class OutputRKAdistsList(PamhyrModelList):
|
||||
_sub_classes = [OutputRKAdists]
|
||||
class GeoTIFFList(PamhyrModelList):
|
||||
_sub_classes = [GeoTIFF]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data["status"])
|
||||
|
||||
new._lst = OutputRKAdists._db_load(execute, data)
|
||||
new._lst = GeoTIFF._db_load(execute, data)
|
||||
|
||||
return new
|
||||
|
||||
@@ -38,19 +38,22 @@ class OutputRKAdistsList(PamhyrModelList):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM OutputRKAdists")
|
||||
execute(
|
||||
"DELETE FROM geotiff " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for sl in self._lst:
|
||||
ok &= sl._db_save(execute, data)
|
||||
for gt in self._lst:
|
||||
ok &= gt._db_save(execute, data)
|
||||
|
||||
return ok
|
||||
|
||||
@property
|
||||
def OutputRK_List(self):
|
||||
def files(self):
|
||||
return self.lst
|
||||
|
||||
def new(self, lst, index):
|
||||
n = OutputRKAdists(status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
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,16 +47,21 @@ class Point(object):
|
||||
else:
|
||||
self._sl = sl
|
||||
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
changed = self._name != name
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
if changed and self._profile is not None:
|
||||
self._profile.content_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
|
||||
|
||||
@@ -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,180 @@ 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"
|
||||
)
|
||||
|
||||
# Try to create index at each update
|
||||
execute(
|
||||
"CREATE INDEX IF NOT EXISTS 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,17 +284,55 @@ 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
|
||||
|
||||
@x.setter
|
||||
def x(self, value):
|
||||
changed = self._x != float(value)
|
||||
self._x = float(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
if changed and self._profile is not None:
|
||||
self._profile.content_modified()
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
@@ -168,8 +340,11 @@ class PointXYZ(Point, SQLSubModel):
|
||||
|
||||
@y.setter
|
||||
def y(self, value):
|
||||
changed = self._y != float(value)
|
||||
self._y = float(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
if changed and self._profile is not None:
|
||||
self._profile.content_modified()
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
@@ -177,8 +352,21 @@ class PointXYZ(Point, SQLSubModel):
|
||||
|
||||
@z.setter
|
||||
def z(self, value):
|
||||
changed = self._z != float(value)
|
||||
self._z = float(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
if changed and self._profile is not None:
|
||||
self._profile.content_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,8 +414,13 @@ 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)
|
||||
@@ -240,13 +433,3 @@ class PointXYZ(Point, SQLSubModel):
|
||||
d = np.sqrt(abs(a2 - pow((c2 - b2 + a2) / (2*np.sqrt(c2)), 2)))
|
||||
|
||||
return d
|
||||
|
||||
def copy(self):
|
||||
p = PointXYZ(self.x,
|
||||
self.y,
|
||||
self.z,
|
||||
self.name,
|
||||
self._profile,
|
||||
self._status)
|
||||
p.sl = self.sl
|
||||
return p
|
||||
@@ -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 + 1, 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)
|
||||
@@ -55,6 +47,8 @@ class Profile(object):
|
||||
self._sl = None
|
||||
|
||||
self._points: List[Point] = []
|
||||
self._modification_count = 0
|
||||
self._old_interpolated = ""
|
||||
|
||||
self._profile_type = _type
|
||||
|
||||
@@ -66,24 +60,19 @@ 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]
|
||||
@@ -93,6 +82,9 @@ class Profile(object):
|
||||
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)
|
||||
|
||||
@@ -101,6 +93,12 @@ class Profile(object):
|
||||
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),
|
||||
@@ -122,7 +120,7 @@ class Profile(object):
|
||||
@num.setter
|
||||
def num(self, value: int):
|
||||
self._num = int(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def code1(self):
|
||||
@@ -135,7 +133,7 @@ class Profile(object):
|
||||
@code1.setter
|
||||
def code1(self, value: int):
|
||||
self._code1 = int(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def code2(self):
|
||||
@@ -148,7 +146,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):
|
||||
@@ -164,8 +162,13 @@ class Profile(object):
|
||||
|
||||
@rk.setter
|
||||
def rk(self, value: float):
|
||||
self._rk = float(value)
|
||||
self._status.modified()
|
||||
value = float(value)
|
||||
changed = value != self._rk
|
||||
self._rk = value
|
||||
if changed:
|
||||
self.content_modified()
|
||||
else:
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -177,8 +180,13 @@ class Profile(object):
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str):
|
||||
self._name = value.strip()
|
||||
self._status.modified()
|
||||
value = value.strip()
|
||||
changed = value != self._name
|
||||
self._name = value
|
||||
if changed:
|
||||
self.content_modified()
|
||||
else:
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def sl(self):
|
||||
@@ -191,7 +199,7 @@ class Profile(object):
|
||||
@sl.setter
|
||||
def sl(self, value):
|
||||
self._sl = value
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def profile_type(self):
|
||||
@@ -204,7 +212,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):
|
||||
@@ -219,7 +227,39 @@ class Profile(object):
|
||||
The list of named point
|
||||
"""
|
||||
return [point for point in self.points
|
||||
if point.point_is_named()]
|
||||
if point.is_named()]
|
||||
|
||||
@property
|
||||
def modification_count(self):
|
||||
"""Content revision for this session; not stored in the database."""
|
||||
return self._modification_count
|
||||
|
||||
def increase_modification_count(self):
|
||||
"""Increase content revision for this session;
|
||||
not stored in the database."""
|
||||
self._modification_count += 1
|
||||
|
||||
def decrease_modification_count(self):
|
||||
"""Decrease content revision for this session;
|
||||
not stored in the database."""
|
||||
self._modification_count -= 1
|
||||
|
||||
@property
|
||||
def old_interpolated(self):
|
||||
"""Old interpolated value for this session;
|
||||
not stored in the database."""
|
||||
return self._old_interpolated
|
||||
|
||||
@old_interpolated.setter
|
||||
def old_interpolated(self, value):
|
||||
self._old_interpolated = value
|
||||
|
||||
def content_modified(self):
|
||||
"""An edited profile is no longer an interpolation result."""
|
||||
# self._modification_count += 1
|
||||
if hasattr(self, "_interpolated"):
|
||||
self._interpolated = False
|
||||
self.modified()
|
||||
|
||||
def insert_point(self, index: int, point: Point):
|
||||
"""Insert point at index.
|
||||
@@ -231,21 +271,25 @@ class Profile(object):
|
||||
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.content_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.content_modified()
|
||||
|
||||
def delete_points(self, points):
|
||||
"""Delete some elements in profile list
|
||||
@@ -256,31 +300,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.content_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.content_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.content_modified()
|
||||
|
||||
# Sort
|
||||
|
||||
@@ -293,32 +337,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.content_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.content_modified()
|
||||
|
||||
@timer
|
||||
def reverse(self):
|
||||
self._points.reverse()
|
||||
self._status.modified()
|
||||
self.content_modified()
|
||||
|
||||
# Sediment Layers
|
||||
|
||||
|
||||
@@ -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,15 @@ 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,
|
||||
enabled=True, interpolated=False):
|
||||
"""ProfileXYZ constructor
|
||||
|
||||
Args:
|
||||
@@ -69,15 +74,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
|
||||
@@ -85,12 +91,18 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
self.time_l = 0.0
|
||||
self._station = []
|
||||
self.station_up_to_date = False
|
||||
self._enabled = bool(enabled)
|
||||
self._interpolated = bool(interpolated)
|
||||
|
||||
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 +111,23 @@ 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)
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
interpolated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
|
||||
FOREIGN KEY(sl) REFERENCES sedimentary_layer(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
rl = int(release)
|
||||
@@ -128,83 +148,242 @@ 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"
|
||||
)
|
||||
|
||||
if major == "0" and int(minor) <= 2:
|
||||
if int(release) < 10:
|
||||
execute(
|
||||
"ALTER TABLE geometry_profileXYZ " +
|
||||
"ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT TRUE"
|
||||
)
|
||||
|
||||
if major == "0" and (int(minor) < 2 or
|
||||
(minor == "2" and int(release) < 10)):
|
||||
if minor != "0":
|
||||
execute(
|
||||
"ALTER TABLE geometry_profileXYZ " +
|
||||
"ADD COLUMN interpolated BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
execute(
|
||||
"UPDATE geometry_profileXYZ " +
|
||||
"SET interpolated = CASE " +
|
||||
"WHEN LOWER(name) LIKE 'interpol%' THEN TRUE " +
|
||||
"ELSE FALSE END"
|
||||
)
|
||||
|
||||
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, enabled, interpolated " +
|
||||
"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)
|
||||
enabled = (next(it) == 1)
|
||||
interpolated = (next(it) == 1)
|
||||
|
||||
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,
|
||||
enabled=enabled,
|
||||
interpolated=interpolated
|
||||
)
|
||||
if deleted:
|
||||
profile.set_as_deleted()
|
||||
|
||||
if sl == -1 or sl is None:
|
||||
new._sl = None
|
||||
profile._sl = None
|
||||
else:
|
||||
new._sl = next(
|
||||
profile._sl = next(
|
||||
filter(
|
||||
lambda s: s.id == sl,
|
||||
data["sediment_layers_list"].sediment_layers
|
||||
), None)
|
||||
),
|
||||
None
|
||||
)
|
||||
|
||||
data["profile"] = new
|
||||
new._points = PointXYZ._db_load(execute, data.copy())
|
||||
loaded_save = data['loaded_pid']
|
||||
data['loaded_pid'] = set()
|
||||
data["profile"] = profile
|
||||
|
||||
yield ind, new
|
||||
profile._points = list(
|
||||
map(
|
||||
lambda ip: ip[1],
|
||||
sorted(
|
||||
PointXYZ._db_load(execute, data.copy()),
|
||||
key=lambda ip: ip[0]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
data['loaded_pid'] = loaded_save
|
||||
|
||||
loaded.add(pid)
|
||||
new.append(profile)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
ok = True
|
||||
ind = data["ind"]
|
||||
|
||||
sl = self._sl.id if self._sl is not None else -1
|
||||
sl = self._sl.pamhyr_id if self._sl is not None else -1
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"geometry_profileXYZ(id, ind, name, reach, " +
|
||||
"rk, num, code1, code2, sl) " +
|
||||
"geometry_profileXYZ(pamhyr_id, deleted, ind, name, reach, " +
|
||||
"rk, num, code1, code2, sl, scenario, enabled, interpolated) " +
|
||||
"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}, " +
|
||||
f"{self._db_format(self._enabled)}, " +
|
||||
f"{self._db_format(self._interpolated)}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
points = self.points
|
||||
|
||||
data["profile"] = self
|
||||
execute(f"DELETE FROM geometry_pointXYZ WHERE profile = {self.id}")
|
||||
execute(
|
||||
"DELETE FROM geometry_pointXYZ " +
|
||||
f"WHERE profile = {self.pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
ind = 0
|
||||
for point in points:
|
||||
for point in self._points:
|
||||
data["ind"] = ind
|
||||
ok &= point._db_save(execute, data)
|
||||
ind += 1
|
||||
|
||||
return ok
|
||||
|
||||
def _data_traversal(self,
|
||||
predicate=lambda obj, data: True,
|
||||
modifier=lambda obj, data: None,
|
||||
data={}):
|
||||
if predicate(self, data):
|
||||
modifier(self, data)
|
||||
|
||||
for p in self._points:
|
||||
p._data_traversal(predicate, modifier, data)
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, header, data):
|
||||
profile = None
|
||||
@@ -237,6 +416,50 @@ 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,
|
||||
enabled=self.is_enabled,
|
||||
interpolated=self.interpolated
|
||||
)
|
||||
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,
|
||||
enabled=self.is_enabled,
|
||||
interpolated=self.interpolated
|
||||
)
|
||||
if self.is_deleted():
|
||||
new_p.set_as_deleted()
|
||||
|
||||
new_p._sl = self.sl
|
||||
|
||||
for point in self._points:
|
||||
p = point.cloned_for(new_p)
|
||||
new_p._points.append(p)
|
||||
|
||||
new_p.modified()
|
||||
return new_p
|
||||
|
||||
def point_from_data(self, header, data):
|
||||
def float_format(s: str):
|
||||
return float(
|
||||
@@ -277,6 +500,12 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
return name
|
||||
|
||||
def modified(self):
|
||||
super(ProfileXYZ, self).modified()
|
||||
|
||||
self.tab_up_to_date = False
|
||||
self.station_up_to_date = False
|
||||
|
||||
def x(self):
|
||||
return [point.x for point in self.points]
|
||||
|
||||
@@ -317,11 +546,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.content_modified()
|
||||
|
||||
def get_point_i(self, index: int) -> PointXYZ:
|
||||
"""Get point at index.
|
||||
@@ -380,11 +618,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.content_modified()
|
||||
|
||||
def insert(self, index: int):
|
||||
"""Insert a new point at index.
|
||||
@@ -395,11 +635,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.content_modified()
|
||||
|
||||
return point
|
||||
|
||||
def filter_isnan(self, lst):
|
||||
@@ -413,6 +658,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)
|
||||
|
||||
@@ -464,6 +710,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:
|
||||
@@ -504,6 +751,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
if len(line.coords) > 2:
|
||||
poly = geometry.Polygon(line)
|
||||
area += poly.area
|
||||
|
||||
return area
|
||||
|
||||
def wet_radius(self, z):
|
||||
@@ -526,6 +774,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:
|
||||
@@ -542,8 +791,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])
|
||||
|
||||
@@ -602,7 +851,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:
|
||||
@@ -614,10 +862,20 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
return n_zones
|
||||
|
||||
def froude(self, z, v):
|
||||
return v / np.sqrt(
|
||||
9.81 * (
|
||||
self.wet_area(z) / self.wet_width(z)
|
||||
)
|
||||
)
|
||||
|
||||
@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:
|
||||
@@ -632,11 +890,8 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
for i in range(self.number_points-1):
|
||||
if zz[i] > z and zz[i+1] <= z:
|
||||
y = np.interp(
|
||||
z,
|
||||
[zz[i], zz[i+1]],
|
||||
[station[i], station[i+1]]
|
||||
)
|
||||
fact = (z - zz[i]) / (zz[i+1] - zz[i])
|
||||
y = station[i] + fact * (station[i+1] - station[i])
|
||||
start.append(y)
|
||||
|
||||
end = []
|
||||
@@ -645,82 +900,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
|
||||
@@ -735,7 +1010,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
Projection of the points of the profile on a plane.
|
||||
"""
|
||||
if self.nb_points < 2:
|
||||
return [0.0]
|
||||
return [0.0] * self.nb_points
|
||||
else:
|
||||
if self.station_up_to_date:
|
||||
return self._station
|
||||
@@ -754,13 +1029,13 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
last_point_not_nan = self._last_point_not_nan(points)
|
||||
|
||||
for index, point in enumerate(points):
|
||||
if point.point_is_named():
|
||||
if point.is_named():
|
||||
index_first_named_point = index
|
||||
first_named_point = point
|
||||
break
|
||||
|
||||
for point in reversed(points):
|
||||
if point.point_is_named():
|
||||
if point.is_named():
|
||||
last_named_point = point
|
||||
break
|
||||
|
||||
@@ -831,57 +1106,53 @@ 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
|
||||
@@ -903,12 +1174,14 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
p = ProfileXYZ(name=self.name,
|
||||
rk=self.rk,
|
||||
reach=self.reach,
|
||||
nb_point=0,
|
||||
code1=0, code2=0,
|
||||
status=self.reach._status)
|
||||
for i, k in enumerate(self.points):
|
||||
p.insert_point(i, k.copy())
|
||||
status=self._status,
|
||||
enabled=self.is_enabled,
|
||||
interpolated=self.interpolated)
|
||||
|
||||
for i, k in enumerate(self.points):
|
||||
p.insert_point(i, k.cloned_for(p))
|
||||
|
||||
p._interpolated = self.interpolated
|
||||
return p
|
||||
|
||||
def purge_aligned_points(self):
|
||||
@@ -923,3 +1196,40 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
align = True
|
||||
self.delete_i([i])
|
||||
break
|
||||
|
||||
def hard_purge_aligned_points(self):
|
||||
align = True
|
||||
points = self.points
|
||||
while (align):
|
||||
align = False
|
||||
for i in range(1, len(points) - 1):
|
||||
d = points[i].dist_from_seg(points[i-1],
|
||||
points[i+1])
|
||||
if d < 0.00001 and points[i].name == "":
|
||||
align = True
|
||||
points.pop(i)
|
||||
break
|
||||
self._points = points
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def is_enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, value):
|
||||
self._enabled = bool(value)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def interpolated(self):
|
||||
return self._interpolated
|
||||
|
||||
@interpolated.setter
|
||||
def interpolated(self, value):
|
||||
self._interpolated = bool(value)
|
||||
self.modified()
|
||||
@@ -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,73 @@ 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"])
|
||||
profile_data = data.copy()
|
||||
profile_data["reach"] = new
|
||||
|
||||
new._profiles = ProfileXYZ._db_load(
|
||||
execute,
|
||||
data=data.copy()
|
||||
new._profiles = sorted(
|
||||
ProfileXYZ._db_load(
|
||||
execute,
|
||||
data=profile_data
|
||||
),
|
||||
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,36 +142,31 @@ class Reach(SQLSubModel):
|
||||
return self._parent.name
|
||||
|
||||
def _get_profiles_list(self):
|
||||
# Profiles list generator is type (int, Point) with the first
|
||||
# element the index of the Point in list
|
||||
logger.debug(f"Load profiles from reach {self.name}")
|
||||
return list(
|
||||
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):
|
||||
self._profiles = profiles
|
||||
|
||||
@property
|
||||
def enabled_profiles(self):
|
||||
return list(filter(lambda p: p.is_enabled, self.profiles))
|
||||
|
||||
@property
|
||||
def number_enabled_profiles(self):
|
||||
return len(self.enabled_profiles)
|
||||
|
||||
def get_profiles_from_rk(self, rk):
|
||||
return list(
|
||||
filter(
|
||||
@@ -163,15 +189,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 +200,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 +215,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 +255,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 +276,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(
|
||||
@@ -335,6 +377,28 @@ class Reach(SQLSubModel):
|
||||
)
|
||||
)
|
||||
|
||||
def get_z_min_enabled_profiles(self):
|
||||
return list(
|
||||
map(
|
||||
lambda profile: profile.z_min(),
|
||||
filter(
|
||||
lambda profile: len(profile) > 0 and profile.is_enabled,
|
||||
self.profiles
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def get_z_max_enabled_profiles(self):
|
||||
return list(
|
||||
map(
|
||||
lambda profile: profile.z_max(),
|
||||
filter(
|
||||
lambda profile: len(profile) > 0 and profile.is_enabled,
|
||||
self.profiles
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def get_rk(self):
|
||||
"""List of profiles rk
|
||||
|
||||
@@ -343,6 +407,17 @@ class Reach(SQLSubModel):
|
||||
"""
|
||||
return [profile.rk for profile in self.profiles]
|
||||
|
||||
def get_rk_enabled_profiles(self):
|
||||
return list(
|
||||
map(
|
||||
lambda profile: profile.rk,
|
||||
filter(
|
||||
lambda profile: len(profile) > 0 and profile.is_enabled,
|
||||
self.profiles
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def get_rk_complete_profiles(self):
|
||||
return list(
|
||||
map(
|
||||
@@ -454,14 +529,22 @@ class Reach(SQLSubModel):
|
||||
)
|
||||
|
||||
@timer
|
||||
def compute_guidelines(self):
|
||||
def compute_guidelines(self, profiles=None, update_cache=True):
|
||||
"""Compute reach guidelines
|
||||
|
||||
Args:
|
||||
profiles: Profiles used to compute the guidelines. If omitted,
|
||||
all non-deleted profiles of the reach are used.
|
||||
update_cache: Whether to update the reach guidelines cache.
|
||||
|
||||
Returns:
|
||||
Tuple of complete and incomplete guidelines name.
|
||||
"""
|
||||
if profiles is None:
|
||||
profiles = self.profiles
|
||||
|
||||
# Get all point contained into a guideline
|
||||
named_points = [profile.named_points() for profile in self.profiles]
|
||||
named_points = [profile.named_points() for profile in profiles]
|
||||
points_name = list(
|
||||
map(
|
||||
lambda lst: list(map(lambda p: p.name, lst)),
|
||||
@@ -492,11 +575,13 @@ class Reach(SQLSubModel):
|
||||
|
||||
complete = guide_set - incomplete
|
||||
|
||||
# Compute guideline and put data in cache
|
||||
self._compute_guidelines_cache(guide_set, named_points,
|
||||
complete, incomplete)
|
||||
if update_cache:
|
||||
# Compute guideline and put data in cache
|
||||
self._compute_guidelines_cache(
|
||||
guide_set, named_points, complete, incomplete
|
||||
)
|
||||
self.modified()
|
||||
|
||||
self._status.modified()
|
||||
return (complete, incomplete)
|
||||
|
||||
def _map_guidelines_points(self, func, full=False):
|
||||
@@ -537,28 +622,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 +699,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 +717,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 +731,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 +747,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
|
||||
@@ -717,8 +805,11 @@ class Reach(SQLSubModel):
|
||||
list_profile.append(list_point_profile)
|
||||
list_point_profile = []
|
||||
else:
|
||||
line.append("")
|
||||
list_point_profile.append(line[:3])
|
||||
# MAGE-8 ST files can append hydraulic data
|
||||
# after the regular x, y, z and point-name
|
||||
# fields. ProfileXYZ still expects exactly
|
||||
# those first four fields.
|
||||
list_point_profile.append(line[:4])
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -894,3 +985,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 + 1)
|
||||
|
||||
@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):
|
||||
@@ -192,3 +277,10 @@ class BasicHS(SQLSubModel):
|
||||
|
||||
def convert(self, new_type):
|
||||
return new_type(id=self.id, name=self.name, status=self._status)
|
||||
|
||||
def cloned(self):
|
||||
new = type(self)(name=self._name, status=self._status)
|
||||
new._enabled = self._enabled
|
||||
new._data = [value.cloned() for value in self._data]
|
||||
new.modified()
|
||||
return new
|
||||
@@ -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.31, status=status),
|
||||
BHSValue("half-angle_tangent", 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.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,12 @@ class BHSValue(SQLSubModel):
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._value = self._type(value)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def cloned(self):
|
||||
return BHSValue(
|
||||
name=self._name,
|
||||
type=self._type,
|
||||
value=self._value,
|
||||
status=self._status
|
||||
)
|
||||
@@ -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,59 +37,52 @@ 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._input_reach = None
|
||||
self._output_reach = None
|
||||
self._enabled = True
|
||||
self._data = []
|
||||
|
||||
HydraulicStructure._id_cnt = max(
|
||||
HydraulicStructure._id_cnt + 1,
|
||||
self.id + 1
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
|
||||
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,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(input_reach) REFERENCES river_reach(pamhyr_id),
|
||||
FOREIGN KEY(input_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:
|
||||
execute(
|
||||
@@ -97,101 +91,219 @@ class HydraulicStructure(SQLSubModel):
|
||||
RENAME COLUMN input_kp TO input_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 output_kp TO output_rk
|
||||
"""
|
||||
"ALTER TABLE hydraulic_structures " +
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
return True
|
||||
if major == "0" and (int(minor) < 2 or
|
||||
(minor == "2" and rl < 10)):
|
||||
cls._db_update_to_0_2_10(execute)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_10(cls, execute):
|
||||
table = "hydraulic_structures"
|
||||
cls._db_create(execute, ext="_tmp")
|
||||
|
||||
execute(
|
||||
f"INSERT INTO {table}_tmp " +
|
||||
"(pamhyr_id, deleted, name, enabled, input_section, " +
|
||||
"input_reach, scenario) " +
|
||||
"SELECT pamhyr_id, deleted, name, enabled, input_section, " +
|
||||
f"input_reach, scenario FROM {table}"
|
||||
)
|
||||
|
||||
execute(f"DROP TABLE {table}")
|
||||
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
|
||||
|
||||
@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, " +
|
||||
"input_reach, scenario) " +
|
||||
"SELECT pamhyr_id, name, enabled, " +
|
||||
"input_section, input_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 FROM {table}"
|
||||
)
|
||||
|
||||
for row in els:
|
||||
it = iter(row)
|
||||
pid = next(it)
|
||||
in_reach_id = next(it)
|
||||
|
||||
if in_reach_id == -1:
|
||||
return
|
||||
|
||||
execute(
|
||||
f"UPDATE {table} " +
|
||||
f"SET input_reach = {reachs[in_reach_id]} " +
|
||||
f"WHERE pamhyr_id = {pid}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_1_1(cls, execute, data,
|
||||
origin_version="0.1.0"):
|
||||
execute(
|
||||
"ALTER TABLE hydraulic_structures " +
|
||||
"ADD COLUMN input_section INTEGER"
|
||||
)
|
||||
|
||||
cls._db_update_to_0_1_1_assoc_section_from_rk(
|
||||
execute, "hydraulic_structures",
|
||||
reach_column="input_reach",
|
||||
rk_column="input_rk",
|
||||
section_column="input_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, input_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
|
||||
)
|
||||
input_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 = reduce(
|
||||
lambda acc, n: n if n.pamhyr_id == input_reach_id else acc,
|
||||
data["edges"],
|
||||
None
|
||||
)
|
||||
sections = []
|
||||
if hs.input_reach is not None:
|
||||
sections += hs.input_reach.reach.profiles
|
||||
|
||||
hs.input_section = reduce(
|
||||
lambda acc, s: s if s.pamhyr_id == input_section_id else acc,
|
||||
sections,
|
||||
None
|
||||
)
|
||||
|
||||
loaded.add(hs_id)
|
||||
|
||||
data['hs_id'] = hs_id
|
||||
hs._data = BasicHS._db_load(execute, data)
|
||||
|
||||
return hs
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(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
|
||||
input_section = 'NULL'
|
||||
if self.input_section is not None:
|
||||
input_section = self.input_section.pamhyr_id
|
||||
|
||||
input_rk = -1
|
||||
if self.input_rk is not None:
|
||||
input_rk = self.input_rk
|
||||
|
||||
output_rk = -1
|
||||
if self.output_rk is not None:
|
||||
output_rk = self.output_rk
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"hydraulic_structures(" +
|
||||
" id, name, enabled, input_rk, output_rk, " +
|
||||
" input_reach, output_reach" +
|
||||
" pamhyr_id, deleted, name, enabled, " +
|
||||
" input_section, input_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}, {input_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:
|
||||
@@ -199,41 +311,55 @@ 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
|
||||
def input_section(self):
|
||||
return self._input_section
|
||||
|
||||
@output_rk.setter
|
||||
def output_rk(self, output_rk):
|
||||
self._output_rk = output_rk
|
||||
self._status.modified()
|
||||
@input_section.setter
|
||||
def input_section(self, input_section):
|
||||
self._input_section = input_section
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -242,7 +368,7 @@ class HydraulicStructure(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def input_reach(self):
|
||||
@@ -251,35 +377,43 @@ class HydraulicStructure(SQLSubModel):
|
||||
@input_reach.setter
|
||||
def input_reach(self, input_reach):
|
||||
self._input_reach = input_reach
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def output_reach(self):
|
||||
return self._output_reach
|
||||
|
||||
@output_reach.setter
|
||||
def output_reach(self, output_reach):
|
||||
self._output_reach = output_reach
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def basic_structures(self):
|
||||
return self._data.copy()
|
||||
return self.lst.copy()
|
||||
|
||||
def cloned_for(self, input_reach, input_section):
|
||||
new = HydraulicStructure(name=self._name, status=self._status)
|
||||
new._enabled = self._enabled
|
||||
new._input_reach = input_reach
|
||||
new._input_section = input_section
|
||||
new._data = [structure.cloned() for structure in self.lst]
|
||||
new.modified()
|
||||
return new
|
||||
|
||||
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],
|
||||
@@ -289,20 +423,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,11 +53,72 @@ 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 split_reach(self, reach, profile, reach1, reach2):
|
||||
profiles = reach.reach.profiles
|
||||
split_index = profiles.index(profile)
|
||||
sections1 = dict(zip(
|
||||
profiles[:split_index + 1],
|
||||
reach1.reach.profiles
|
||||
))
|
||||
sections2 = dict(zip(
|
||||
profiles[split_index:],
|
||||
reach2.reach.profiles
|
||||
))
|
||||
|
||||
def split_endpoint(endpoint_reach, section):
|
||||
if endpoint_reach is not reach:
|
||||
return endpoint_reach, section
|
||||
if section not in profiles:
|
||||
return endpoint_reach, section
|
||||
|
||||
index = profiles.index(section)
|
||||
if index < split_index:
|
||||
return reach1, sections1[section]
|
||||
return reach2, sections2[section]
|
||||
|
||||
structures = [
|
||||
structure
|
||||
for structure in self.lst
|
||||
if structure.input_reach is reach
|
||||
]
|
||||
|
||||
for structure in structures:
|
||||
input_reach, input_section = split_endpoint(
|
||||
structure.input_reach,
|
||||
structure.input_section
|
||||
)
|
||||
self._lst.append(structure.cloned_for(
|
||||
input_reach,
|
||||
input_section
|
||||
))
|
||||
|
||||
if len(structures) != 0:
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled_reaches_list(self):
|
||||
return [
|
||||
structure
|
||||
for structure in self.lst
|
||||
if (structure.input_reach is None or
|
||||
(not structure.input_reach.is_deleted() and
|
||||
structure.input_reach.is_enable()))
|
||||
]
|
||||
|
||||
def get_for_enabled_reaches(self, index):
|
||||
structures = self.enabled_reaches_list
|
||||
if not 0 <= index < len(structures):
|
||||
return None
|
||||
return structures[index]
|
||||
|
||||
def __copy__(self):
|
||||
new = HydraulicStructureList()
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -19,37 +19,40 @@
|
||||
import logging
|
||||
|
||||
from copy import copy, deepcopy
|
||||
from math import isclose
|
||||
from tools import trace, timer
|
||||
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 +60,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 +90,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 +249,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,
|
||||
@@ -150,6 +257,17 @@ class Data(SQLSubModel):
|
||||
)
|
||||
return new
|
||||
|
||||
def cloned_for(self, reach, section):
|
||||
return Data(
|
||||
name=self._name,
|
||||
comment=self._comment,
|
||||
section=section,
|
||||
discharge=self._discharge,
|
||||
height=self._height,
|
||||
reach=reach,
|
||||
status=self._status,
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
@@ -161,8 +279,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 +293,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 +322,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 +338,7 @@ class Data(SQLSubModel):
|
||||
self._height = float(value)
|
||||
self._update_from_height()
|
||||
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
|
||||
class InitialConditions(SQLSubModel):
|
||||
@@ -241,8 +359,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 +388,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 +410,83 @@ 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)
|
||||
# Values read from Mage INI files are strings, whereas profile RK
|
||||
# values are stored as floats. Normalize the imported value before
|
||||
# looking up its associated profile.
|
||||
rk = float(rk)
|
||||
|
||||
n['rk'] = rk
|
||||
section = reduce(
|
||||
lambda acc, s: (
|
||||
# Mage INI files store RK values with two decimal places.
|
||||
# Allow for the rounding introduced by that format.
|
||||
s if isclose(s.rk, rk, rel_tol=0.0, abs_tol=0.0051)
|
||||
else acc
|
||||
),
|
||||
self._reach.reach.profiles,
|
||||
None
|
||||
)
|
||||
|
||||
if section is None:
|
||||
raise ValueError(
|
||||
f"No profile with RK {rk} exists in reach "
|
||||
f"{self._reach.name}"
|
||||
)
|
||||
|
||||
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 +502,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 +526,27 @@ 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 _splited_for(self, reach, sections):
|
||||
new = InitialConditions(reach=reach, status=self._status)
|
||||
|
||||
new._data = [
|
||||
data.cloned_for(reach, sections[data["section"]])
|
||||
for data in self.data
|
||||
if data["section"] in sections
|
||||
]
|
||||
|
||||
if len(new._data) != 0:
|
||||
new.modified()
|
||||
|
||||
return new
|
||||
|
||||
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,12 +556,14 @@ 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)
|
||||
@@ -421,21 +604,17 @@ class InitialConditions(SQLSubModel):
|
||||
logger.debug(f" discharge = {discharge}")
|
||||
|
||||
new = Data(reach=self._reach, status=self._status)
|
||||
new["rk"] = profile.rk
|
||||
new["rk"] = profile
|
||||
new["discharge"] = discharge
|
||||
new["elevation"] = elevation
|
||||
|
||||
previous_elevation = elevation
|
||||
self._data.append(new)
|
||||
|
||||
self._data.reverse()
|
||||
|
||||
def generate_discharge(self, discharge: float, compute_height: bool):
|
||||
|
||||
profiles = self._reach.reach.profiles.copy()
|
||||
if profiles is None:
|
||||
return None
|
||||
profiles.reverse()
|
||||
|
||||
previous_elevation = -99999.99
|
||||
|
||||
@@ -446,20 +625,24 @@ 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
|
||||
|
||||
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
|
||||
|
||||
@@ -474,8 +657,9 @@ class InitialConditions(SQLSubModel):
|
||||
else:
|
||||
height = (
|
||||
discharge
|
||||
/
|
||||
((width * 0.8) * strickler * (abs(incline) ** (0.5)))
|
||||
/ ((width * 0.8)
|
||||
* strickler
|
||||
* (abs(incline) ** (0.5)))
|
||||
) ** (0.6)
|
||||
|
||||
elevation = max(
|
||||
@@ -489,15 +673,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,
|
||||
@@ -513,9 +695,9 @@ class InitialConditions(SQLSubModel):
|
||||
data_discharge[profile.rk] = 0.0
|
||||
else:
|
||||
for data in self._data:
|
||||
data_discharge[data["rk"]] = data["discharge"]
|
||||
data_discharge[data["rk"].rk] = data["discharge"]
|
||||
|
||||
self._data = []
|
||||
self.clear_data()
|
||||
for profile in profiles:
|
||||
|
||||
if not compute_discharge:
|
||||
@@ -529,7 +711,7 @@ class InitialConditions(SQLSubModel):
|
||||
[upstream_rk, downstream_rk],
|
||||
[elevation1, elevation2])
|
||||
new = Data(reach=self._reach, status=self._status)
|
||||
new["rk"] = profile.rk
|
||||
new["rk"] = profile
|
||||
new["discharge"] = d
|
||||
new["elevation"] = elevation
|
||||
self._data.append(new)
|
||||
@@ -1,5 +1,5 @@
|
||||
# InitialConditionsDict.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-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)
|
||||
@@ -87,3 +88,28 @@ class InitialConditionsDict(PamhyrModelDict):
|
||||
new = InitialConditions(reach=reach, status=self._status)
|
||||
self.set(reach, new)
|
||||
return new
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
if reach not in self._dict:
|
||||
return
|
||||
|
||||
profiles = reach.reach.profiles
|
||||
split_index = profiles.index(profile)
|
||||
|
||||
sections1 = dict(zip(
|
||||
profiles[:split_index + 1],
|
||||
reach1.reach.profiles
|
||||
))
|
||||
sections2 = dict(zip(
|
||||
profiles[split_index:],
|
||||
reach2.reach.profiles
|
||||
))
|
||||
|
||||
self.set(
|
||||
reach1,
|
||||
self._dict[reach]._splited_for(reach1, sections1)
|
||||
)
|
||||
self.set(
|
||||
reach2,
|
||||
self._dict[reach]._splited_for(reach2, sections2)
|
||||
)
|
||||
@@ -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 + 1
|
||||
)
|
||||
|
||||
@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,102 @@ 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()
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
split_rk = profile.rk
|
||||
reach_rks = reach.reach.get_rk()
|
||||
if len(reach_rks) == 0:
|
||||
return
|
||||
|
||||
lower_reach_rk = min(reach_rks)
|
||||
upper_reach_rk = max(reach_rks)
|
||||
parts = [
|
||||
(reach1.id, lower_reach_rk, split_rk),
|
||||
(reach2.id, split_rk, upper_reach_rk),
|
||||
]
|
||||
specifications = [
|
||||
specification
|
||||
for specification in self._data
|
||||
if (not specification.is_deleted() and
|
||||
specification.reach == reach.id)
|
||||
]
|
||||
|
||||
for specification in specifications:
|
||||
begin = specification.start_rk
|
||||
end = specification.end_rk
|
||||
if begin is None or end is None:
|
||||
continue
|
||||
|
||||
lower, upper = sorted((begin, end))
|
||||
reverse = begin > end
|
||||
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
start_rk = clipped_upper if reverse else clipped_lower
|
||||
end_rk = clipped_lower if reverse else clipped_upper
|
||||
self._data.append(specification.cloned_for(
|
||||
new_reach, start_rk, end_rk
|
||||
))
|
||||
|
||||
if len(specifications) != 0:
|
||||
self.modified()
|
||||
|
||||
def get_specs_for_enabled_reaches(self, reaches):
|
||||
enabled_reaches = {
|
||||
reach.id
|
||||
for reach in reaches
|
||||
if not reach.is_deleted() and reach.is_enable()
|
||||
}
|
||||
return [
|
||||
specification
|
||||
for specification in self._data
|
||||
if (not specification.is_deleted() and
|
||||
(specification.reach in (None, -1) or
|
||||
specification.reach in enabled_reaches))
|
||||
]
|
||||
@@ -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 = {}
|
||||
@@ -59,6 +62,12 @@ class InitialConditionsAdisTSList(PamhyrModelList):
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
for initial_condition in self.lst:
|
||||
initial_condition.split_reach(
|
||||
reach, profile, reach1, reach2
|
||||
)
|
||||
|
||||
@property
|
||||
def Initial_Conditions_List(self):
|
||||
return self.lst
|
||||
@@ -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,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 ICAdisTSSpec(SQLSubModel):
|
||||
_sub_classes = [
|
||||
]
|
||||
_id_cnt = 0
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
super(ICAdisTSSpec, self).__init__()
|
||||
|
||||
self._status = status
|
||||
|
||||
if id == -1:
|
||||
self.id = ICAdisTSSpec._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
status=None, owner_scenario=-1):
|
||||
super(ICAdisTSSpec, self).__init__(
|
||||
id=id,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._name_section = name
|
||||
self._reach = None
|
||||
@@ -53,73 +49,138 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
self._rate = None
|
||||
self._enabled = True
|
||||
|
||||
ICAdisTSSpec._id_cnt = max(ICAdisTSSpec._id_cnt + 1, self.id + 1)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
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 +192,37 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
new_spec.rate = rate
|
||||
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
|
||||
|
||||
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)
|
||||
@@ -168,7 +236,8 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name_section = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
@@ -177,7 +246,8 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@reach.setter
|
||||
def reach(self, reach):
|
||||
self._reach = reach
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def start_rk(self):
|
||||
@@ -186,7 +256,8 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@start_rk.setter
|
||||
def start_rk(self, start_rk):
|
||||
self._start_rk = start_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def end_rk(self):
|
||||
@@ -195,7 +266,8 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@end_rk.setter
|
||||
def end_rk(self, end_rk):
|
||||
self._end_rk = end_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def concentration(self):
|
||||
@@ -204,7 +276,8 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@concentration.setter
|
||||
def concentration(self, concentration):
|
||||
self._concentration = concentration
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def eg(self):
|
||||
@@ -213,7 +286,8 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@eg.setter
|
||||
def eg(self, eg):
|
||||
self._eg = eg
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def em(self):
|
||||
@@ -222,7 +296,8 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@em.setter
|
||||
def em(self, em):
|
||||
self._em = em
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def ed(self):
|
||||
@@ -231,7 +306,8 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@ed.setter
|
||||
def ed(self, ed):
|
||||
self._ed = ed
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def rate(self):
|
||||
@@ -240,7 +316,8 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@rate.setter
|
||||
def rate(self, rate):
|
||||
self._rate = rate
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
@@ -249,4 +326,18 @@ class ICAdisTSSpec(SQLSubModel):
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def cloned_for(self, reach, start_rk, end_rk):
|
||||
new = ICAdisTSSpec(name=self._name_section, status=self._status)
|
||||
new._reach = reach
|
||||
new._start_rk = start_rk
|
||||
new._end_rk = end_rk
|
||||
new._concentration = self._concentration
|
||||
new._eg = self._eg
|
||||
new._em = self._em
|
||||
new._ed = self._ed
|
||||
new._rate = self._rate
|
||||
new._enabled = self._enabled
|
||||
new.modified()
|
||||
return new
|
||||
@@ -0,0 +1,272 @@
|
||||
# InitialConditionsTemperature.py -- Pamhyr
|
||||
# Copyright (C) 2023-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from functools import reduce
|
||||
|
||||
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.InitialConditionsTemperature.InitialConditionsTemperatureSpec \
|
||||
import ICTemperatureSpec
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class InitialConditionsTemperature(SQLSubModel):
|
||||
_sub_classes = [
|
||||
ICTemperatureSpec,
|
||||
]
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "default",
|
||||
status=None, owner_scenario=-1):
|
||||
super(InitialConditionsTemperature, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
self._status = status
|
||||
|
||||
self._name = name
|
||||
self._temperature = None
|
||||
self._data = []
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE initial_conditions_temperature{ext}(
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
temperature REAL NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()}
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == "0":
|
||||
if int(minor) < 2 or (int(minor) == 2 and int(release) <= 7):
|
||||
table_name = "initial_conditions_temperature"
|
||||
if not cls.is_table_exists(execute, table_name):
|
||||
cls._db_create(execute)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
status = data['status']
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, " +
|
||||
"name, temperature, scenario " +
|
||||
"FROM initial_conditions_temperature " +
|
||||
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)
|
||||
name = next(it)
|
||||
temperature = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
ic = cls(
|
||||
id=pid,
|
||||
name=name,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
ic.set_as_deleted()
|
||||
|
||||
ic.temperature = temperature
|
||||
|
||||
data['ic_default_id'] = pid
|
||||
ic._data = ICTemperatureSpec._db_load(execute, data)
|
||||
|
||||
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):
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(
|
||||
"DELETE FROM initial_conditions_temperature " +
|
||||
f"WHERE pamhyr_id = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
temperature = self._temperature if \
|
||||
self._temperature is not None else 0.0
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"initial_conditions_temperature(" +
|
||||
"pamhyr_id, deleted, name, temperature, " +
|
||||
"scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self.is_deleted()}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"{temperature}, {self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
execute(sql)
|
||||
|
||||
data['ic_default_id'] = self.id
|
||||
execute(
|
||||
"DELETE FROM initial_conditions_temperature_spec " +
|
||||
f"WHERE ic_default = {self.id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for ic_spec in self._data:
|
||||
ic_spec._db_save(execute, data)
|
||||
|
||||
return True
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return list(filter(lambda item: not item.is_deleted(), self._data))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
return self._temperature
|
||||
|
||||
@temperature.setter
|
||||
def temperature(self, temperature):
|
||||
self._temperature = temperature
|
||||
self.modified()
|
||||
|
||||
def new(self, index):
|
||||
n = ICTemperatureSpec(status=self._status)
|
||||
self._data.insert(index, n)
|
||||
self.modified()
|
||||
return n
|
||||
|
||||
def delete(self, data):
|
||||
list(
|
||||
map(
|
||||
lambda x: x.set_as_deleted(),
|
||||
data
|
||||
)
|
||||
)
|
||||
self.modified()
|
||||
|
||||
def delete_i(self, indexes):
|
||||
list(
|
||||
map(
|
||||
lambda e: e[1].set_as_deleted(),
|
||||
filter(
|
||||
lambda e: e[0] in indexes,
|
||||
enumerate(self._data)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.modified()
|
||||
|
||||
def insert(self, index, data):
|
||||
if data in self._data:
|
||||
self.undelete([data])
|
||||
else:
|
||||
self._data.insert(index, data)
|
||||
|
||||
self.modified()
|
||||
|
||||
def undelete(self, lst):
|
||||
for x in lst:
|
||||
x.set_as_not_deleted()
|
||||
|
||||
self.modified()
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
parts = []
|
||||
for new_reach in (reach1, reach2):
|
||||
rks = new_reach.reach.get_rk()
|
||||
if rks:
|
||||
parts.append((new_reach.id, min(rks), max(rks)))
|
||||
|
||||
specifications = [
|
||||
specification
|
||||
for specification in self._data
|
||||
if (not specification.is_deleted() and
|
||||
specification.reach == reach.id)
|
||||
]
|
||||
clones = []
|
||||
|
||||
for specification in specifications:
|
||||
begin = specification.start_rk
|
||||
end = specification.end_rk
|
||||
if begin is None or end is None:
|
||||
continue
|
||||
|
||||
lower, upper = sorted((begin, end))
|
||||
reverse = begin > end
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
start_rk = clipped_upper if reverse else clipped_lower
|
||||
end_rk = clipped_lower if reverse else clipped_upper
|
||||
clones.append(specification.cloned_for(
|
||||
new_reach, start_rk, end_rk
|
||||
))
|
||||
|
||||
if clones:
|
||||
self._data.extend(clones)
|
||||
self.modified()
|
||||
@@ -0,0 +1,73 @@
|
||||
# InitialConditionsTemperatureList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.InitialConditionsTemperature.InitialConditionsTemperature \
|
||||
import InitialConditionsTemperature
|
||||
|
||||
|
||||
class InitialConditionsTemperatureList(PamhyrModelList):
|
||||
_sub_classes = [
|
||||
InitialConditionsTemperature,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data['status'])
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
new._lst = InitialConditionsTemperature._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute(
|
||||
"DELETE FROM initial_conditions_temperature " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
for ic in self._lst:
|
||||
ic._db_save(execute, data=data)
|
||||
|
||||
return True
|
||||
|
||||
def new(self, index):
|
||||
n = InitialConditionsTemperature(status=self._status)
|
||||
self._lst.insert(index, n)
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
for initial_condition in self.lst:
|
||||
initial_condition.split_reach(
|
||||
reach, profile, reach1, reach2
|
||||
)
|
||||
|
||||
@property
|
||||
def Initial_Conditions_List(self):
|
||||
return self.lst
|
||||
@@ -0,0 +1,215 @@
|
||||
# InitialConditionsAdisTSSpec.py -- Pamhyr
|
||||
# Copyright (C) 2023-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class ICTemperatureSpec(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None, owner_scenario=-1):
|
||||
super(ICTemperatureSpec, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._name_section = name
|
||||
self._reach = None
|
||||
self._start_rk = None
|
||||
self._end_rk = None
|
||||
self._temperature = None
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE initial_conditions_temperature_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,
|
||||
temperature REAL NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(ic_default)
|
||||
REFERENCES initial_conditions_temperature(pamhyr_id),
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == "0":
|
||||
if int(minor) < 2 or (int(minor) == 2 and int(release) <= 7):
|
||||
table_name = "initial_conditions_temperature_spec"
|
||||
if not cls.is_table_exists(execute, table_name):
|
||||
cls._db_create(execute)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
status = data['status']
|
||||
reachs = data["edges"]
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, name, reach, start_rk, end_rk, " +
|
||||
"temperature, scenario " +
|
||||
"FROM initial_conditions_temperature_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:
|
||||
it = iter(row)
|
||||
|
||||
id = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
reach = next(it)
|
||||
start_rk = next(it)
|
||||
end_rk = next(it)
|
||||
temperature = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
new_spec = cls(
|
||||
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
|
||||
new_spec.end_rk = end_rk
|
||||
new_spec.temperature = temperature
|
||||
|
||||
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
|
||||
|
||||
ic_default = data['ic_default_id']
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"initial_conditions_temperature_spec(pamhyr_id, deleted, " +
|
||||
"ic_default, name, reach, " +
|
||||
"start_rk, end_rk, temperature, scenario) " +
|
||||
"VALUES (" +
|
||||
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}, {self._end_rk}, " +
|
||||
f"{self._temperature}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name_section
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name_section = name
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
|
||||
@reach.setter
|
||||
def reach(self, reach):
|
||||
self._reach = reach
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def start_rk(self):
|
||||
return self._start_rk
|
||||
|
||||
@start_rk.setter
|
||||
def start_rk(self, start_rk):
|
||||
self._start_rk = start_rk
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def end_rk(self):
|
||||
return self._end_rk
|
||||
|
||||
@end_rk.setter
|
||||
def end_rk(self, end_rk):
|
||||
self._end_rk = end_rk
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
return self._temperature
|
||||
|
||||
@temperature.setter
|
||||
def temperature(self, temperature):
|
||||
self._temperature = temperature
|
||||
self._status.modified()
|
||||
|
||||
def cloned_for(self, reach, start_rk, end_rk):
|
||||
new = ICTemperatureSpec(
|
||||
name=self._name_section,
|
||||
status=self._status
|
||||
)
|
||||
new._reach = reach
|
||||
new._start_rk = start_rk
|
||||
new._end_rk = end_rk
|
||||
new._temperature = self._temperature
|
||||
new.modified()
|
||||
return new
|
||||
@@ -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,233 @@ 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 + 1)
|
||||
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)
|
||||
|
||||
def cloned(self):
|
||||
return Data(
|
||||
self[0], self[1],
|
||||
types=self._types,
|
||||
status=self._status
|
||||
)
|
||||
|
||||
|
||||
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 +270,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 +329,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 +489,75 @@ 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
|
||||
|
||||
def cloned_for(self, reach, begin_section, end_section):
|
||||
new = type(self)(name=self._name, status=self._status)
|
||||
new._reach = reach
|
||||
new._begin_section = begin_section
|
||||
new._end_section = end_section
|
||||
new._data = [data.cloned() for data in self.data]
|
||||
new.modified()
|
||||
return new
|
||||
|
||||
@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 +565,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 +604,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 +626,7 @@ class LateralContribution(SQLSubModel):
|
||||
)
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete(self, els):
|
||||
self._data = list(
|
||||
@@ -359,14 +635,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 +654,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 +668,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 +684,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 +692,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 = {}
|
||||
@@ -68,6 +71,89 @@ class LateralContributionList(PamhyrModelListWithTab):
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
profiles = reach.reach.profiles
|
||||
split_index = profiles.index(profile)
|
||||
|
||||
parts = [
|
||||
(
|
||||
reach1,
|
||||
0,
|
||||
split_index,
|
||||
dict(zip(
|
||||
profiles[:split_index + 1],
|
||||
reach1.reach.profiles
|
||||
))
|
||||
),
|
||||
(
|
||||
reach2,
|
||||
split_index,
|
||||
len(profiles) - 1,
|
||||
dict(zip(
|
||||
profiles[split_index:],
|
||||
reach2.reach.profiles
|
||||
))
|
||||
),
|
||||
]
|
||||
|
||||
for tab in self._tabs:
|
||||
contributions = [
|
||||
contribution
|
||||
for contribution in self.get_tab(tab)
|
||||
if contribution.reach is reach
|
||||
]
|
||||
|
||||
for contribution in contributions:
|
||||
if (contribution.begin_section not in profiles or
|
||||
contribution.end_section not in profiles):
|
||||
continue
|
||||
|
||||
begin = profiles.index(contribution.begin_section)
|
||||
end = profiles.index(contribution.end_section)
|
||||
lower, upper = sorted((begin, end))
|
||||
reverse = begin > end
|
||||
clones = []
|
||||
|
||||
for new_reach, part_lower, part_upper, sections in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
begin_index = (
|
||||
clipped_upper if reverse else clipped_lower
|
||||
)
|
||||
end_index = (
|
||||
clipped_lower if reverse else clipped_upper
|
||||
)
|
||||
clones.append(contribution.cloned_for(
|
||||
new_reach,
|
||||
sections[profiles[begin_index]],
|
||||
sections[profiles[end_index]]
|
||||
))
|
||||
|
||||
if len(clones) == 0:
|
||||
continue
|
||||
|
||||
self._tabs[tab].extend(clones)
|
||||
|
||||
self._status.modified()
|
||||
|
||||
def get_tab_for_enabled_reaches(self, tab):
|
||||
return [
|
||||
contribution
|
||||
for contribution in self.get_tab(tab)
|
||||
if (contribution.reach is None or
|
||||
(not contribution.reach.is_deleted() and
|
||||
contribution.reach.is_enable()))
|
||||
]
|
||||
|
||||
def get_for_enabled_reaches(self, tab, index):
|
||||
contributions = self.get_tab_for_enabled_reaches(tab)
|
||||
if not 0 <= index < len(contributions):
|
||||
return None
|
||||
return contributions[index]
|
||||
|
||||
def __copy__(self):
|
||||
new = LateralContributionList()
|
||||
|
||||
|
||||
@@ -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,397 @@ 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)
|
||||
|
||||
def cloned(self):
|
||||
return Data(
|
||||
self[0], self[1],
|
||||
types=self._types,
|
||||
status=self._status
|
||||
)
|
||||
|
||||
|
||||
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 +436,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 +451,7 @@ class LateralContributionAdisTS(SQLSubModel):
|
||||
@header.setter
|
||||
def header(self, header):
|
||||
self._header = header
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def pollutant(self):
|
||||
@@ -199,7 +459,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 +473,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 +482,70 @@ class LateralContributionAdisTS(SQLSubModel):
|
||||
@end_rk.setter
|
||||
def end_rk(self, end_rk):
|
||||
self._end_rk = end_rk
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def cloned_for(self, reach, begin_rk, end_rk):
|
||||
new = LateralContributionAdisTS(
|
||||
pollutant=self._pollutant,
|
||||
status=self._status
|
||||
)
|
||||
new._reach = reach
|
||||
new._begin_rk = begin_rk
|
||||
new._end_rk = end_rk
|
||||
new._data = [data.cloned() for data in self.data]
|
||||
new.modified()
|
||||
return new
|
||||
|
||||
@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 = {}
|
||||
@@ -62,6 +68,46 @@ class LateralContributionsAdisTSList(PamhyrModelList):
|
||||
self._status.modified()
|
||||
return n
|
||||
|
||||
def split_reach(self, reach, profile, reach1, reach2):
|
||||
parts = []
|
||||
for new_reach in (reach1, reach2):
|
||||
rks = new_reach.reach.get_rk()
|
||||
if len(rks) == 0:
|
||||
continue
|
||||
parts.append((new_reach.id, min(rks), max(rks)))
|
||||
|
||||
contributions = [
|
||||
contribution
|
||||
for contribution in self.lst
|
||||
if contribution.reach == reach.id
|
||||
]
|
||||
clones = []
|
||||
|
||||
for contribution in contributions:
|
||||
begin = contribution.begin_rk
|
||||
end = contribution.end_rk
|
||||
if begin is None or end is None:
|
||||
continue
|
||||
|
||||
lower, upper = sorted((begin, end))
|
||||
reverse = begin > end
|
||||
|
||||
for new_reach, part_lower, part_upper in parts:
|
||||
clipped_lower = max(lower, part_lower)
|
||||
clipped_upper = min(upper, part_upper)
|
||||
if clipped_lower >= clipped_upper:
|
||||
continue
|
||||
|
||||
begin_rk = clipped_upper if reverse else clipped_lower
|
||||
end_rk = clipped_lower if reverse else clipped_upper
|
||||
clones.append(contribution.cloned_for(
|
||||
new_reach, begin_rk, end_rk
|
||||
))
|
||||
|
||||
if len(clones) != 0:
|
||||
self._lst.extend(clones)
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def Lat_Cont_List(self):
|
||||
return self.lst
|
||||
@@ -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,29 +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:
|
||||
self.id = type(self)._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
type(self)._id_cnt = max(self.id + 1, type(self)._id_cnt + 1)
|
||||
**kwargs):
|
||||
super(Edge, self).__init__(id=id, **kwargs)
|
||||
|
||||
self._name = name
|
||||
|
||||
self.node1 = node1
|
||||
self.node2 = node2
|
||||
|
||||
@@ -50,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":
|
||||
@@ -64,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":
|
||||
@@ -72,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):
|
||||
@@ -86,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()
|
||||
@@ -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(
|
||||
|
||||
@@ -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,25 +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:
|
||||
self.id = type(self)._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
type(self)._id_cnt = max(self.id + 1, type(self)._id_cnt + 1)
|
||||
**kwargs):
|
||||
super(Node, self).__init__(id=id, **kwargs)
|
||||
|
||||
self._name = name
|
||||
self.pos = Point(x, y)
|
||||
@@ -45,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})"
|
||||
|
||||
@@ -55,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):
|
||||
@@ -77,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,159 +0,0 @@
|
||||
# OutputRKAdists.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
from tools import (
|
||||
trace, timer,
|
||||
old_pamhyr_date_to_timestamp,
|
||||
date_iso_to_timestamp,
|
||||
date_dmy_to_timestamp,
|
||||
)
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
|
||||
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
|
||||
|
||||
self._reach = reach
|
||||
self._rk = rk
|
||||
self._title = str(title)
|
||||
self._enabled = True
|
||||
|
||||
OutputRKAdists._id_cnt = max(
|
||||
OutputRKAdists._id_cnt + 1, self.id + 1)
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
|
||||
@reach.setter
|
||||
def reach(self, reach_id):
|
||||
self._reach = reach_id
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def rk(self):
|
||||
return self._rk
|
||||
|
||||
@rk.setter
|
||||
def rk(self, profile_id):
|
||||
self._rk = profile_id
|
||||
self._status.modified()
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self._title
|
||||
|
||||
@title.setter
|
||||
def title(self, title):
|
||||
self._title = title
|
||||
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)
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
# reach = data["reach"]
|
||||
# profile = data["profile"]
|
||||
status = data["status"]
|
||||
|
||||
table = execute(
|
||||
"SELECT id, reach, rk, title " +
|
||||
f"FROM OutputRKAdists"
|
||||
)
|
||||
|
||||
if table is not None:
|
||||
for row in table:
|
||||
id = row[0]
|
||||
id_reach = row[1]
|
||||
id_rk = row[2]
|
||||
title = row[3]
|
||||
|
||||
new_output = cls(
|
||||
id=id, reach=id_reach,
|
||||
rk=id_rk, title=title,
|
||||
status=status
|
||||
)
|
||||
|
||||
new.append(new_output)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
|
||||
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(sql)
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self._enabled
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
self._status.modified()
|
||||
@@ -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,60 +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
|
||||
|
||||
if name is None or name == "":
|
||||
self.name = f"pol{self.id}"
|
||||
else:
|
||||
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 + 1)
|
||||
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,
|
||||
@@ -91,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
|
||||
@@ -181,7 +525,6 @@ class Pollutants(SQLSubModel):
|
||||
return len(self._data) != 0
|
||||
|
||||
def new_from_data(self, data):
|
||||
|
||||
try:
|
||||
new = [int(data[0])]
|
||||
new += [float(d) for d in data[1:]]
|
||||
|
||||
@@ -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(self.id + 1, REPLine._id_cnt + 1)
|
||||
|
||||
def __getitem__(self, key):
|
||||
value = None
|
||||
|
||||
@@ -113,35 +107,75 @@ class REPLine(SQLSubModel):
|
||||
self._status.modified()
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE rep_lines(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE rep_lines{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
enabled BOOLEAN NOT NULL,
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
line TEXT NOT NULL,
|
||||
solvers TEXT NOT NULL
|
||||
solvers TEXT NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 9:
|
||||
cls._db_create(execute)
|
||||
return True
|
||||
else:
|
||||
cls._db_update_to_0_0_14(execute, data)
|
||||
|
||||
if major == "0" and int(minor) <= 1:
|
||||
if int(release) < 2:
|
||||
execute(
|
||||
"ALTER TABLE rep_lines " +
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_0_14(cls, execute, data):
|
||||
table = "rep_lines"
|
||||
|
||||
cls.update_db_add_pamhyr_id(execute, table, data)
|
||||
Scenario.update_db_add_scenario(execute, table)
|
||||
|
||||
cls._db_create(execute, ext="_tmp")
|
||||
execute(
|
||||
f"INSERT INTO {table}_tmp " +
|
||||
"(pamhyr_id, enabled, name, line, solvers, scenario) " +
|
||||
"SELECT pamhyr_id, enabled, name, line, solvers, scenario " +
|
||||
f"FROM {table}"
|
||||
)
|
||||
|
||||
execute(f"DROP TABLE {table}")
|
||||
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
table = execute(
|
||||
"SELECT id, enabled, name, line, solvers " +
|
||||
"FROM rep_lines"
|
||||
"SELECT pamhyr_id, enabled, deleted, name, line, " +
|
||||
"solvers, scenario " +
|
||||
"FROM rep_lines " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
@@ -149,32 +183,47 @@ class REPLine(SQLSubModel):
|
||||
|
||||
id = next(it)
|
||||
enabled = (next(it) == 1)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
line = next(it)
|
||||
solvers = set(next(it).split(";;"))
|
||||
owner_scenario = next(it)
|
||||
|
||||
f = cls(
|
||||
id=id, enabled=enabled, name=name, line=line,
|
||||
solvers=solvers, status=data['status']
|
||||
id=id, enabled=enabled,
|
||||
name=name, line=line, solvers=solvers,
|
||||
status=data['status'], owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
f.set_as_deleted()
|
||||
|
||||
loaded.add(id)
|
||||
new.append(f)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
solvers = ";;".join(self._solvers)
|
||||
|
||||
sql = (
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"rep_lines(id, enabled, name, line, solvers) " +
|
||||
"rep_lines(pamhyr_id, enabled, deleted, " +
|
||||
"name, line, solvers, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._enabled}, " +
|
||||
f"{self._pamhyr_id}, {self._enabled}, " +
|
||||
f"{self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._line)}', " +
|
||||
f"'{self._db_format(solvers)}'" +
|
||||
f"'{self._db_format(solvers)}', " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
@@ -1,5 +1,5 @@
|
||||
# REPLineList.py -- Pamhyr
|
||||
# Copyright (C) 2024 INRAE
|
||||
# Copyright (C) 2024-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -19,7 +19,7 @@
|
||||
from tools import trace, timer
|
||||
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
from Model.Tools.PamhyrList import PamhyrModelList
|
||||
from Model.Tools.PamhyrListExt import PamhyrModelList
|
||||
from Model.REPLine.REPLine import REPLine
|
||||
|
||||
|
||||
@@ -38,7 +38,10 @@ class REPLineList(PamhyrModelList):
|
||||
ok = True
|
||||
|
||||
# Delete previous data
|
||||
execute("DELETE FROM rep_lines")
|
||||
execute(
|
||||
"DELETE FROM rep_lines " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
for af in self._lst:
|
||||
ok &= af._db_save(execute, data)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Reservoir.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-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,148 +19,384 @@
|
||||
import logging
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Reservoir(SQLSubModel):
|
||||
class Data(SQLSubModel):
|
||||
_sub_classes = []
|
||||
_id_cnt = 0
|
||||
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
status=None):
|
||||
super(Reservoir, self).__init__()
|
||||
def __init__(self, id: int = -1,
|
||||
elevation: float = 0.,
|
||||
surface: float = 0.,
|
||||
status=None, owner_scenario=-1):
|
||||
super(Data, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._status = status
|
||||
self._data = [elevation, surface]
|
||||
|
||||
if id == -1:
|
||||
self.id = Reservoir._id_cnt
|
||||
else:
|
||||
self.id = id
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE reservoir_data{ext}(
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ind INTEGER NOT NULL,
|
||||
elevation REAL NOT NULL,
|
||||
surface REAL NOT NULL,
|
||||
reservoir INTEGER,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(reservoir) REFERENCES reservoir(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == minor == "0":
|
||||
if int(release) > 5:
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
|
||||
if major == "0" and int(minor) <= 1:
|
||||
if int(release) < 2:
|
||||
execute(
|
||||
"ALTER TABLE reservoir_data " +
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0(cls, execute, data):
|
||||
table = "reservoir_data"
|
||||
id2pid = data['id2pid']
|
||||
|
||||
cls.update_db_add_pamhyr_id(
|
||||
execute, table,
|
||||
data=data
|
||||
)
|
||||
Scenario.update_db_add_scenario(execute, table)
|
||||
|
||||
cls._db_create(execute, ext="_tmp")
|
||||
execute(
|
||||
f"INSERT INTO {table}_tmp " +
|
||||
"(pamhyr_id, ind, elevation, surface, reservoir, scenario) " +
|
||||
"SELECT pamhyr_id, ind, elevation, surface, reservoir, scenario " +
|
||||
f"FROM {table}"
|
||||
)
|
||||
|
||||
execute(f"DROP TABLE {table}")
|
||||
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
|
||||
|
||||
if 'reservoir' in id2pid:
|
||||
reservoirs = id2pid['reservoir']
|
||||
cls._db_update_to_0_2_0_set_reservoir_pid(
|
||||
execute, table, reservoirs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0_set_reservoir_pid(cls, execute, table, reservoirs):
|
||||
bcs = execute(
|
||||
f"SELECT pamhyr_id, reservoir FROM {table}"
|
||||
)
|
||||
|
||||
for row in bcs:
|
||||
it = iter(row)
|
||||
pid = next(it)
|
||||
reservoir_id = next(it)
|
||||
|
||||
execute(
|
||||
f"UPDATE {table} " +
|
||||
f"SET reservoir = {reservoirs[reservoir_id]} " +
|
||||
f"WHERE pamhyr_id = {pid}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
scenario = data["scenario"]
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
loaded = data['loaded_pid']
|
||||
reseroire = data["reservoir"]
|
||||
|
||||
values = execute(
|
||||
"SELECT pamhyr_id, deleted, ind, " +
|
||||
"elevation, surface, scenario " +
|
||||
"FROM reservoir_data " +
|
||||
f"WHERE reservoir = {reseroire.pamhyr_id} " +
|
||||
f"AND scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) " +
|
||||
"ORDER BY ind ASC"
|
||||
)
|
||||
|
||||
for v in values:
|
||||
it = iter(v)
|
||||
|
||||
pid = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
ind = next(it)
|
||||
elevation = next(it)
|
||||
surface = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
nd = cls(
|
||||
id=pid, status=data['status'],
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
nd.set_as_deleted()
|
||||
|
||||
nd._data = [elevation, surface]
|
||||
|
||||
loaded.add(pid)
|
||||
new.append(nd)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
pid = self._pamhyr_id
|
||||
ind = data["ind"]
|
||||
elevation = self._db_format(str(self[0]))
|
||||
surface = self._db_format(str(self[1]))
|
||||
reservoir = data["reservoir"]
|
||||
|
||||
execute(
|
||||
"INSERT INTO reservoir_data " +
|
||||
"(pamhyr_id, deleted, ind, " +
|
||||
"elevation, surface, reservoir, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{pid}, " +
|
||||
f"{self._db_format(self.is_deleted())}, " +
|
||||
f"{ind}, '{elevation}', {surface}, " +
|
||||
f"{reservoir._pamhyr_id}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._data[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._data[key] = float(value)
|
||||
|
||||
|
||||
class Reservoir(SQLSubModel):
|
||||
_sub_classes = [Data]
|
||||
|
||||
def __init__(self, id: int = -1,
|
||||
name: str = "",
|
||||
status=None, owner_scenario=-1):
|
||||
super(Reservoir, self).__init__(
|
||||
id=id, status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._node = None
|
||||
self._data = []
|
||||
|
||||
Reservoir._id_cnt = max(Reservoir._id_cnt + 1, self.id + 1)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE reservoir(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE reservoir{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
node INTEGER,
|
||||
FOREIGN KEY(node) REFERENCES river_node(id)
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(node) REFERENCES river_node(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
execute("""
|
||||
CREATE TABLE reservoir_data(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
ind INTEGER NOT NULL,
|
||||
elevation REAL NOT NULL,
|
||||
surface REAL NOT NULL,
|
||||
reservoir INTEGER,
|
||||
FOREIGN KEY(reservoir) REFERENCES reservoir(id)
|
||||
)
|
||||
""")
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
if major == minor == "0":
|
||||
if int(release) < 5:
|
||||
cls._db_create(execute)
|
||||
return True
|
||||
|
||||
return True
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
|
||||
if major == "0" and minor == "1":
|
||||
if int(release) < 2:
|
||||
execute(
|
||||
"ALTER TABLE additional_files " +
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0(cls, execute, data):
|
||||
table = "reservoir"
|
||||
nodes = data['id2pid']['river_node']
|
||||
|
||||
cls.update_db_add_pamhyr_id(execute, table, data=data)
|
||||
cls._db_update_to_0_2_0_set_node_pid(execute, table, nodes)
|
||||
Scenario.update_db_add_scenario(execute, table)
|
||||
|
||||
cls._db_create(execute, ext="_tmp")
|
||||
execute(
|
||||
f"INSERT INTO {table}_tmp " +
|
||||
"(pamhyr_id, name, node, scenario) " +
|
||||
"SELECT pamhyr_id, name, node, scenario " +
|
||||
f"FROM {table}"
|
||||
)
|
||||
|
||||
execute(f"DROP TABLE {table}")
|
||||
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name, node " +
|
||||
"FROM reservoir "
|
||||
"SELECT pamhyr_id, deleted, name, node, scenario " +
|
||||
"FROM reservoir " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
node_id = row[2]
|
||||
new_reservoir = cls(id, name, status=data["status"])
|
||||
it = iter(row)
|
||||
|
||||
id = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
node_id = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
new_reservoir = cls(
|
||||
id, name,
|
||||
status=data["status"],
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
new_reservoir.set_as_deleted()
|
||||
|
||||
new_reservoir._node = None
|
||||
if node_id != -1:
|
||||
if node_id != -1 and node_id is not None:
|
||||
new_reservoir._node = next(
|
||||
filter(
|
||||
lambda n: n.id == node_id, data["nodes"]
|
||||
), None
|
||||
)
|
||||
|
||||
new_data = []
|
||||
table = execute(
|
||||
"SELECT elevation, surface " +
|
||||
"FROM reservoir_data " +
|
||||
f"WHERE reservoir = {id} " +
|
||||
"ORDER BY ind ASC"
|
||||
data["reservoir"] = new_reservoir
|
||||
new_reservoir._data = Data._db_load(
|
||||
execute, data=data
|
||||
)
|
||||
for t in table:
|
||||
new_data.append((t[0], t[1]))
|
||||
|
||||
new_reservoir._data = new_data
|
||||
|
||||
loaded.add(id)
|
||||
new.append(new_reservoir)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
new += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(f"DELETE FROM reservoir WHERE id = {self.id}")
|
||||
execute(f"DELETE FROM reservoir_data WHERE reservoir = {self.id}")
|
||||
execute(
|
||||
"DELETE FROM reservoir " +
|
||||
f"WHERE pamhyr_id = {self.pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
execute(
|
||||
"DELETE FROM reservoir_data " +
|
||||
f"WHERE reservoir = {self.pamhyr_id} " +
|
||||
f"AND scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
node_id = -1
|
||||
node_id = 'NULL'
|
||||
if self._node is not None:
|
||||
node_id = self._node.id
|
||||
node_id = self._node.pamhyr_id
|
||||
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"reservoir(id, name, node) " +
|
||||
"reservoir(pamhyr_id, deleted, name, node, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"{node_id}" +
|
||||
f"{self.pamhyr_id}, " +
|
||||
f"{self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"{node_id}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
data["reservoir"] = self
|
||||
|
||||
ind = 0
|
||||
for d in self._data:
|
||||
sql = (
|
||||
"INSERT INTO " +
|
||||
"reservoir_data(ind, elevation, surface, reservoir) " +
|
||||
f"VALUES ({ind}, '{d[0]}', {d[1]}, {self.id})"
|
||||
)
|
||||
execute(sql)
|
||||
data["ind"] = ind
|
||||
|
||||
d._db_save(execute, data)
|
||||
|
||||
ind += 1
|
||||
|
||||
return True
|
||||
|
||||
def _data_traversal(self,
|
||||
predicate=lambda obj, data: True,
|
||||
modifier=lambda obj, data: None,
|
||||
data={}):
|
||||
if predicate(self, data):
|
||||
modifier(self, data)
|
||||
|
||||
for d in self._data:
|
||||
d._data_traversal(predicate, modifier, data)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == "":
|
||||
return f"R{self.id + 1}"
|
||||
return f"Reservoir #{self.pamhyr_id}"
|
||||
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
@@ -169,7 +405,7 @@ class Reservoir(SQLSubModel):
|
||||
@node.setter
|
||||
def node(self, node):
|
||||
self._node = node
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def has_node(self):
|
||||
return self._node is not None
|
||||
@@ -202,42 +438,63 @@ class Reservoir(SQLSubModel):
|
||||
return (new_0, new_1)
|
||||
|
||||
def add(self, index: int):
|
||||
value = (self._default_elevation, self._default_surface)
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
return value
|
||||
data = Data(
|
||||
elevation=self._default_elevation,
|
||||
surface=self._default_surface,
|
||||
status=self._status,
|
||||
)
|
||||
|
||||
self._data.insert(index, data)
|
||||
self.modified()
|
||||
return data
|
||||
|
||||
def insert(self, index: int, value):
|
||||
self._data.insert(index, value)
|
||||
self._status.modified()
|
||||
if type(value) is tuple or type(value) is list:
|
||||
value = Data(
|
||||
elevation=value[0],
|
||||
surface=value[1]
|
||||
)
|
||||
|
||||
def delete_i(self, indexes):
|
||||
self._data.insert(index, value)
|
||||
self.modified()
|
||||
|
||||
def hard_delete_i(self, indexes):
|
||||
self._data = list(
|
||||
map(
|
||||
lambda e: e[1],
|
||||
filter(
|
||||
lambda e: e[0] not in indexes,
|
||||
enumerate(self.data)
|
||||
enumerate(self._data)
|
||||
)
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete(self, els):
|
||||
self._data = list(
|
||||
filter(
|
||||
lambda e: e not in els,
|
||||
self.data
|
||||
def delete_i(self, indexes):
|
||||
list(
|
||||
map(
|
||||
lambda e: e[1].set_as_deleted(),
|
||||
filter(
|
||||
lambda e: e[0] in indexes,
|
||||
enumerate(self.lst)
|
||||
)
|
||||
)
|
||||
)
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def delete(self, els):
|
||||
for el in els:
|
||||
el.set_as_deleted()
|
||||
|
||||
self.modified()
|
||||
|
||||
def sort(self, _reverse=False, key=None):
|
||||
if key is None:
|
||||
self._data.sort(reverse=_reverse)
|
||||
self._data.sort(reverse=_reverse, key=lambda d: d[0])
|
||||
else:
|
||||
self._data.sort(reverse=_reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
self.modified()
|
||||
|
||||
def get_i(self, index):
|
||||
return self.data[index]
|
||||
@@ -246,13 +503,12 @@ class Reservoir(SQLSubModel):
|
||||
lst = []
|
||||
for r in _range:
|
||||
lst.append(r)
|
||||
|
||||
return lst
|
||||
|
||||
def _set_i_c_v(self, index, column, value):
|
||||
v = list(self._data[index])
|
||||
v[column] = value
|
||||
self._data[index] = tuple(v)
|
||||
self._status.modified()
|
||||
self._data[index][column] = value
|
||||
self.modified()
|
||||
|
||||
def set_i_elevation(self, index: int, value):
|
||||
self._set_i_c_v(index, 0, value)
|
||||
@@ -265,11 +521,11 @@ class Reservoir(SQLSubModel):
|
||||
next = index - 1
|
||||
d = self._data
|
||||
d[index], d[next] = d[next], d[index]
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
|
||||
def move_down(self, index):
|
||||
if index >= 0:
|
||||
prev = index + 1
|
||||
d = self._data
|
||||
d[index], d[prev] = d[prev], d[index]
|
||||
self._status.modified()
|
||||
self.modified()
|
||||
@@ -1,5 +1,5 @@
|
||||
# ReservoirList.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-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.Reservoir.Reservoir import Reservoir
|
||||
|
||||
|
||||
@@ -39,8 +39,14 @@ class ReservoirList(PamhyrModelList):
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
execute("DELETE FROM reservoir")
|
||||
execute("DELETE FROM reservoir_data")
|
||||
execute(
|
||||
"DELETE FROM reservoir " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
execute(
|
||||
"DELETE FROM reservoir_data " +
|
||||
f"WHERE scenario = {self._status.scenario_id}"
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Results.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-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
|
||||
@@ -14,32 +14,357 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import struct
|
||||
import logging
|
||||
import itertools
|
||||
import numpy as np
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
|
||||
from tools import timer
|
||||
|
||||
from Model.Scenario import Scenario
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Results.River.River import River
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Results(object):
|
||||
def __init__(self, study=None, solver=None,
|
||||
repertory="", name="0"):
|
||||
class AdditionalData(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id=-1, study=None, data=None):
|
||||
super(AdditionalData, self).__init__(
|
||||
id=id, status=study.status,
|
||||
owner_scenario=study.status.scenario.id
|
||||
)
|
||||
|
||||
self._study = study
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE results_add_data{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
result INTEGER NOT NULL,
|
||||
type_x TEXT NOT NULL,
|
||||
type_y TEXT NOT NULL,
|
||||
legend TEXT NOT NULL,
|
||||
unit TEXT NOT NULL,
|
||||
data_len INTEGER NOT NULL,
|
||||
x BLOB NOT NULL,
|
||||
y BLOB NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(result) REFERENCES results(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, result, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext != "":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == "0" and int(minor) == 2 and int(release) <= 1:
|
||||
cls._db_create(execute)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = []
|
||||
|
||||
study = data['study']
|
||||
status = data['status']
|
||||
scenario = data["scenario"]
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, type_x, type_y, " +
|
||||
"legend, unit, data_len, x, y, " +
|
||||
"scenario " +
|
||||
"FROM results_add_data " +
|
||||
f"WHERE scenario = {scenario.id}"
|
||||
)
|
||||
|
||||
if table is None:
|
||||
return new
|
||||
|
||||
for v in table:
|
||||
it = iter(v)
|
||||
|
||||
pid = next(it)
|
||||
type_x = next(it)
|
||||
type_y = next(it)
|
||||
legend = next(it)
|
||||
unit = next(it)
|
||||
data_len = next(it)
|
||||
bx = next(it)
|
||||
by = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
data_format = ">" + ''.join(itertools.repeat("d", data_len))
|
||||
x = struct.unpack(data_format, bx)
|
||||
y = struct.unpack(data_format, by)
|
||||
|
||||
data = {
|
||||
'type_x': type_x,
|
||||
'type_y': type_y,
|
||||
'legend': legend,
|
||||
'unit': unit,
|
||||
'x': x, 'y': y
|
||||
}
|
||||
|
||||
new_data = cls(study=study)
|
||||
new_data._data = data
|
||||
new.append(new_data)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if self._status.scenario.id != self._owner_scenario:
|
||||
return
|
||||
|
||||
pid = self._pamhyr_id
|
||||
data_len = len(self._data["x"])
|
||||
|
||||
data_format = ">" + ''.join(itertools.repeat("d", data_len))
|
||||
bx = struct.pack(data_format, *self._data["x"])
|
||||
by = struct.pack(data_format, *self._data["y"])
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"results_add_data (pamhyr_id, result, " +
|
||||
"type_x, type_y, " +
|
||||
"legend, unit, data_len, x, y, " +
|
||||
"scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
self._pamhyr_id, data["result"],
|
||||
self._data["type_x"], self._data["type_y"],
|
||||
self._data["legend"], self._data["unit"],
|
||||
data_len, bx, by, self._owner_scenario
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class TableData(SQLSubModel):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id=-1, study=None,
|
||||
legend="", data=None,
|
||||
owner_scenario=-1):
|
||||
super(TableData, self).__init__(
|
||||
id=id, status=study.status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._study = study
|
||||
self._legend = legend
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE results_table_data{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
result INTEGER NOT NULL,
|
||||
legend TEXT NOT NULL,
|
||||
unit TEXT NOT NULL,
|
||||
data_shape_x INTEGER NOT NULL,
|
||||
data_shape_y INTEGER NOT NULL,
|
||||
data_shape_z INTEGER DEFAULT -1,
|
||||
data BLOB NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(result) REFERENCES results(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, result, legend, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext != "":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
if not cls.is_table_exists(execute, "results_table_data"):
|
||||
cls._db_create(execute)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
res = {}
|
||||
|
||||
study = data['study']
|
||||
status = data['status']
|
||||
scenario = data["scenario"]
|
||||
result = data["result"]
|
||||
rid = data["result_pid"]
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, " +
|
||||
"legend, unit, " +
|
||||
"data_shape_x, data_shape_y, data_shape_z, " +
|
||||
"data, " +
|
||||
"scenario " +
|
||||
"FROM results_table_data " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND result = {rid}"
|
||||
)
|
||||
|
||||
for v in table:
|
||||
it = iter(v)
|
||||
|
||||
pid = next(it)
|
||||
legend = next(it)
|
||||
unit = next(it)
|
||||
data_shape_x = next(it)
|
||||
data_shape_y = next(it)
|
||||
data_shape_z = next(it)
|
||||
data = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
dtype = cls._unit_to_dtype(unit)
|
||||
|
||||
np_data = np.frombuffer(data, dtype=dtype)
|
||||
if data_shape_y == -1:
|
||||
np_data = np.reshape(np_data, data_shape_x)
|
||||
elif data_shape_z == -1:
|
||||
np_data = np.reshape(np_data, (data_shape_x,
|
||||
data_shape_y))
|
||||
else:
|
||||
np_data = np.reshape(np_data, (data_shape_x,
|
||||
data_shape_y,
|
||||
data_shape_z))
|
||||
|
||||
new_table = cls(study=study,
|
||||
legend=legend, data=np_data,
|
||||
owner_scenario=owner_scenario)
|
||||
|
||||
res[legend] = new_table
|
||||
|
||||
return res
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if self._status.scenario.id != self._owner_scenario:
|
||||
return
|
||||
|
||||
shape_x = -1
|
||||
shape_y = -1
|
||||
shape_z = -1
|
||||
shape = self._data.shape
|
||||
|
||||
if len(shape) == 2:
|
||||
shape_x, shape_y = shape
|
||||
elif len(shape) == 1:
|
||||
shape_x, = shape
|
||||
elif len(shape) == 3:
|
||||
shape_x, shape_y, shape_z = shape
|
||||
else:
|
||||
logger.error(
|
||||
f"Failed to parse numpy shape: {shape}"
|
||||
)
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"results_table_data (pamhyr_id, result, " +
|
||||
"legend, unit, " +
|
||||
"data_shape_x, data_shape_y, data_shape_z, " +
|
||||
"data, " +
|
||||
"scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
self._pamhyr_id, data["result"],
|
||||
self._legend, self._dtype_to_unit(self._data.dtype),
|
||||
shape_x, shape_y, shape_z,
|
||||
self._data,
|
||||
self._owner_scenario
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _unit_to_dtype(cls, unit):
|
||||
t = {
|
||||
"i32": np.int32, "i64": np.int64,
|
||||
"u32": np.uint32, "u64": np.uint64,
|
||||
"f32": np.float32, "f64": np.float64,
|
||||
}
|
||||
|
||||
return t[unit]
|
||||
|
||||
def _dtype_to_unit(self, dtype):
|
||||
t = {
|
||||
np.dtype(np.int32): "i32", np.dtype(np.int64): "i64",
|
||||
np.dtype(np.uint32): "u32", np.dtype(np.uint64): "u64",
|
||||
np.dtype(np.float32): "f32", np.dtype(np.float64): "f64",
|
||||
}
|
||||
|
||||
return t[dtype]
|
||||
|
||||
def table(self):
|
||||
return self._data
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, tuple):
|
||||
return self._data[key[0], key[1]]
|
||||
|
||||
self._data[key]
|
||||
|
||||
|
||||
class Results(SQLSubModel):
|
||||
_sub_classes = [River, TableData, AdditionalData]
|
||||
|
||||
def __init__(self, id=-1, study=None, solver=None,
|
||||
repertory="", name="0", is_ensemble=False):
|
||||
super(Results, self).__init__(
|
||||
id=id, status=study.status,
|
||||
owner_scenario=study.status.scenario.id
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._study = study
|
||||
# Solver readers add their own reaches; ensembles need them upfront.
|
||||
self._river = River(self._study, self, init=is_ensemble)
|
||||
|
||||
self._solver = solver
|
||||
self._repertory = repertory
|
||||
self._name = name
|
||||
|
||||
self._river = River(self._study)
|
||||
self._is_ensemble = is_ensemble
|
||||
|
||||
self._meta_data = {
|
||||
# Keep results creation date
|
||||
"creation_date": datetime.now(),
|
||||
"study_revision": study.status.version,
|
||||
"additional_data": [],
|
||||
"table": {},
|
||||
}
|
||||
|
||||
self._ts_index = {}
|
||||
|
||||
if solver is not None:
|
||||
self.set("solver_type", solver._type)
|
||||
|
||||
def is_ensemble(self):
|
||||
return self._is_ensemble
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
date = self._meta_data["creation_date"]
|
||||
@@ -53,6 +378,7 @@ class Results(object):
|
||||
def study(self):
|
||||
return self._study
|
||||
|
||||
@timer
|
||||
def set(self, key, value):
|
||||
self._meta_data[key] = value
|
||||
|
||||
@@ -60,12 +386,275 @@ class Results(object):
|
||||
def is_valid(self):
|
||||
return ("timestamps" in self._meta_data)
|
||||
|
||||
@property
|
||||
def solver_name(self):
|
||||
if self._solver is None:
|
||||
return self._meta_data["solver_name"]
|
||||
|
||||
return self._solver.name
|
||||
|
||||
def get(self, key):
|
||||
return self._meta_data[key]
|
||||
|
||||
def set_timestamp_index(self, index):
|
||||
self._ts_index = index
|
||||
|
||||
def get_timestamp_id(self, ts):
|
||||
return self._ts_index[ts]
|
||||
|
||||
def compute_timestamp_index(self):
|
||||
for i, ts in enumerate(self._meta_data["timestamps"]):
|
||||
self._ts_index[ts] = i
|
||||
|
||||
def reload(self):
|
||||
return self._solver.results(
|
||||
self._study,
|
||||
self._repertory,
|
||||
qlog=None,
|
||||
)
|
||||
|
||||
def bufferize(self, key):
|
||||
if self.is_valid:
|
||||
for reach in self._river._reachs:
|
||||
reach.bufferize(self._meta_data["timestamps"], key)
|
||||
|
||||
def timestamps_to_struct(self):
|
||||
ts = self._meta_data["timestamps"]
|
||||
sf = ">" + ''.join(itertools.repeat("d", len(ts)))
|
||||
|
||||
return struct.pack(sf, ts)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE results{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
solver_name TEXT NOT NULL,
|
||||
solver_type TEXT NOT NULL,
|
||||
study_revision INTEGER NOT NULL,
|
||||
creation_date DATE NOT NULL,
|
||||
nb_timestamps INTEGER NOT NULL,
|
||||
timestamps BLOB NOT NULL,
|
||||
seed INTEGER,
|
||||
number_of_run INTEGER,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
if ext != "":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
create = False
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
cls._db_create(execute)
|
||||
create = True
|
||||
|
||||
if major == "0" and int(minor) == 2:
|
||||
if int(release) < 1 and not create:
|
||||
cls._db_create(execute)
|
||||
create = True
|
||||
|
||||
if major == "0" and int(minor) <= 2:
|
||||
if int(release) < 9 and not create:
|
||||
execute(
|
||||
"ALTER TABLE results ADD COLUMN " +
|
||||
"seed INTEGER"
|
||||
)
|
||||
execute(
|
||||
"ALTER TABLE results ADD COLUMN " +
|
||||
"number_of_run INTEGER"
|
||||
)
|
||||
execute(
|
||||
"ALTER TABLE results RENAME COLUMN " +
|
||||
"creation_data TO creation_date"
|
||||
)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = None
|
||||
|
||||
study = data['study']
|
||||
status = data['status']
|
||||
scenario = data["scenario"]
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, solver_name, solver_type, " +
|
||||
"study_revision, creation_date, " +
|
||||
"seed, number_of_run, " +
|
||||
"nb_timestamps, timestamps, " +
|
||||
"scenario " +
|
||||
"FROM results " +
|
||||
f"WHERE scenario = {scenario.id}"
|
||||
)
|
||||
|
||||
if table is None:
|
||||
yield
|
||||
|
||||
if len(table) > 1:
|
||||
logger.warning("Multiple results for this scenario")
|
||||
|
||||
for v in table:
|
||||
it = iter(v)
|
||||
|
||||
pid = next(it)
|
||||
solver_name = next(it)
|
||||
solver_type = next(it)
|
||||
revision = next(it)
|
||||
creation_date = next(it)
|
||||
seed = next(it)
|
||||
number_of_run = next(it)
|
||||
nb_timestamps = next(it)
|
||||
timestamps_bytes = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
new_results = cls(study=study)
|
||||
|
||||
new_results.set("solver_name", solver_name)
|
||||
new_results.set("solver_type", solver_type)
|
||||
new_results.set("study_revision", revision)
|
||||
new_results.set("creation_date", creation_date)
|
||||
if seed is not None:
|
||||
new_results.set("seed", seed)
|
||||
new_results.set("number_of_run", number_of_run)
|
||||
|
||||
data["result_pid"] = pid
|
||||
data["result"] = new_results
|
||||
|
||||
table_data = TableData._db_load(execute, data)
|
||||
new_results.set("table", table_data)
|
||||
|
||||
sf = ">" + ''.join(itertools.repeat("d", nb_timestamps))
|
||||
ts = struct.unpack(sf, timestamps_bytes)
|
||||
new_results.set("timestamps", ts)
|
||||
|
||||
data["timestamps"] = sorted(ts)
|
||||
new_results.compute_timestamp_index()
|
||||
|
||||
data["parent"] = new_results
|
||||
new_results._river = River._db_load(execute, data)
|
||||
|
||||
new_results.set(
|
||||
"additional_data",
|
||||
AdditionalData._db_load(execute, data)
|
||||
)
|
||||
|
||||
# Update global index
|
||||
i = 0
|
||||
for reach in new_results._river.reachs:
|
||||
nb = len(reach.profiles)
|
||||
reach.set_global_index(range(i, i + nb))
|
||||
i += nb
|
||||
|
||||
if "Z" in table_data:
|
||||
for ts in data["timestamps"]:
|
||||
new_results._river.update_water_limits(
|
||||
ts,
|
||||
table_data["Z"][new_results.get_timestamp_id(ts), :]
|
||||
)
|
||||
|
||||
yield (solver_type, new_results)
|
||||
|
||||
def _db_save_clear(self, execute, solver_type, data=None):
|
||||
old_pid = execute(
|
||||
"SELECT pamhyr_id FROM results " +
|
||||
f"WHERE scenario = {self._owner_scenario} " +
|
||||
f"AND solver_type = '{solver_type}'"
|
||||
)
|
||||
if len(old_pid) != 0:
|
||||
for pid in old_pid:
|
||||
pid = pid[0]
|
||||
|
||||
execute(
|
||||
"DELETE FROM results " +
|
||||
f"WHERE scenario = {self._owner_scenario} " +
|
||||
f"AND solver_type = '{solver_type}'"
|
||||
)
|
||||
execute(
|
||||
"DELETE FROM results_data " +
|
||||
f"WHERE scenario = {self._owner_scenario} " +
|
||||
f"AND result = {pid}"
|
||||
)
|
||||
execute(
|
||||
"DELETE FROM results_table_data " +
|
||||
f"WHERE scenario = {self._owner_scenario} " +
|
||||
f"AND result = {pid}"
|
||||
)
|
||||
execute(
|
||||
"DELETE FROM results_add_data " +
|
||||
f"WHERE scenario = {self._owner_scenario} " +
|
||||
f"AND result = {pid}"
|
||||
)
|
||||
|
||||
def _is_valid(self):
|
||||
return "timestamps" in self._meta_data
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if self._status.scenario.id != self._owner_scenario:
|
||||
return True
|
||||
|
||||
if not self._is_valid():
|
||||
return False
|
||||
|
||||
pid = self._pamhyr_id
|
||||
if self._solver is None:
|
||||
solver_name = self.get("solver_name")
|
||||
solver_type = self.get("solver_type")
|
||||
else:
|
||||
solver_name = self._solver._name
|
||||
solver_type = self._solver._type
|
||||
|
||||
self._db_save_clear(execute, solver_type, data=data)
|
||||
|
||||
ts = sorted(self.get("timestamps"))
|
||||
sf = ">" + ''.join(itertools.repeat("d", len(ts)))
|
||||
|
||||
if "seed" in self._meta_data:
|
||||
seed = int(self.get("seed"))
|
||||
number_of_run = int(self.get("number_of_run"))
|
||||
else:
|
||||
seed = None
|
||||
number_of_run = None
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"results (pamhyr_id, solver_name, solver_type, " +
|
||||
"study_revision, creation_date, " +
|
||||
"seed, number_of_run, " +
|
||||
"nb_timestamps, timestamps, " +
|
||||
"scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
self._pamhyr_id, solver_name, solver_type,
|
||||
self._status.version, self.get("creation_date"),
|
||||
seed, number_of_run,
|
||||
len(ts), struct.pack(sf, *ts),
|
||||
self._owner_scenario
|
||||
)
|
||||
|
||||
data["result"] = self._pamhyr_id
|
||||
self._river._db_save(execute, data)
|
||||
|
||||
tables = self.get("table")
|
||||
for legend in tables:
|
||||
tables[legend]._db_save(execute, data)
|
||||
|
||||
for add_data in self.get("additional_data"):
|
||||
add_data._db_save(execute, data)
|
||||
|
||||
return True
|
||||
|
||||
def new_table_data(self, legend, data):
|
||||
return TableData(
|
||||
study=self._study,
|
||||
legend=legend,
|
||||
data=data,
|
||||
owner_scenario=self._owner_scenario
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
# Results.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,12 +21,13 @@ import os
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Results.River.RiverAdisTS import River
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Results(object):
|
||||
class Results(SQLSubModel):
|
||||
def __init__(self, study=None, solver=None,
|
||||
repertory="", name="0", type_pol=None):
|
||||
self._study = study
|
||||
@@ -50,6 +51,9 @@ class Results(object):
|
||||
|
||||
self._phys_var_list = ["C", "G", "M", "D", "L", "N", "R"]
|
||||
|
||||
if solver is not None:
|
||||
self.set("solver_type", solver._type)
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
date = self._meta_data["creation_date"]
|
||||
@@ -87,3 +91,6 @@ class Results(object):
|
||||
self._repertory,
|
||||
qlog=None,
|
||||
)
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
return False
|
||||
@@ -1,5 +1,5 @@
|
||||
# River.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
|
||||
@@ -14,18 +14,34 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import types
|
||||
import struct
|
||||
import logging
|
||||
import itertools
|
||||
import numpy as np
|
||||
|
||||
from tools import flatten, timer
|
||||
from functools import reduce
|
||||
from datetime import datetime
|
||||
|
||||
from Model.Scenario import Scenario
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Profile(object):
|
||||
def __init__(self, profile, study):
|
||||
class Profile(SQLSubModel):
|
||||
def __init__(self, profile, study, parent):
|
||||
super(Profile, self).__init__(
|
||||
id=-1, status=study.status,
|
||||
owner_scenario=study.status.scenario.id
|
||||
)
|
||||
|
||||
self._parent = parent
|
||||
self._study = study
|
||||
self._profile = profile # Source profile in the study
|
||||
self._data = {} # Dict of dict {<ts>: {<key>: <value>, ...}, ...}
|
||||
self._global_index = -1
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
@@ -42,40 +58,244 @@ class Profile(object):
|
||||
def geometry(self):
|
||||
return self._profile
|
||||
|
||||
@property
|
||||
def global_index(self):
|
||||
return self._global_index
|
||||
|
||||
@global_index.setter
|
||||
def global_index(self, id):
|
||||
self._global_index = id
|
||||
|
||||
@timer
|
||||
def set(self, timestamp, key, data):
|
||||
if timestamp not in self._data:
|
||||
self._data[timestamp] = {}
|
||||
|
||||
self._data[timestamp][key] = data
|
||||
|
||||
@timer
|
||||
def get_ts(self, timestamp):
|
||||
return self._data[timestamp]
|
||||
|
||||
@timer
|
||||
def get_key(self, key):
|
||||
return list(
|
||||
map(lambda ts: self._data[ts][key], self._data)
|
||||
res = list(
|
||||
map(lambda ts: self._data[ts][key],
|
||||
sorted(self._data.keys()))
|
||||
)
|
||||
return res
|
||||
|
||||
@timer
|
||||
def get_ts_key(self, timestamp, key):
|
||||
if timestamp in self._data:
|
||||
if key in self._data[timestamp]:
|
||||
return self._data[timestamp][key]
|
||||
v = self._data[timestamp][key]
|
||||
|
||||
# If is a generator, compute value(s)
|
||||
if isinstance(v, types.GeneratorType):
|
||||
v = next(v)
|
||||
self._data[timestamp][key] = v
|
||||
|
||||
return v
|
||||
|
||||
return None
|
||||
|
||||
def has_sediment(self):
|
||||
def has_sediment_layers(self):
|
||||
return any(map(lambda ts: "sl" in self._data[ts], self._data))
|
||||
|
||||
def has_bedload(self):
|
||||
return any(map(lambda ts: "sl" in self._data[ts], self._data))
|
||||
|
||||
class Reach(object):
|
||||
def __init__(self, reach, study):
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE results_data{ext}(
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
result INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
reach INTEGER NOT NULL,
|
||||
section INTEGER NOT NULL,
|
||||
len_data INTEGER NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(result) REFERENCES results(pamhyr_id),
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
|
||||
FOREIGN KEY(section)
|
||||
REFERENCES geometry_profileXYZ(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, result, key, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
status = data['status']
|
||||
|
||||
parent = data['parent']
|
||||
study = data['study']
|
||||
reach = data['reach']
|
||||
profile = data['profile']
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
timestamps = data['timestamps']
|
||||
|
||||
new = cls(profile, study, parent)
|
||||
|
||||
values = execute(
|
||||
"SELECT pamhyr_id, result, key, " +
|
||||
"len_data, data, scenario " +
|
||||
"FROM results_data " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND result = {data['result_pid']} " +
|
||||
f"AND reach = {reach.pamhyr_id} " +
|
||||
f"AND section = {profile.pamhyr_id}"
|
||||
)
|
||||
|
||||
for v in values:
|
||||
it = iter(v)
|
||||
|
||||
pid = next(it)
|
||||
result = next(it)
|
||||
key = next(it)
|
||||
len_data = next(it)
|
||||
data = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
if key in ["Z", "Q", "V"]:
|
||||
sf = ">" + ''.join(itertools.repeat("f", len_data))
|
||||
len_values = len(values)
|
||||
values = struct.unpack(sf, data)
|
||||
elif key in ["sl"]:
|
||||
sf = ">" + ''.join(itertools.repeat("f", len_data))
|
||||
values = struct.unpack(sf, data)
|
||||
values = cls._db_load_data_sl_format(
|
||||
values, len_data, timestamps
|
||||
)
|
||||
|
||||
for timestamp, value in zip(timestamps, values):
|
||||
new.set(timestamp, key, value)
|
||||
if key == "Z":
|
||||
new.update_water_limits(timestamp, value)
|
||||
|
||||
return new
|
||||
|
||||
@classmethod
|
||||
def _db_load_data_sl_format(cls, values, len_data, timestamps):
|
||||
tuple_size = 3
|
||||
|
||||
# HACK: Transforme list of value to list of n-tuple
|
||||
# sediment results: (h, d50, sigma)
|
||||
tmp_values = [[]]
|
||||
for value in values:
|
||||
t = tmp_values[-1]
|
||||
if len(t) < tuple_size:
|
||||
t.append(value)
|
||||
else:
|
||||
tmp_values.append([value])
|
||||
|
||||
tmp_values = list(map(tuple, tmp_values))
|
||||
|
||||
# HACK: Transforme list of n-tuple to list of n-list
|
||||
# of n-tuple, where n is the number of layer.
|
||||
nb_layer = len_data / len(timestamps) / tuple_size
|
||||
values = [[]]
|
||||
for value in tmp_values:
|
||||
t = values[-1]
|
||||
if len(t) < nb_layer:
|
||||
t.append(value)
|
||||
else:
|
||||
values.append([value])
|
||||
|
||||
return list(map(lambda x: [x], values))
|
||||
|
||||
def update_water_limits(self, timestamp, z):
|
||||
limits = self.geometry.get_water_limits(z)
|
||||
self.set(
|
||||
timestamp, "water_limits", limits
|
||||
)
|
||||
|
||||
def get_keys(self):
|
||||
return reduce(
|
||||
lambda acc, ts: acc.union(self._data[ts].keys()),
|
||||
self._data.keys(), set()
|
||||
)
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
pid = self._pamhyr_id
|
||||
result = data["result"]
|
||||
|
||||
keys = self.get_keys()
|
||||
logger.debug(f"{keys}...")
|
||||
for key in keys:
|
||||
values = self.get_key(key)
|
||||
|
||||
if key in ["Z", "Q", "V"]:
|
||||
values = list(map(float, values))
|
||||
sf = ">" + ''.join(itertools.repeat("f", len(values)))
|
||||
len_values = len(values)
|
||||
elif key == "sl":
|
||||
# HACK: Some dirty code to transforme list of list of
|
||||
# tuple to list of values and ensure the values is
|
||||
# float type...
|
||||
values = flatten(
|
||||
flatten(
|
||||
map(lambda v: list(
|
||||
map(lambda t: list(
|
||||
map(float, t)), v[0])),
|
||||
values)))
|
||||
len_values = len(values)
|
||||
sf = ">" + ''.join(itertools.repeat("f", len_values))
|
||||
else:
|
||||
continue
|
||||
|
||||
data_bytes = struct.pack(sf, *values)
|
||||
|
||||
execute(
|
||||
"INSERT INTO " +
|
||||
"results_data (pamhyr_id, result, " +
|
||||
"reach, section, " +
|
||||
"key, len_data, data, " +
|
||||
"scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
pid, result,
|
||||
data["reach"].pamhyr_id,
|
||||
self._profile.pamhyr_id,
|
||||
key, len_values, data_bytes,
|
||||
self._owner_scenario
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Reach(SQLSubModel):
|
||||
_sub_classes = [Profile]
|
||||
|
||||
def __init__(self, reach, study, parent, with_init=True, profiles=None):
|
||||
super(Reach, self).__init__(
|
||||
id=-1, status=study.status,
|
||||
owner_scenario=study.status.scenario.id
|
||||
)
|
||||
|
||||
self._parent = parent
|
||||
self._study = study
|
||||
self._reach = reach # Source reach in the study
|
||||
self._profiles = list(
|
||||
map(
|
||||
lambda p: Profile(p, self._study),
|
||||
reach.profiles
|
||||
self._profiles = []
|
||||
if with_init:
|
||||
if profiles is None:
|
||||
profiles = reach.profiles
|
||||
self._profiles = list(
|
||||
map(
|
||||
lambda p: Profile(p, self._study, self._parent),
|
||||
profiles
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self._profile_mask = list(
|
||||
map(
|
||||
@@ -83,6 +303,10 @@ class Reach(object):
|
||||
)
|
||||
)
|
||||
|
||||
self._global_index = (-1, -1)
|
||||
|
||||
self._buffers = {}
|
||||
|
||||
def __len__(self):
|
||||
return len(self._profiles)
|
||||
|
||||
@@ -102,22 +326,97 @@ class Reach(object):
|
||||
def profile_mask(self):
|
||||
return self._profile_mask
|
||||
|
||||
@property
|
||||
def buffers(self):
|
||||
return self._buffers
|
||||
|
||||
def profile(self, id):
|
||||
return self._profiles[id]
|
||||
|
||||
def set_global_index(self, indexs):
|
||||
self._global_index = (indexs[0], indexs[-1])
|
||||
|
||||
for profile, ind in zip(self._profiles, indexs):
|
||||
profile.global_index = ind
|
||||
|
||||
@property
|
||||
def global_index(self):
|
||||
return self._global_index
|
||||
|
||||
@timer
|
||||
def set(self, profile_id, timestamp, key, data):
|
||||
self._profiles[profile_id].set(timestamp, key, data)
|
||||
|
||||
def has_sediment(self):
|
||||
return any(map(lambda profile: profile.has_sediment(), self._profiles))
|
||||
return any(map(lambda profile: profile.has_sediment_layers(),
|
||||
self._profiles))
|
||||
|
||||
def has_bedload(self):
|
||||
return any(map(lambda profile: profile.has_bedload(), self._profiles))
|
||||
|
||||
def bufferize(self, timestamps, key):
|
||||
self._buffers[key] = np.zeros((len(timestamps), len(self)))
|
||||
for i, p in enumerate(self._profiles):
|
||||
self._buffers[key][:, i] = p.get_key(key)
|
||||
|
||||
def update_water_limits(self, timestamp, z_data):
|
||||
for profile, z in zip(self._profiles, z_data):
|
||||
profile.update_water_limits(timestamp, z)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
reach = data["reach"]
|
||||
|
||||
new_reach = cls(
|
||||
data["reach"], data["study"], data["parent"],
|
||||
with_init=False
|
||||
)
|
||||
|
||||
for i, profile in enumerate(reach.profiles):
|
||||
data["profile"] = profile
|
||||
result_profile = Profile._db_load(execute, data)
|
||||
if len(result_profile) > 0:
|
||||
new_reach._profiles.append(result_profile)
|
||||
|
||||
new_reach._profile_mask = [
|
||||
profile.name[0:8] != 'interpol'
|
||||
for profile in new_reach._profiles
|
||||
]
|
||||
|
||||
return new_reach
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
logger.debug("Save reach...")
|
||||
for profile in self._profiles:
|
||||
data["profile"] = profile.geometry.pamhyr_id
|
||||
profile._db_save(execute, data)
|
||||
|
||||
|
||||
class River(object):
|
||||
def __init__(self, study):
|
||||
class River(SQLSubModel):
|
||||
_sub_classes = [Reach]
|
||||
|
||||
def __init__(self, study, parent, init=True):
|
||||
super(River, self).__init__(
|
||||
id=-1, status=study.status,
|
||||
owner_scenario=study.status.scenario.id
|
||||
)
|
||||
|
||||
self._study = study
|
||||
self._parent = parent
|
||||
|
||||
# Dict with timestamps as key
|
||||
self._reachs = []
|
||||
if init:
|
||||
for i, _ in enumerate(self._study.river.enable_edges()):
|
||||
self.add(i)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._reachs)
|
||||
@@ -132,10 +431,13 @@ class River(object):
|
||||
def has_reach(self, id):
|
||||
return 0 <= id < len(self._reachs)
|
||||
|
||||
def add(self, reach_id):
|
||||
def add(self, reach_id, profiles=None):
|
||||
reachs = self._study.river.enable_edges()
|
||||
|
||||
new = Reach(reachs[reach_id].reach, self._study)
|
||||
new = Reach(
|
||||
reachs[reach_id].reach, self._study, self._parent,
|
||||
profiles=profiles
|
||||
)
|
||||
|
||||
self._reachs.append(new)
|
||||
return new
|
||||
@@ -147,3 +449,41 @@ class River(object):
|
||||
self._reachs
|
||||
)
|
||||
)
|
||||
|
||||
def update_water_limits(self, timestamp, data):
|
||||
for reach in self._reachs:
|
||||
ind = reach.global_index
|
||||
|
||||
reach.update_water_limits(
|
||||
timestamp,
|
||||
data[ind[0]:ind[1] + 1]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
study = data["study"]
|
||||
parent = data["parent"]
|
||||
|
||||
new_river = cls(study, parent, init=False)
|
||||
|
||||
for reach in study.river.reachs():
|
||||
data["reach"] = reach.reach
|
||||
result_reach = Reach._db_load(execute, data)
|
||||
if len(result_reach) > 0:
|
||||
new_river._reachs.append(result_reach)
|
||||
|
||||
return new_river
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
logger.debug("Save river...")
|
||||
for reach in self._reachs:
|
||||
data["reach"] = reach.geometry
|
||||
reach._db_save(execute, data)
|
||||
@@ -1,5 +1,5 @@
|
||||
# River.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
# Copyright (C) 2023-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,13 +64,15 @@ class Profile(object):
|
||||
|
||||
|
||||
class Reach(object):
|
||||
def __init__(self, reach, study):
|
||||
def __init__(self, reach, study, profiles=None):
|
||||
self._study = study
|
||||
self._reach = reach # Source reach in the study
|
||||
if profiles is None:
|
||||
profiles = reach.profiles
|
||||
self._profiles = list(
|
||||
map(
|
||||
lambda p: Profile(p, self._study),
|
||||
reach.profiles
|
||||
profiles
|
||||
)
|
||||
)
|
||||
|
||||
@@ -89,6 +91,10 @@ class Reach(object):
|
||||
def profiles(self):
|
||||
return self._profiles.copy()
|
||||
|
||||
def get_rk(self):
|
||||
"""Return the PK values for profiles present in the results."""
|
||||
return [profile.rk for profile in self._profiles]
|
||||
|
||||
def profile(self, id):
|
||||
return self._profiles[id]
|
||||
|
||||
@@ -119,10 +125,10 @@ class River(object):
|
||||
def has_reach(self, id):
|
||||
return 0 <= id < len(self._reachs)
|
||||
|
||||
def add(self, reach_id):
|
||||
def add(self, reach_id, profiles=None):
|
||||
reachs = self._study.river.enable_edges()
|
||||
|
||||
new = Reach(reachs[reach_id].reach, self._study)
|
||||
new = Reach(reachs[reach_id].reach, self._study, profiles=profiles)
|
||||
|
||||
self._reachs.append(new)
|
||||
return new
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# River.py -- Pamhyr river model
|
||||
# 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,9 +16,13 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import types
|
||||
import logging
|
||||
|
||||
from tools import flatten, logger_exception
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
from Model.Network.Node import Node
|
||||
from Model.Network.Edge import Edge
|
||||
@@ -45,7 +49,6 @@ from Model.REPLine.REPLineList import REPLineList
|
||||
|
||||
from Solver.Solvers import solver_type_list
|
||||
|
||||
from Model.OutputRKAdists.OutputRKListAdists import OutputRKAdistsList
|
||||
from Model.Pollutants.PollutantsList import PollutantsList
|
||||
from Model.InitialConditionsAdisTS.InitialConditionsAdisTSList \
|
||||
import InitialConditionsAdisTSList
|
||||
@@ -56,63 +59,163 @@ from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList \
|
||||
from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
|
||||
from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
|
||||
|
||||
from Model.InitialConditionsTemperature.InitialConditionsTemperatureList \
|
||||
import InitialConditionsTemperatureList
|
||||
|
||||
class RiverNode(Node, SQLSubModel):
|
||||
from Model.BoundaryConditionsTemperature.BoundaryConditionsTemperatureList \
|
||||
import BoundaryConditionsTemperatureList
|
||||
|
||||
from Model.WeatherParameters.WeatherParametersList \
|
||||
import WeatherParametersList
|
||||
|
||||
from Model.GeoTIFF.GeoTIFFList import GeoTIFFList
|
||||
from Model.Ensembles.EnsembleList import EnsembleList
|
||||
from Model.Results.Results import Results
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class RiverNode(Node):
|
||||
_sub_classes = []
|
||||
|
||||
def __init__(self, id: str, name: str,
|
||||
x: float, y: float,
|
||||
status=None):
|
||||
def __init__(self, id: int = -1, name: str = "",
|
||||
x: float = 0.0, y: float = 0.0,
|
||||
status=None, owner_scenario=-1):
|
||||
super(RiverNode, self).__init__(
|
||||
id, name,
|
||||
x, y,
|
||||
status=status
|
||||
id=id, name=name, x=x, y=y,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._locker = None
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE river_node(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE river_node{ext}(
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
x REAL NOT NULL,
|
||||
y REAL NOT NULL
|
||||
y REAL NOT NULL,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
cls._create_submodel(execute)
|
||||
return True
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return True
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
cls._db_update_to_0_2_0(execute, data=data)
|
||||
|
||||
if major == "0" and minor == "1":
|
||||
if int(release) < 2:
|
||||
execute(
|
||||
"ALTER TABLE river_node " +
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0(cls, execute, data=None):
|
||||
table = "river_node"
|
||||
|
||||
cls.update_db_add_pamhyr_id(execute, table, data)
|
||||
Scenario.update_db_add_scenario(execute, table)
|
||||
|
||||
cls._db_create(execute, ext="_tmp")
|
||||
|
||||
# Copy table
|
||||
execute(
|
||||
f"INSERT INTO {table}_tmp " +
|
||||
f"(pamhyr_id, name, x, y, scenario) " +
|
||||
"SELECT pamhyr_id, name, x, y, scenario " +
|
||||
f"FROM {table}"
|
||||
)
|
||||
|
||||
execute(f"DROP TABLE {table}")
|
||||
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
nodes = []
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if scenario is None:
|
||||
return nodes
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, name, x, y, scenario " +
|
||||
"FROM river_node " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))}) "
|
||||
)
|
||||
|
||||
table = execute("SELECT id, name, x, y FROM river_node")
|
||||
for row in table:
|
||||
# Update id counter
|
||||
cls._id_cnt = max(cls._id_cnt, row[0])
|
||||
# Create new node
|
||||
nodes.append(cls(*row, status=data["status"]))
|
||||
it = iter(row)
|
||||
|
||||
pid = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
x = next(it)
|
||||
y = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
node = cls(
|
||||
id=pid,
|
||||
name=name, x=x, y=y,
|
||||
status=data["status"],
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
if deleted:
|
||||
node.set_as_deleted()
|
||||
|
||||
loaded.add(pid)
|
||||
nodes.append(node)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
nodes += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return nodes
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
sql = (
|
||||
"INSERT OR REPLACE INTO river_node(id, name, x, y) VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self.name)}', " +
|
||||
f"{self.x}, {self.y}" +
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(
|
||||
"INSERT OR REPLACE INTO river_node(" +
|
||||
"pamhyr_id, deleted, name, x, y, scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self._pamhyr_id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self.name)}', " +
|
||||
f"{self.x}, {self.y}, {self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
return True
|
||||
|
||||
def _data_traversal(self,
|
||||
predicate=lambda obj, data: True,
|
||||
modifier=lambda obj, data: None,
|
||||
data={}):
|
||||
if predicate(self, data):
|
||||
modifier(self, data)
|
||||
|
||||
def is_deleted(self):
|
||||
return self._deleted
|
||||
|
||||
@property
|
||||
def locker(self):
|
||||
return self._locker
|
||||
@@ -122,100 +225,207 @@ class RiverNode(Node, SQLSubModel):
|
||||
self._locker = locker
|
||||
|
||||
|
||||
class RiverReach(Edge, SQLSubModel):
|
||||
class RiverReach(Edge):
|
||||
_sub_classes = [
|
||||
Reach,
|
||||
FrictionList,
|
||||
]
|
||||
|
||||
def __init__(self, id: str, name: str,
|
||||
def __init__(self, id: str = -1, name: str = "",
|
||||
node1: RiverNode = None,
|
||||
node2: RiverNode = None,
|
||||
status=None):
|
||||
status=None, owner_scenario=-1):
|
||||
super(RiverReach, self).__init__(
|
||||
id, name,
|
||||
node1, node2,
|
||||
status=status
|
||||
id=id, name=name,
|
||||
node1=node1, node2=node2,
|
||||
status=status,
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
|
||||
self._reach = Reach(status=self._status, parent=self)
|
||||
self._frictions = FrictionList(status=self._status)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE river_reach(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
CREATE TABLE river_reach{ext} (
|
||||
{cls.create_db_add_pamhyr_id()},
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
name TEXT NOT NULL,
|
||||
enable BOOLEAN NOT NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
node1 INTEGER,
|
||||
node2 INTEGER,
|
||||
FOREIGN KEY(node1) REFERENCES river_node(id),
|
||||
FOREIGN KEY(node2) REFERENCES river_node(id)
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(node1) REFERENCES river_node(pamhyr_id),
|
||||
FOREIGN KEY(node2) REFERENCES river_node(pamhyr_id),
|
||||
PRIMARY KEY(pamhyr_id, scenario)
|
||||
)
|
||||
""")
|
||||
|
||||
cls._create_submodel(execute)
|
||||
return True
|
||||
if ext == "_tmp":
|
||||
return True
|
||||
|
||||
return cls._create_submodel(execute)
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
return cls._update_submodel(execute, version)
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == minor == "0":
|
||||
cls._db_update_to_0_2_0(execute, data)
|
||||
|
||||
if major == "0" and minor == "1":
|
||||
if int(release) < 2:
|
||||
execute(
|
||||
"ALTER TABLE river_reach " +
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0(cls, execute, data=None):
|
||||
table = "river_reach"
|
||||
nodes = data['id2pid']['river_node']
|
||||
|
||||
cls.update_db_add_pamhyr_id(execute, table, data)
|
||||
Scenario.update_db_add_scenario(execute, table)
|
||||
|
||||
cls._db_create(execute, ext="_tmp")
|
||||
|
||||
# Copy table
|
||||
execute(
|
||||
f"INSERT INTO {table}_tmp " +
|
||||
f"(pamhyr_id, name, enabled, node1, node2, scenario) " +
|
||||
"SELECT pamhyr_id, name, enable, node1, node2, scenario " +
|
||||
f"FROM {table}"
|
||||
)
|
||||
|
||||
execute(f"DROP TABLE {table}")
|
||||
execute(f"ALTER TABLE {table}_tmp RENAME TO {table}")
|
||||
|
||||
cls._db_update_to_0_2_0_set_node_pid(execute, table, nodes)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0_set_node_pid(cls, execute, table, nodes):
|
||||
bcs = execute(
|
||||
f"SELECT pamhyr_id, node1, node2 FROM {table}"
|
||||
)
|
||||
|
||||
for row in bcs:
|
||||
it = iter(row)
|
||||
|
||||
pid = next(it)
|
||||
node1_id = next(it)
|
||||
node2_id = next(it)
|
||||
|
||||
execute(
|
||||
f"UPDATE {table} " +
|
||||
f"SET node1 = {nodes[node1_id]}, " +
|
||||
f"node2 = {nodes[node2_id]} " +
|
||||
f"WHERE pamhyr_id = {pid}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
reachs = []
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
if scenario is None:
|
||||
return reachs
|
||||
|
||||
table = execute(
|
||||
"SELECT id, name, enable, node1, node2 FROM river_reach"
|
||||
"SELECT pamhyr_id, deleted, name, enabled, " +
|
||||
"node1, node2, scenario " +
|
||||
"FROM river_reach " +
|
||||
f"WHERE scenario = {scenario.id} " +
|
||||
f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
# Update id counter
|
||||
cls._id_cnt = max(cls._id_cnt, row[0])
|
||||
# Create new reach
|
||||
id = row[0]
|
||||
name = row[1]
|
||||
enable = (row[2] == 1)
|
||||
# Get nodes corresponding to db foreign key id
|
||||
node1 = next(filter(lambda n: n.id == row[3], data["nodes"]))
|
||||
node2 = next(filter(lambda n: n.id == row[4], data["nodes"]))
|
||||
it = iter(row)
|
||||
|
||||
new = cls(id, name, node1, node2, status=data["status"])
|
||||
new.enable(enable=enable)
|
||||
pid = next(it)
|
||||
deleted = (next(it) == 1)
|
||||
name = next(it)
|
||||
enabled = (next(it) == 1)
|
||||
node1_pid = next(it)
|
||||
node2_pid = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
# Get nodes corresponding to db foreign key id
|
||||
node1 = next(
|
||||
filter(
|
||||
lambda n: n.pamhyr_id == node1_pid, data["nodes"]
|
||||
)
|
||||
)
|
||||
node2 = next(
|
||||
filter(
|
||||
lambda n: n.pamhyr_id == node2_pid, data["nodes"]
|
||||
)
|
||||
)
|
||||
|
||||
new = cls(
|
||||
id=pid, name=name, node1=node1, node2=node2,
|
||||
status=data["status"],
|
||||
owner_scenario=owner_scenario
|
||||
)
|
||||
new.enable(enable=enabled)
|
||||
if deleted:
|
||||
new.set_as_deleted()
|
||||
|
||||
data["reach"] = new
|
||||
|
||||
new._reach = Reach._db_load(execute, data)
|
||||
new._frictions = FrictionList._db_load(execute, data)
|
||||
|
||||
loaded.add(pid)
|
||||
reachs.append(new)
|
||||
|
||||
data["scenario"] = scenario.parent
|
||||
reachs += cls._db_load(execute, data)
|
||||
data["scenario"] = scenario
|
||||
|
||||
return reachs
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
sql = (
|
||||
if not self.must_be_saved():
|
||||
return True
|
||||
|
||||
execute(
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"river_reach(id, name, enable, node1, node2) " +
|
||||
"river_reach(" +
|
||||
"pamhyr_id, deleted, name, enabled, " +
|
||||
"node1, node2, scenario" +
|
||||
") " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, '{self._db_format(self._name)}', " +
|
||||
f"{self._db_format(self.is_enable())},"
|
||||
f"{self.node1.id}, {self.node2.id}" +
|
||||
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"{self._db_format(self.is_enable())}," +
|
||||
f"{self.node1.pamhyr_id}, {self.node2.pamhyr_id}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
execute(sql)
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
data["reach"] = self
|
||||
|
||||
objs = [self._reach, self._frictions]
|
||||
return self._save_submodel(execute, objs, data)
|
||||
|
||||
def _data_traversal(self,
|
||||
predicate=lambda obj, data: True,
|
||||
modifier=lambda obj, data: None,
|
||||
data={}):
|
||||
if predicate(self, data):
|
||||
modifier(self, data)
|
||||
|
||||
self._reach._data_traversal(predicate, modifier, data)
|
||||
self._frictions._data_traversal(predicate, modifier, data)
|
||||
|
||||
def is_deleted(self):
|
||||
return self._deleted
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
@@ -224,32 +434,68 @@ class RiverReach(Edge, SQLSubModel):
|
||||
def frictions(self):
|
||||
return self._frictions
|
||||
|
||||
def _split(self, profile, new_node):
|
||||
name = self.name
|
||||
node1 = self.node1
|
||||
node2 = self.node2
|
||||
|
||||
class River(Graph, SQLSubModel):
|
||||
new_reach1 = RiverReach(
|
||||
id=-1, name=name + "(part 1)",
|
||||
node1=node1, node2=new_node,
|
||||
status=self._status,
|
||||
)
|
||||
|
||||
new_reach2 = RiverReach(
|
||||
id=-1, name=name + "(part 2)",
|
||||
node1=new_node, node2=node2,
|
||||
status=self._status,
|
||||
)
|
||||
|
||||
nr1, nr2 = self._reach._split(profile, new_reach1, new_reach2)
|
||||
|
||||
new_reach1._reach = nr1
|
||||
new_reach1._frictions = self._frictions._splited_for(new_reach1)
|
||||
|
||||
new_reach2._reach = nr2
|
||||
new_reach2._frictions = self._frictions._splited_for(new_reach2)
|
||||
|
||||
self.disable()
|
||||
|
||||
return new_reach1, new_reach2
|
||||
|
||||
|
||||
class River(Graph):
|
||||
_sub_classes = [
|
||||
StricklersList,
|
||||
SedimentLayerList,
|
||||
RiverNode,
|
||||
RiverReach,
|
||||
BoundaryConditionList,
|
||||
LateralContributionList,
|
||||
InitialConditionsDict,
|
||||
StricklersList,
|
||||
SolverParametersList,
|
||||
SedimentLayerList,
|
||||
ReservoirList,
|
||||
HydraulicStructureList,
|
||||
AddFileList,
|
||||
REPLineList,
|
||||
OutputRKAdistsList,
|
||||
PollutantsList,
|
||||
InitialConditionsAdisTSList,
|
||||
BoundaryConditionsAdisTSList,
|
||||
LateralContributionsAdisTSList,
|
||||
D90AdisTSList,
|
||||
DIFAdisTSList,
|
||||
InitialConditionsTemperatureList,
|
||||
BoundaryConditionsTemperatureList,
|
||||
WeatherParametersList,
|
||||
GeoTIFFList,
|
||||
EnsembleList,
|
||||
Results
|
||||
]
|
||||
|
||||
def __init__(self, status=None):
|
||||
super(River, self).__init__(status=status)
|
||||
super(River, self).__init__(
|
||||
status=status
|
||||
)
|
||||
|
||||
# Replace Node and Edge ctor by custom ctor
|
||||
self._node_ctor = RiverNode
|
||||
@@ -269,7 +515,6 @@ class River(Graph, SQLSubModel):
|
||||
)
|
||||
self._additional_files = AddFileList(status=self._status)
|
||||
self._rep_lines = REPLineList(status=self._status)
|
||||
self._Output_rk_adists = OutputRKAdistsList(status=self._status)
|
||||
self._Pollutants = PollutantsList(status=self._status)
|
||||
self._InitialConditionsAdisTS = InitialConditionsAdisTSList(
|
||||
status=self._status)
|
||||
@@ -279,6 +524,20 @@ class River(Graph, SQLSubModel):
|
||||
status=self._status)
|
||||
self._D90AdisTS = D90AdisTSList(status=self._status)
|
||||
self._DIFAdisTS = DIFAdisTSList(status=self._status)
|
||||
self._InitialConditionsTemperature = (
|
||||
InitialConditionsTemperatureList(status=self._status)
|
||||
)
|
||||
self._BoundaryConditionsTemperature = (
|
||||
BoundaryConditionsTemperatureList(status=self._status)
|
||||
)
|
||||
self._WeatherParameters = (WeatherParametersList(status=self._status))
|
||||
|
||||
self._geotiff = GeoTIFFList(status=self._status)
|
||||
|
||||
# Scenario ensemble
|
||||
self._ensembles = EnsembleList(status=self._status)
|
||||
|
||||
self._results = {}
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
@@ -286,13 +545,12 @@ class River(Graph, SQLSubModel):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version):
|
||||
cls._update_submodel(execute, version)
|
||||
return True
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(data["status"])
|
||||
new = cls(status=data["status"])
|
||||
|
||||
# Stricklers (Stricklers is load in first because it's needed
|
||||
# for reachs)
|
||||
@@ -300,62 +558,77 @@ class River(Graph, SQLSubModel):
|
||||
execute, data
|
||||
)
|
||||
data["stricklers"] = new._stricklers
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
# Initial conditions
|
||||
new._sediment_layers = SedimentLayerList._db_load(
|
||||
execute, data
|
||||
)
|
||||
data["sediment_layers_list"] = new._sediment_layers
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
# Network
|
||||
new._nodes = RiverNode._db_load(
|
||||
execute, data
|
||||
)
|
||||
data["nodes"] = new.nodes()
|
||||
data["nodes"] = new._nodes
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
new._edges = RiverReach._db_load(
|
||||
execute, data
|
||||
)
|
||||
data["edges"] = new.edges()
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
# Boundary Condition
|
||||
new._boundary_condition = BoundaryConditionList._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
# Lateral Contribution
|
||||
new._lateral_contribution = LateralContributionList._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
# Initial conditions
|
||||
new._initial_conditions = InitialConditionsDict._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
# Reservoir
|
||||
new._reservoir = ReservoirList._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
# Hydraulic Structures
|
||||
new._hydraulic_structures = HydraulicStructureList._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
# Parameters
|
||||
new._parameters = SolverParametersList._db_load(
|
||||
execute, data
|
||||
)
|
||||
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
# Additional Files
|
||||
new._additional_files = AddFileList._db_load(
|
||||
execute, data
|
||||
)
|
||||
new._rep_lines = REPLineList._db_load(execute, data)
|
||||
|
||||
new._Output_rk_adists = OutputRKAdistsList._db_load(
|
||||
execute, data
|
||||
)
|
||||
data['loaded_pid'] = set()
|
||||
|
||||
new._Pollutants = PollutantsList._db_load(execute, data)
|
||||
|
||||
@@ -372,8 +645,26 @@ class River(Graph, SQLSubModel):
|
||||
|
||||
new._DIFAdisTS = DIFAdisTSList._db_load(execute, data)
|
||||
|
||||
new._InitialConditionsTemperature = \
|
||||
InitialConditionsTemperatureList._db_load(execute, data)
|
||||
|
||||
new._BoundaryConditionsTemperature = \
|
||||
BoundaryConditionsTemperatureList._db_load(execute, data)
|
||||
|
||||
new._WeatherParameters = WeatherParametersList._db_load(execute, data)
|
||||
|
||||
new._geotiff = GeoTIFFList._db_load(execute, data)
|
||||
|
||||
new._ensembles = EnsembleList._db_load(execute, data)
|
||||
|
||||
return new
|
||||
|
||||
def _db_load_results(self, execute, data=None):
|
||||
results_lst = Results._db_load(execute, data)
|
||||
|
||||
for solv_type, results in results_lst:
|
||||
self._results[solv_type] = results
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
self._db_save_delete_artefact(execute, data)
|
||||
|
||||
@@ -391,7 +682,6 @@ class River(Graph, SQLSubModel):
|
||||
for solver in self._parameters:
|
||||
objs.append(self._parameters[solver])
|
||||
|
||||
objs.append(self._Output_rk_adists)
|
||||
objs.append(self._Pollutants)
|
||||
objs.append(self._InitialConditionsAdisTS)
|
||||
objs.append(self._BoundaryConditionsAdisTS)
|
||||
@@ -399,6 +689,17 @@ class River(Graph, SQLSubModel):
|
||||
objs.append(self._D90AdisTS)
|
||||
objs.append(self._DIFAdisTS)
|
||||
|
||||
objs.append(self._InitialConditionsTemperature)
|
||||
objs.append(self._BoundaryConditionsTemperature)
|
||||
objs.append(self._WeatherParameters)
|
||||
|
||||
objs.append(self._geotiff)
|
||||
|
||||
objs.append(self._ensembles)
|
||||
|
||||
for solv_type in self.results:
|
||||
objs.append(self.results[solv_type])
|
||||
|
||||
self._save_submodel(execute, objs, data)
|
||||
return True
|
||||
|
||||
@@ -422,15 +723,66 @@ class River(Graph, SQLSubModel):
|
||||
execute(
|
||||
f"DELETE FROM {table} " +
|
||||
"WHERE " +
|
||||
f"scenario = {self._status.scenario_id} AND (" +
|
||||
" OR ".join(
|
||||
map(
|
||||
lambda n: f"( id <> {n.id} )", els
|
||||
lambda n: f"( pamhyr_id <> {n.pamhyr_id} )",
|
||||
els
|
||||
)
|
||||
)
|
||||
) + ")"
|
||||
)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
|
||||
def _data_traversal(self,
|
||||
predicate=lambda obj, data: True,
|
||||
modifier=lambda obj, data: None,
|
||||
data={}):
|
||||
"""Traversal data and execute modifier fonction if predicate
|
||||
true
|
||||
|
||||
Args:
|
||||
predicate: Function predicate, take current obj and data as input
|
||||
modifier: Function modifier, take current obj and data as input
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
|
||||
"""
|
||||
if predicate(self, data):
|
||||
modifier(self, data)
|
||||
|
||||
for obj in self._get_objs_list():
|
||||
obj._data_traversal(predicate, modifier, data)
|
||||
|
||||
def _get_objs_list(self):
|
||||
objs = (self._nodes + self._edges)
|
||||
objs += [
|
||||
self._boundary_condition,
|
||||
self._initial_conditions,
|
||||
self._lateral_contribution,
|
||||
self._sediment_layers,
|
||||
self._stricklers,
|
||||
self._reservoir,
|
||||
self._hydraulic_structures,
|
||||
self._additional_files,
|
||||
self._rep_lines,
|
||||
self._Pollutants,
|
||||
self._InitialConditionsAdisTS,
|
||||
self._BoundaryConditionsAdisTS,
|
||||
self._LateralContributionsAdisTS,
|
||||
self._D90AdisTS, self._DIFAdisTS,
|
||||
self._InitialConditionsTemperature,
|
||||
self._BoundaryConditionsTemperature,
|
||||
self._WeatherParameters,
|
||||
self._geotiff, self._ensembles,
|
||||
]
|
||||
|
||||
for solver in self._parameters:
|
||||
objs.append(self._parameters[solver])
|
||||
|
||||
return objs
|
||||
|
||||
def init_default(self):
|
||||
self.init_default_network()
|
||||
self.init_default_sediment()
|
||||
@@ -468,6 +820,9 @@ execution...
|
||||
|
||||
Last export at: @date."""
|
||||
|
||||
def reachs(self):
|
||||
return self.edges()
|
||||
|
||||
@property
|
||||
def boundary_condition(self):
|
||||
return self._boundary_condition
|
||||
@@ -513,6 +868,10 @@ Last export at: @date."""
|
||||
def additional_files(self):
|
||||
return self._additional_files
|
||||
|
||||
@property
|
||||
def geotiff(self):
|
||||
return self._geotiff
|
||||
|
||||
@property
|
||||
def rep_lines(self):
|
||||
return self._rep_lines
|
||||
@@ -521,10 +880,6 @@ Last export at: @date."""
|
||||
def parameters(self):
|
||||
return self._parameters
|
||||
|
||||
@property
|
||||
def Output_rk_adists(self):
|
||||
return self._Output_rk_adists
|
||||
|
||||
@property
|
||||
def Pollutants(self):
|
||||
return self._Pollutants
|
||||
@@ -549,6 +904,67 @@ Last export at: @date."""
|
||||
def dif_adists(self):
|
||||
return self._DIFAdisTS
|
||||
|
||||
@property
|
||||
def ic_temperature(self):
|
||||
return self._InitialConditionsTemperature
|
||||
|
||||
@property
|
||||
def boundary_conditions_temperature(self):
|
||||
return self._BoundaryConditionsTemperature
|
||||
|
||||
@property
|
||||
def weather_parameters(self):
|
||||
return self._WeatherParameters
|
||||
|
||||
def profile_dependencies(self, profile):
|
||||
"""Return model data which directly reference *profile*.
|
||||
|
||||
PK-based intervals are deliberately not included: only explicit
|
||||
references to the profile object prevent it from being disabled.
|
||||
"""
|
||||
dependencies = []
|
||||
|
||||
def add(kind, item, association):
|
||||
dependencies.append({
|
||||
"kind": kind,
|
||||
"item": item,
|
||||
"association": association,
|
||||
})
|
||||
|
||||
for structure in self._hydraulic_structures.lst:
|
||||
if structure.input_section is profile:
|
||||
add("hydraulic_structure", structure, "")
|
||||
|
||||
for tab in self._lateral_contribution._tabs_list:
|
||||
for contribution in self._lateral_contribution.get_tab(tab):
|
||||
if contribution.begin_section is profile:
|
||||
add("lateral_contribution", contribution, "begin_section")
|
||||
if contribution.end_section is profile:
|
||||
add("lateral_contribution", contribution, "end_section")
|
||||
|
||||
for parameter in self._WeatherParameters.lst:
|
||||
if parameter.begin_section is profile:
|
||||
add("weather_parameter", parameter, "begin_section")
|
||||
if parameter.end_section is profile:
|
||||
add("weather_parameter", parameter, "end_section")
|
||||
|
||||
reach_id = profile.reach.pamhyr_id
|
||||
for initial_condition in self._InitialConditionsTemperature.lst:
|
||||
for specification in initial_condition.data:
|
||||
same_reach = specification.reach == reach_id
|
||||
same_rk = (
|
||||
specification.start_rk == profile.rk or
|
||||
specification.end_rk == profile.rk
|
||||
)
|
||||
if same_reach and same_rk:
|
||||
add(
|
||||
"initial_condition_temperature",
|
||||
specification,
|
||||
"cross_section_pk",
|
||||
)
|
||||
|
||||
return dependencies
|
||||
|
||||
def get_params(self, solver):
|
||||
if solver in self._parameters:
|
||||
return self._parameters[solver]
|
||||
@@ -589,3 +1005,70 @@ Last export at: @date."""
|
||||
)
|
||||
|
||||
return has
|
||||
|
||||
@property
|
||||
def results(self):
|
||||
results = self._results
|
||||
if isinstance(results, types.GeneratorType):
|
||||
self._results = next(results)
|
||||
|
||||
return self._results
|
||||
|
||||
@results.setter
|
||||
def results(self, results):
|
||||
solv_type = results.get("solver_type")
|
||||
|
||||
self._results[solv_type] = results
|
||||
|
||||
def _split_reach(self, reach, profile):
|
||||
profiles = reach.reach.profiles
|
||||
if not profiles or profile not in profiles[1:-1]:
|
||||
raise ValueError(
|
||||
"A reach cannot be split at its first or last profile"
|
||||
)
|
||||
|
||||
node1 = reach.node1
|
||||
node2 = reach.node2
|
||||
|
||||
x1, y1 = node1.x, node1.y
|
||||
x2, y2 = node2.x, node2.y
|
||||
|
||||
center_x = (x1 + x2) / 2
|
||||
center_y = (y1 + y2) / 2
|
||||
|
||||
new_node = self.add_node(center_x, center_y)
|
||||
|
||||
r1, r2 = reach._split(profile, new_node)
|
||||
|
||||
self._add_edge(r1)
|
||||
self._add_edge(r2)
|
||||
|
||||
self._initial_conditions.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._lateral_contribution.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._hydraulic_structures.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._InitialConditionsAdisTS.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._LateralContributionsAdisTS.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._InitialConditionsTemperature.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._WeatherParameters.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._D90AdisTS.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
self._DIFAdisTS.split_reach(
|
||||
reach, profile, r1, r2
|
||||
)
|
||||
|
||||
return r1, r2
|
||||
@@ -1,53 +0,0 @@
|
||||
# Saved.py -- Pamhyr model status class
|
||||
# Copyright (C) 2023-2024 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class SavedStatus(object):
|
||||
def __init__(self, version=0):
|
||||
super(SavedStatus, self).__init__()
|
||||
self._version = version
|
||||
self._saved = True
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
@version.setter
|
||||
def version(self, version):
|
||||
self._version = version
|
||||
|
||||
def is_saved(self):
|
||||
return self._saved
|
||||
|
||||
def save(self):
|
||||
logger.debug("model status set as saved")
|
||||
self._saved = True
|
||||
|
||||
def modified(self):
|
||||
if self._saved:
|
||||
self._version += 1
|
||||
|
||||
logger.debug(
|
||||
"STATUS: Model status set as modified " +
|
||||
f"at version {self._version}"
|
||||
)
|
||||
self._saved = False
|
||||
@@ -0,0 +1,375 @@
|
||||
# Scenario.py -- Pamhyr
|
||||
# Copyright (C) 2024-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from tools import logger_exception, timer, flatten
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Scenario(SQLSubModel):
|
||||
_id_cnt = 0
|
||||
_sub_classes = []
|
||||
|
||||
tables_with_deleted_column = [
|
||||
# Adists
|
||||
"boundary_condition_adists", "boundary_condition_data_adists",
|
||||
"lateral_contribution_adists", "lateral_contribution_data_adists",
|
||||
"initial_conditions_adists", "initial_conditions_adists_spec",
|
||||
"d90_adists", "d90_adists_spec",
|
||||
"dif_adists_spec",
|
||||
"pollutants", "pollutants_characteristics",
|
||||
# Hydraulic
|
||||
"additional_files",
|
||||
"boundary_condition", "boundary_condition_data",
|
||||
"lateral_contribution", "lateral_contribution_data",
|
||||
"friction", "stricklers",
|
||||
"hydraulic_structures",
|
||||
"hydraulic_structures_basic",
|
||||
"initial_conditions",
|
||||
"sedimentary_layer", "sedimentary_layer_layer",
|
||||
"reservoir", "reservoir_data",
|
||||
"rep_lines",
|
||||
"geometry_pointXYZ", "geometry_profileXYZ",
|
||||
"river_reach", "river_node",
|
||||
"geotiff", "reservoir", "reservoir_data",
|
||||
# Temperature
|
||||
"boundary_condition_temperature",
|
||||
"boundary_condition_data_temperature",
|
||||
"initial_conditions_temperature",
|
||||
"initial_conditions_temperature_spec",
|
||||
"weather_parameters", "weather_parameters_data",
|
||||
"ensemble",
|
||||
]
|
||||
|
||||
related_tables = tables_with_deleted_column + [
|
||||
"dif_adists",
|
||||
"solver_parameter",
|
||||
"hydraulic_structures_basic_value",
|
||||
"results", "results_data", "results_add_data",
|
||||
]
|
||||
|
||||
def __init__(self,
|
||||
id: int = -1,
|
||||
name: str = "",
|
||||
description: str = "",
|
||||
x: int = 1000.0, y: int = 1000.0,
|
||||
is_ensemble: bool = False,
|
||||
revision: int = 0,
|
||||
parent=None):
|
||||
super(Scenario, self).__init__()
|
||||
|
||||
self._set_id(id)
|
||||
|
||||
self._x, self._y = x, y
|
||||
self._name = name
|
||||
self._description = description
|
||||
self._is_ensemble = is_ensemble
|
||||
self._revision = revision
|
||||
self._parent = parent
|
||||
|
||||
def _set_id(self, id):
|
||||
if id == -1:
|
||||
self._id = Scenario._id_cnt
|
||||
else:
|
||||
self._id = id
|
||||
|
||||
Scenario._id_cnt = max(
|
||||
self._id + 1, Scenario._id_cnt + 1
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute):
|
||||
execute("""
|
||||
CREATE TABLE scenario(
|
||||
id INTEGER PRIMARY KEY,
|
||||
x REAL NOT NULL DEFAULT 1000,
|
||||
y REAL NOT NULL DEFAULT 1000,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
is_ensemble BOOL DEFAULT FALSE,
|
||||
revision INTEGER NOT NULL,
|
||||
parent_id INTEGER REFERENCES scenario(id)
|
||||
)
|
||||
""")
|
||||
|
||||
cls._create_submodel(execute)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _db_add_default(cls, execute):
|
||||
execute(
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"scenario(id, x, y, name, description, revision, parent_id) " +
|
||||
"VALUES (\n" +
|
||||
" 0, 1000, 1000, 'default', 'Default scenario',\n" +
|
||||
" 0, NULL\n" +
|
||||
")"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def create_db_add_scenario(cls):
|
||||
return "scenario INTEGER NOT NULL DEFAULT 0"
|
||||
|
||||
@classmethod
|
||||
def create_db_add_scenario_fk(cls):
|
||||
return "FOREIGN KEY(scenario) REFERENCES scenario(id)"
|
||||
|
||||
@classmethod
|
||||
def _db_update(cls, execute, version, data=None):
|
||||
major, minor, release = version.strip().split(".")
|
||||
|
||||
if major == "0" and int(minor) < 2:
|
||||
cls._db_create(execute)
|
||||
cls._db_add_default(execute)
|
||||
|
||||
if major == "0" and int(minor) == 2:
|
||||
if int(release) < 8:
|
||||
execute(
|
||||
f"ALTER TABLE scenario " +
|
||||
"ADD COLUMN is_ensemble BOOL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def update_db_add_scenario(cls, execute, table):
|
||||
execute(
|
||||
f"ALTER TABLE {table} " +
|
||||
"ADD COLUMN scenario INTEGER NOT NULL DEFAULT 0"
|
||||
)
|
||||
|
||||
# execute(
|
||||
# f"ALTER TABLE {table} " +
|
||||
# "ADD CONSTRAINT fk_scenario FOREIGN KEY (scenario) " +
|
||||
# "REFERENCES scenario(id)"
|
||||
# )
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
scenarios = {}
|
||||
|
||||
table = execute(
|
||||
"SELECT id, x, y, name, description, " +
|
||||
" is_ensemble, revision, parent_id " +
|
||||
"FROM scenario " +
|
||||
"ORDER BY id ASC"
|
||||
)
|
||||
|
||||
for row in table:
|
||||
it = iter(row)
|
||||
|
||||
id = next(it)
|
||||
x, y = next(it), next(it)
|
||||
name = next(it)
|
||||
desc = next(it)
|
||||
is_ens = next(it)
|
||||
revi = next(it)
|
||||
parent = next(it)
|
||||
|
||||
if parent is not None:
|
||||
parent = scenarios[parent]
|
||||
|
||||
new = cls(
|
||||
id=id, x=x, y=y,
|
||||
name=name, description=desc,
|
||||
is_ensemble=is_ens,
|
||||
revision=revi,
|
||||
parent=parent
|
||||
)
|
||||
scenarios[id] = new
|
||||
|
||||
return scenarios
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
if self.is_deleted():
|
||||
return self.drop_all(execute)
|
||||
|
||||
parent = None
|
||||
if self.parent is not None:
|
||||
parent = self.parent._id
|
||||
|
||||
execute(
|
||||
"INSERT OR REPLACE INTO " +
|
||||
"scenario(id, x, y, name, description, " +
|
||||
" is_ensemble, revision, parent_id) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
self._id, self.x, self.y,
|
||||
self.name, self.description, self._is_ensemble,
|
||||
self._revision, parent
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def drop_all(self, execute):
|
||||
execute(f"DELETE FROM scenario WHERE id = {self.id}")
|
||||
|
||||
tables = self.related_tables
|
||||
|
||||
for table in tables:
|
||||
execute(
|
||||
f"DELETE FROM {table} " +
|
||||
f"WHERE scenario = {self.id}"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def get_parent_branch(self):
|
||||
def aux(scenario, acc):
|
||||
if scenario is None:
|
||||
return acc
|
||||
|
||||
return aux(scenario.parent, [scenario.id] + acc)
|
||||
|
||||
return aux(self, [])
|
||||
|
||||
@timer
|
||||
def clean_deleted_data(self, execute):
|
||||
tables = self.tables_with_deleted_column
|
||||
branch = self.get_parent_branch()
|
||||
|
||||
all_ids = []
|
||||
|
||||
for table in tables:
|
||||
if self.parent is None:
|
||||
# This scenario is the default scenario, so we can
|
||||
# delete all data marked as deleted
|
||||
execute(
|
||||
f"DELETE FROM {table} " +
|
||||
f"WHERE scenario = {self.id} " +
|
||||
"AND deleted = TRUE"
|
||||
)
|
||||
continue
|
||||
|
||||
# Select pamhyr_id for each deleted data in this scenario
|
||||
# who do not exists into parents scenarios
|
||||
ids = execute(
|
||||
f"""
|
||||
SELECT pamhyr_id FROM {table}
|
||||
WHERE deleted = TRUE
|
||||
AND scenario = {self.id}
|
||||
AND pamhyr_id NOT IN (
|
||||
SELECT pamhyr_id FROM {table}
|
||||
WHERE scenario IN
|
||||
({', '.join(map(str, branch[:-1]))})
|
||||
)
|
||||
""", fetch_one=False
|
||||
)
|
||||
if ids is None or len(ids) == 0:
|
||||
continue
|
||||
|
||||
ids = flatten(ids)
|
||||
|
||||
logger.debug(
|
||||
f"({self.name}) Drop deleted data into '{table}' : {ids}"
|
||||
)
|
||||
|
||||
execute(
|
||||
f"DELETE FROM {table} " +
|
||||
f"WHERE scenario = {self.id} " +
|
||||
"AND pamhyr_id IN " +
|
||||
f"({', '.join(map(str, ids))})"
|
||||
)
|
||||
|
||||
all_ids += ids
|
||||
|
||||
return all_ids
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self._x
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
return self._y
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == "":
|
||||
return f"Child of '{self._parent.name}' ({self.id})"
|
||||
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
if self._description == "":
|
||||
return f"Child of '{self._parent.name}' ({self.id})"
|
||||
|
||||
return self._description
|
||||
|
||||
@description.setter
|
||||
def description(self, description):
|
||||
self._description = description
|
||||
|
||||
@property
|
||||
def revision(self):
|
||||
return self._revision
|
||||
|
||||
@revision.setter
|
||||
def revision(self, revision):
|
||||
self._revision = revision
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
def is_ensemble(self):
|
||||
return self._is_ensemble
|
||||
|
||||
def set_pos(self, x, y):
|
||||
self._x = x
|
||||
self._y = y
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key == "name":
|
||||
self.name = value
|
||||
elif key == "description":
|
||||
self.description = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == "id":
|
||||
return self.id
|
||||
if key == "name":
|
||||
return self.name
|
||||
if key == "description":
|
||||
return self.description
|
||||
if key == "is_ensemble":
|
||||
return self.is_ensemble()
|
||||
if key == "parent":
|
||||
return self.parent
|
||||
|
||||
return None
|
||||
|
||||
def workdir(self):
|
||||
current = "scenario_" + str(self.id)
|
||||
if self.parent is None or self.parent.id == 0:
|
||||
return current
|
||||
|
||||
return os.path.join(self.parent.workdir(), current)
|
||||
@@ -0,0 +1,94 @@
|
||||
# Scenarios.py -- Pamhyr
|
||||
# Copyright (C) 2024-2025 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from functools import reduce
|
||||
|
||||
from tools import logger_exception
|
||||
|
||||
from Model.Tools.PamhyrDict import PamhyrModelDict
|
||||
from Model.Scenario import Scenario
|
||||
|
||||
|
||||
class Scenarios(PamhyrModelDict):
|
||||
_sub_classes = [
|
||||
Scenario,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _db_load(cls, execute, data=None):
|
||||
new = cls(status=data["status"])
|
||||
|
||||
new._dict = Scenario._db_load(
|
||||
execute,
|
||||
data=data
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
ok = True
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
scenarios = self._dict
|
||||
for sid in scenarios:
|
||||
ok &= scenarios[sid]._db_save(execute, data)
|
||||
|
||||
return ok
|
||||
|
||||
def get(self, key):
|
||||
if key in self._dict:
|
||||
return self._dict[key]
|
||||
|
||||
return None
|
||||
|
||||
def new(self, parent, is_ensemble=False):
|
||||
new = Scenario(
|
||||
parent=parent,
|
||||
is_ensemble=is_ensemble
|
||||
)
|
||||
self.set(new._id, new)
|
||||
return new
|
||||
|
||||
def __len__(self):
|
||||
return len(self.lst)
|
||||
|
||||
@property
|
||||
def lst(self):
|
||||
return list(
|
||||
map(
|
||||
lambda k: self.get(k),
|
||||
self._dict
|
||||
)
|
||||
)
|
||||
|
||||
def delete(self, key):
|
||||
if key not in self._dict:
|
||||
return
|
||||
|
||||
self._dict[key].set_as_deleted()
|
||||
self._status.modified()
|
||||
|
||||
def is_leaf(self, scenario):
|
||||
return not reduce(
|
||||
lambda acc, s: (
|
||||
acc or (not s.is_deleted() and s.parent is scenario)
|
||||
),
|
||||
self._dict.values(),
|
||||
False
|
||||
)
|
||||