Compare commits

..
Author SHA1 Message Date
parouby 7eb39e5896 git: Update 'full-requirement.txt'. 2026-10-01 09:30:34 +02:00
parouby 28cc88b4b8 Doc: Wiki: En: Add ensemble images. 2026-09-30 16:56:00 +02:00
parouby c9925cd03c tests_cases: Add Saar version with ensemble. 2026-09-30 16:54:04 +02:00
parouby 40f9465929 Pamhyr2: PEP8 minor fix. 2026-09-29 17:17:02 +02:00
parouby 359805b713 Ensemble: Edit: Add message box for invalid parameter. 2026-09-29 16:24:25 +02:00
parouby 31565b9170 Ensemble: Distribution: Add parameters checkers. 2026-09-29 16:16:21 +02:00
parouby 9c2a0541df Pamhyr2: Fix PEP 8. 2026-09-25 16:51:04 +02:00
parouby 38d7d587fa Ensemble: Fix widget double spin box min and max. 2026-09-25 16:17:42 +02:00
parouby f19ab9455a Ensemble: Edit: Add Binomial distribution and minor change. 2026-09-25 15:16:32 +02:00
parouby cadffe40d6 Ensemble: Edit: Fix crash at change 1 params distribution to 2 params. 2026-09-25 14:58:50 +02:00
parouby 5a195e2822 Ensemble: Minor indent fix. 2026-09-25 11:51:09 +02:00
parouby 63592b91a9 Ensemble: Add Export/Import method with json files. 2026-09-25 11:46:37 +02:00
parouby 88345c58a7 Ensemble: Fix editable status at ensemble creation. 2026-09-24 16:04:42 +02:00
parouby 7124b2b61c Ensemble: Results: Setup export methods. 2026-09-24 15:14:35 +02:00
parouby f93e6a7d7c Results: Refactoring '_export_time' method. 2026-09-24 11:24:52 +02:00
parouby 5d46401b85 Results: Refactoring '_export_rk' methode. 2026-09-24 10:30:16 +02:00
parouby 97867080d9 Ensemble: Add undo command to ensemble edition. 2026-09-23 16:28:30 +02:00
parouby 93a4b44143 Ensemble: Results: Add standard derivation to hydrogram plot. 2026-09-23 16:07:31 +02:00
parouby 5e3e5628d2 Results: Fix river loading reachs creation. 2026-09-23 14:27:59 +02:00
parouby 4a6349cd9c Merge branch 'scenario-dev-pa' of gitlab.com:pamhyr/pamhyr2 into scenario-dev-pa 2026-09-22 18:49:57 +02:00
parouby e9c998fec8 Ensemble: Results: Add seed and number_of_run to DB (0.2.9). 2026-09-22 18:48:37 +02:00
parouby 290b8918fc Ensemble: Fix context menu if right click on non current scenario. 2026-09-22 18:20:02 +02:00
parouby 0e6de5d0f0 Results: PlotRKC: Fix bedload evolution display. 2026-09-22 18:01:12 +02:00
parouby 98d30a326e Ensemble: Edit: Display current selected target data. 2026-09-22 16:39:55 +02:00
parouby 5f64499984 Ensemble: Results: Remove custom plot. 2026-09-22 15:43:00 +02:00
parouby 6e32c740ae Doc: Dev: Add text for glab installation. 2026-09-22 15:41:54 +02:00
Theophile Terraz e0721715d0 update matplotlib version in requirements.txt 2026-09-22 14:59:25 +02:00
parouby 10edc00973 Pamhyr2: Fix PEP8. 2026-09-18 16:52:40 +02:00
parouby f8829ce82c View: Tools: Tree: Minor change. 2026-09-18 16:44:26 +02:00
parouby ac91b712a3 Ensemble: Switch to edit dialog. 2026-09-18 16:34:29 +02:00
parouby 4bccbbac33 Ensemble: Select current tree item at dialog open. 2026-09-18 16:27:11 +02:00
parouby 5c8b7290ba Ensemble: Add stricklers select from tree view. 2026-09-18 16:06:51 +02:00
parouby 288713ce5b View: Tools: Add tree model. 2026-09-18 15:51:09 +02:00
parouby ff3b5a320c Ensemble: Edit: Add parameters widgets. 2026-09-18 10:32:45 +02:00
parouby a2e0f6a517 Ensemble: Add base of edit dialog. 2026-09-17 16:07:46 +02:00
parouby 7356e5dd30 Ensemble: Create ensemble scenario from current scenario. 2026-09-17 10:02:01 +02:00
parouby 933c6c1952 Ensemble: PlotRKC: Add standard derivation. 2026-09-16 16:10:46 +02:00
parouby d8fd4488e2 Ensemble: Add standard derivation computation. 2026-09-16 15:24:00 +02:00
parouby fef2a2e979 Pamhyr: Fix pep8. 2026-09-15 17:33:51 +02:00
parouby f001dffb68 Ensemble: PlotRKC: Minor change. 2026-09-15 17:29:37 +02:00
parouby 2818644b7f Ensemble: Distribution: Add default values for each distribution. 2026-09-15 16:57:00 +02:00
parouby 86d93fd702 Ensemble: Ui: Minor change. 2026-09-15 16:47:26 +02:00
parouby 593cc0c91b Ensemble: Mage: Fix iterative mean computation. 2026-09-15 16:32:27 +02:00
parouby 32a316589b Ensemble: Rename 'run_number' to 'number_of_run' and fix seed spin box max value. 2026-09-10 16:49:11 +02:00
parouby d4bc9bdf75 Ensemble: Set custom seed. 2026-09-10 16:39:38 +02:00
parouby 3325ba935b Ensemble: Run: Add function combobox and seed spin box. 2026-09-10 16:17:57 +02:00
parouby 345f24996f Ensemble: Rename function to disctribution. 2026-09-10 15:56:29 +02:00
parouby 4040b2456c Ensemble: Function: Add Poisson law and 1 params dialog. 2026-09-10 15:09:24 +02:00
parouby 8e77d31a5a Ensemble: Add normal low and generalise minmaxdialog. 2026-09-10 14:57:48 +02:00
parouby 6e029beafc Ensemble: Function: Rename get_sample to random and use np.random. 2026-09-10 11:40:57 +02:00
parouby f5a7cd5984 Ensemble: PlotH: Add ensemble ploth. 2026-09-10 11:14:03 +02:00
parouby d4d5e26a2b Ensemble: Results: Rename Z to z_mean and dont keep individual result. 2026-09-10 10:44:03 +02:00
parouby 92990948ad Ensemble: PlotRKC: Fix table selection plot update. 2026-09-10 10:31:30 +02:00
50 changed files with 2514 additions and 714 deletions

No files matched your search

+6
View File
@@ -1069,6 +1069,12 @@ gitlab-ci.
sudo python3 -m pip install pyinstaller sudo python3 -m pip install pyinstaller
#+end_src #+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}}} *** Windows (Wine) {{{windows}}}
The ci-runner environment for Wine need at least wine version 8, let The ci-runner environment for Wine need at least wine version 8, let
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+2 -1
View File
@@ -4,7 +4,7 @@ PyQt5-sip==12.18.0
PyQtWebEngine==5.15.6 PyQtWebEngine==5.15.6
QScintilla>=2.14.1 QScintilla>=2.14.1
pyqtgraph>=0.12.1 pyqtgraph>=0.12.1
matplotlib==3.8.3 matplotlib==3.10.8
numpy>=1.24.2 numpy>=1.24.2
colorama>=0.4.3 colorama>=0.4.3
pyinstaller>=5.11.0 pyinstaller>=5.11.0
@@ -14,3 +14,4 @@ platformdirs>=4.2.0
pyshp>=2.3.1 pyshp>=2.3.1
rasterio==1.3.11 rasterio==1.3.11
#fortranformat==2.0.3 #fortranformat==2.0.3
psutil>=7.2.2
+1 -1
View File
@@ -4,7 +4,7 @@ PyQt5-sip==12.18.0
#PyQtWebEngine==5.15.6 #PyQtWebEngine==5.15.6
QScintilla>=2.14.1 QScintilla>=2.14.1
pyqtgraph>=0.12.1 pyqtgraph>=0.12.1
matplotlib==3.8.3 matplotlib==3.10.8
numpy>=1.24.2 numpy>=1.24.2
colorama>=0.4.3 colorama>=0.4.3
pyinstaller>=5.11.0 pyinstaller>=5.11.0
@@ -1,4 +1,4 @@
# Function.py -- Pamhyr # Distribution.py -- Pamhyr
# Copyright (C) 2026 INRAE # Copyright (C) 2026 INRAE
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
@@ -30,13 +30,13 @@ from Model.Scenario import Scenario
logger = logging.getLogger() logger = logging.getLogger()
class Function(SQLSubModel): class Distribution(SQLSubModel):
_sub_classes = [] _sub_classes = []
def __init__(self, id: int = -1, def __init__(self, id: int = -1,
name: str = "", script: str = "", name: str = "", script: str = "",
status=None): status=None):
super(Function, self).__init__( super(Distribution, self).__init__(
id=id, status=status, id=id, status=status,
owner_scenario=0 owner_scenario=0
) )
@@ -44,15 +44,27 @@ class Function(SQLSubModel):
self._name = name self._name = name
self._type = "generic" self._type = "generic"
self._script = script self._script = script
self._labels = []
self._default_values = []
self._params_checkers = []
self._params_tips = []
@property @property
def name(self): def name(self):
return self._name return self._name
@property
def labels(self):
return self._labels
@property
def default_values(self):
return self._default_values
@classmethod @classmethod
def _db_create(cls, execute, ext=""): def _db_create(cls, execute, ext=""):
execute(f""" execute(f"""
CREATE TABLE ensemble_function{ext} ( CREATE TABLE ensemble_distribution{ext} (
{cls.create_db_add_pamhyr_id()}, {cls.create_db_add_pamhyr_id()},
deleted BOOLEAN NOT NULL DEFAULT FALSE, deleted BOOLEAN NOT NULL DEFAULT FALSE,
name TEXT NOT NULL, name TEXT NOT NULL,
@@ -85,7 +97,7 @@ class Function(SQLSubModel):
table = execute( table = execute(
"SELECT pamhyr_id, deleted, name, script " + "SELECT pamhyr_id, deleted, name, script " +
"FROM ensemble_function " + "FROM ensemble_distribution " +
f"WHERE pamhyr_id NOT IN ({', '.join(map(str, loaded))})" f"WHERE pamhyr_id NOT IN ({', '.join(map(str, loaded))})"
) )
@@ -97,20 +109,20 @@ class Function(SQLSubModel):
name = next(it) name = next(it)
script = next(it) script = next(it)
new_function = cls( new_distribution = cls(
id, name=name, script=script, id, name=name, script=script,
status=data["status"] status=data["status"]
) )
if deleted: if deleted:
new_function.set_as_deleted() new_distribution.set_as_deleted()
loaded.add(id) loaded.add(id)
new.append(new_function) new.append(new_distribution)
return new return new
def _db_save(self, execute, data=None): def _db_save(self, execute, data=None):
# Only save custom function # Only save custom distribution
if self._type != "custom": if self._type != "custom":
return True return True
@@ -119,7 +131,7 @@ class Function(SQLSubModel):
execute( execute(
"INSERT INTO " + "INSERT INTO " +
"ensemble_function(pamhyr_id, deleted, " + "ensemble_distribution(pamhyr_id, deleted, " +
" name, script) " + " name, script) " +
"VALUES (?, ?, ?, ?)", "VALUES (?, ?, ?, ?)",
self.pamhyr_id, self.is_deleted(), self.pamhyr_id, self.is_deleted(),
@@ -135,15 +147,30 @@ class Function(SQLSubModel):
if predicate(self, data): if predicate(self, data):
modifier(self, data) modifier(self, data)
def get_sample(self, r): 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) raise NotImplementedMethodeError(cls, cls._load)
########################### ###############################
# FUNCTION IMPLEMENTATION # # DISTRIBUTION IMPLEMENTATION #
########################### ###############################
class Uniform(Function): class Uniform(Distribution):
_sub_classes = [] _sub_classes = []
def __init__(self, id: int = -1, def __init__(self, id: int = -1,
@@ -154,19 +181,106 @@ class Uniform(Function):
name=name, script=script name=name, script=script
) )
def get_sample(self, ens_range, nb): self._labels = ["low", "high"]
b = ens_range[0] self._default_values = [20.0, 40.0]
t = ens_range[1] self._params_checkers = [self.check_params, self.check_params]
self._params_tips = ["uniform_low_tips", "uniform_high_tips"]
step = (t - b) / nb def check_params(self, params):
return (params[0] < params[1])
r = np.arange(b, t, step) def random(self, params, nb):
np.random.shuffle(r) lst = params.copy()
lst.append(nb)
return r return np.random.uniform(*lst)
class Custom(Function): 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 = [] _sub_classes = []
def __init__(self, id: int = -1, def __init__(self, id: int = -1,
@@ -177,13 +291,17 @@ class Custom(Function):
name=name, script=script name=name, script=script
) )
self._labels = ["custom"]
self._type = "custom" self._type = "custom"
self._default_values = [""]
self._params_checkers = [self.check_params]
self._params_tips = ["custom_params_tips"]
def get_sample(self, ens_range, nb): def random(self, params, nb):
try: try:
# Run script to (re)define the sample function # Run script to (re)define the sample distribution
f = eval(self._script) f = eval(self._script)
return f(ens_range, nb) return f(params, nb)
except Exception as e: except Exception as e:
logger_exception(e) logger_exception(e)
return None # TODO: Raise helpful exception return None # TODO: Raise helpful exception
@@ -1,4 +1,4 @@
# FunctionList.py -- Pamhyr # DistributionList.py -- Pamhyr
# Copyright (C) 2026 INRAE # Copyright (C) 2026 INRAE
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
@@ -19,23 +19,27 @@
from tools import trace, timer from tools import trace, timer
from Model.Tools.PamhyrListExt import PamhyrModelList from Model.Tools.PamhyrListExt import PamhyrModelList
from Model.Ensembles.Function import * from Model.Ensembles.Distribution import *
class FunctionList(PamhyrModelList): class DistributionList(PamhyrModelList):
_sub_classes = [Function] _sub_classes = [Distribution]
@classmethod @classmethod
def _db_load(cls, execute, data=None): def _db_load(cls, execute, data=None):
new = cls(status=data['status']) new = cls(status=data['status'])
# Default functions # Default distributions
d = {"status": data['status']}
new._lst = [ new._lst = [
Uniform(status=data['status']), Uniform(**d),
Normal(**d),
Binomial(**d),
Poisson(**d),
] ]
# DB custom functions # DB custom distributions
new._lst += Function._db_load( new._lst += Distribution._db_load(
execute, data execute, data
) )
@@ -43,7 +47,7 @@ class FunctionList(PamhyrModelList):
def _db_save(self, execute, data={}): def _db_save(self, execute, data={}):
execute( execute(
"DELETE FROM ensemble_function" "DELETE FROM ensemble_distribution"
) )
for fun in self._lst: for fun in self._lst:
+48 -23
View File
@@ -24,7 +24,7 @@ import numpy as np
from Model.Tools.PamhyrDB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario from Model.Scenario import Scenario
from Model.Ensembles.Function import Uniform from Model.Ensembles.Distribution import Uniform
logger = logging.getLogger() logger = logging.getLogger()
@@ -33,7 +33,7 @@ class Ensemble(SQLSubModel):
_sub_classes = [] _sub_classes = []
def __init__(self, id: int = -1, name: str = "", def __init__(self, id: int = -1, name: str = "",
function=None, params=[0., 100.], distribution=None, params=[0., 100.],
data_type=None, target_data=None, data_type=None, target_data=None,
status=None, owner_scenario=-1): status=None, owner_scenario=-1):
super(Ensemble, self).__init__( super(Ensemble, self).__init__(
@@ -43,7 +43,7 @@ class Ensemble(SQLSubModel):
self._name = name self._name = name
self._data_type = data_type self._data_type = data_type
self._function = function self._distribution = distribution
self._params = params self._params = params
self._target_data = target_data self._target_data = target_data
@@ -55,8 +55,8 @@ class Ensemble(SQLSubModel):
deleted BOOLEAN NOT NULL DEFAULT FALSE, deleted BOOLEAN NOT NULL DEFAULT FALSE,
name TEXT NOT NULL, name TEXT NOT NULL,
type VARCHAR(8) NOT NULL, type VARCHAR(8) NOT NULL,
function INTEGER NOT NULL, distribution INTEGER NOT NULL,
function_name VARCHAR(32) NOT NULL, distribution_name VARCHAR(32) NOT NULL,
params BLOB NOT NULL, params BLOB NOT NULL,
params_len INTEGER NOT NULL, params_len INTEGER NOT NULL,
data_pid INTEGER NOT NULL, data_pid INTEGER NOT NULL,
@@ -86,13 +86,13 @@ class Ensemble(SQLSubModel):
def _db_load(cls, execute, data=None): def _db_load(cls, execute, data=None):
new = [] new = []
scenario = data["scenario"] scenario = data["scenario"]
functions = data["ens_functions"] distributions = data["ens_distributions"]
stricklers = data["stricklers"] stricklers = data["stricklers"]
loaded = data['loaded_pid'] loaded = data['loaded_pid']
table = execute( table = execute(
"SELECT pamhyr_id, deleted, name, type, " + "SELECT pamhyr_id, deleted, name, type, " +
"function, function_name, params, params_len, data_pid, " + "distribution, distribution_name, params, params_len, data_pid, " +
"scenario " + "scenario " +
"FROM ensemble " + "FROM ensemble " +
f"WHERE scenario = {scenario.id} " + f"WHERE scenario = {scenario.id} " +
@@ -117,8 +117,8 @@ class Ensemble(SQLSubModel):
if fid != -1: if fid != -1:
def fn(f): return f._pamhyr_id == fid def fn(f): return f._pamhyr_id == fid
function = next( distribution = next(
filter(fn, functions.lst), filter(fn, distributions.lst),
None None
) )
@@ -130,11 +130,11 @@ class Ensemble(SQLSubModel):
None None
) )
rdata = cls._decode_params(params, params_len) rdata = list(cls._decode_params(params, params_len))
new_ensemble = cls( new_ensemble = cls(
id, name=name, id, name=name,
function=function, distribution=distribution,
params=rdata, params=rdata,
data_type=data_type, data_type=data_type,
target_data=target_data, target_data=target_data,
@@ -161,14 +161,14 @@ class Ensemble(SQLSubModel):
bparams, length = self._encode_params() bparams, length = self._encode_params()
if self._function is None: if self._distribution is None:
fid = -1 fid = -1
fname = "NULL" fname = "NULL"
else: else:
fid = self._function._pamhyr_id fid = self._distribution._pamhyr_id
fname = self._function._name fname = self._distribution._name
if self._function._type == "generic": if self._distribution._type == "generic":
fid = -1 fid = -1
data_pid = -1 data_pid = -1
@@ -178,7 +178,7 @@ class Ensemble(SQLSubModel):
execute( execute(
"INSERT INTO " + "INSERT INTO " +
"ensemble(pamhyr_id, deleted, name, type, " + "ensemble(pamhyr_id, deleted, name, type, " +
" function, function_name, params, params_len, " + " distribution, distribution_name, params, params_len, " +
" data_pid, scenario) " + " data_pid, scenario) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
self.pamhyr_id, self.is_deleted(), self.pamhyr_id, self.is_deleted(),
@@ -201,9 +201,9 @@ class Ensemble(SQLSubModel):
return struct.unpack(data_format, bparams) return struct.unpack(data_format, bparams)
def get_sample(self, nb): def random(self, nb):
return np.nditer( return np.nditer(
self._function.get_sample( self._distribution.random(
self._params, nb self._params, nb
) )
) )
@@ -218,12 +218,12 @@ class Ensemble(SQLSubModel):
self._status.modified() self._status.modified()
@property @property
def function(self): def distribution(self):
return self._function return self._distribution
@function.setter @distribution.setter
def function(self, function): def distribution(self, distribution):
self._function = function self._distribution = distribution
self._status.modified() self._status.modified()
@property @property
@@ -252,3 +252,28 @@ class Ensemble(SQLSubModel):
def target_data(self, target_data): def target_data(self, target_data):
self._target_data = target_data self._target_data = target_data
self._status.modified() 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"]
+9
View File
@@ -51,3 +51,12 @@ class EnsembleList(PamhyrModelList):
self._lst.insert(index, r) self._lst.insert(index, r)
self._status.modified() self._status.modified()
return r 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
+7
View File
@@ -826,6 +826,13 @@ class ProfileXYZ(Profile, SQLSubModel):
return n_zones return n_zones
def froude(self, z, v):
return v / np.sqrt(
9.81 * (
self.wet_area(z) / self.wet_width(z)
)
)
@timer @timer
def get_all_water_limits_ac(self, z): def get_all_water_limits_ac(self, z):
""" """
+41 -7
View File
@@ -277,9 +277,9 @@ class TableData(SQLSubModel):
if len(shape) == 2: if len(shape) == 2:
shape_x, shape_y = shape shape_x, shape_y = shape
elif len(shape == 1): elif len(shape) == 1:
shape_x, = shape shape_x, = shape
elif len(shape == 3): elif len(shape) == 3:
shape_x, shape_y, shape_z = shape shape_x, shape_y, shape_z = shape
else: else:
logger.error( logger.error(
@@ -436,9 +436,11 @@ class Results(SQLSubModel):
solver_name TEXT NOT NULL, solver_name TEXT NOT NULL,
solver_type TEXT NOT NULL, solver_type TEXT NOT NULL,
study_revision INTEGER NOT NULL, study_revision INTEGER NOT NULL,
creation_data DATE NOT NULL, creation_date DATE NOT NULL,
nb_timestamps INTEGER NOT NULL, nb_timestamps INTEGER NOT NULL,
timestamps BLOB NOT NULL, timestamps BLOB NOT NULL,
seed INTEGER,
number_of_run INTEGER,
{Scenario.create_db_add_scenario()}, {Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()}, {Scenario.create_db_add_scenario_fk()},
PRIMARY KEY(pamhyr_id, scenario) PRIMARY KEY(pamhyr_id, scenario)
@@ -464,6 +466,21 @@ class Results(SQLSubModel):
cls._db_create(execute) cls._db_create(execute)
create = True 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) return cls._update_submodel(execute, version, data)
@classmethod @classmethod
@@ -476,7 +493,9 @@ class Results(SQLSubModel):
table = execute( table = execute(
"SELECT pamhyr_id, solver_name, solver_type, " + "SELECT pamhyr_id, solver_name, solver_type, " +
"study_revision, creation_data, nb_timestamps, timestamps, " + "study_revision, creation_date, " +
"seed, number_of_run, " +
"nb_timestamps, timestamps, " +
"scenario " + "scenario " +
"FROM results " + "FROM results " +
f"WHERE scenario = {scenario.id}" f"WHERE scenario = {scenario.id}"
@@ -496,6 +515,8 @@ class Results(SQLSubModel):
solver_type = next(it) solver_type = next(it)
revision = next(it) revision = next(it)
creation_date = next(it) creation_date = next(it)
seed = next(it)
number_of_run = next(it)
nb_timestamps = next(it) nb_timestamps = next(it)
timestamps_bytes = next(it) timestamps_bytes = next(it)
owner_scenario = next(it) owner_scenario = next(it)
@@ -506,6 +527,9 @@ class Results(SQLSubModel):
new_results.set("solver_type", solver_type) new_results.set("solver_type", solver_type)
new_results.set("study_revision", revision) new_results.set("study_revision", revision)
new_results.set("creation_date", creation_date) 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_pid"] = pid
data["result"] = new_results data["result"] = new_results
@@ -598,15 +622,25 @@ class Results(SQLSubModel):
ts = sorted(self.get("timestamps")) ts = sorted(self.get("timestamps"))
sf = ">" + ''.join(itertools.repeat("d", len(ts))) 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( execute(
"INSERT INTO " + "INSERT INTO " +
"results (pamhyr_id, solver_name, solver_type, " + "results (pamhyr_id, solver_name, solver_type, " +
"study_revision, creation_data, " + "study_revision, creation_date, " +
"seed, number_of_run, " +
"nb_timestamps, timestamps, " + "nb_timestamps, timestamps, " +
"scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", "scenario) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
self._pamhyr_id, solver_name, solver_type, self._pamhyr_id, solver_name, solver_type,
self._status.version, self.get("creation_date"), self._status.version, self.get("creation_date"),
len(ts), struct.pack(sf, *ts), self._owner_scenario seed, number_of_run,
len(ts), struct.pack(sf, *ts),
self._owner_scenario
) )
data["result"] = self._pamhyr_id data["result"] = self._pamhyr_id
+5 -4
View File
@@ -393,7 +393,7 @@ class Reach(SQLSubModel):
class River(SQLSubModel): class River(SQLSubModel):
_sub_classes = [Reach] _sub_classes = [Reach]
def __init__(self, study, parent): def __init__(self, study, parent, init=True):
super(River, self).__init__( super(River, self).__init__(
id=-1, status=study.status, id=-1, status=study.status,
owner_scenario=study.status.scenario.id owner_scenario=study.status.scenario.id
@@ -404,8 +404,9 @@ class River(SQLSubModel):
# Dict with timestamps as key # Dict with timestamps as key
self._reachs = [] self._reachs = []
for i, _ in enumerate(self._study.river.enable_edges()): if init:
self.add(i) for i, _ in enumerate(self._study.river.enable_edges()):
self.add(i)
def __len__(self): def __len__(self):
return len(self._reachs) return len(self._reachs)
@@ -458,7 +459,7 @@ class River(SQLSubModel):
study = data["study"] study = data["study"]
parent = data["parent"] parent = data["parent"]
new_river = cls(study, parent) new_river = cls(study, parent, init=False)
for reach in study.river.reachs(): for reach in study.river.reachs():
data["reach"] = reach.reach data["reach"] = reach.reach
+18 -14
View File
@@ -33,7 +33,7 @@ from Model.Status import StudyStatus
from Model.Except import NotImplementedMethodeError from Model.Except import NotImplementedMethodeError
from Model.River import River from Model.River import River
from Model.Geometry.Reach import Reach from Model.Geometry.Reach import Reach
from Model.Ensembles.FunctionList import FunctionList from Model.Ensembles.DistributionList import DistributionList
from Model.HydraulicStructures.HydraulicStructures import ( from Model.HydraulicStructures.HydraulicStructures import (
HydraulicStructure HydraulicStructure
) )
@@ -47,11 +47,11 @@ logger = logging.getLogger()
class Study(SQLModel): class Study(SQLModel):
_version = "0.2.8" _version = "0.2.9"
_sub_classes = [ _sub_classes = [
Scenario, Scenario,
FunctionList, DistributionList,
River, River,
] ]
@@ -87,7 +87,7 @@ class Study(SQLModel):
self.scenarios[0] = s0 self.scenarios[0] = s0
self.status.scenario = s0 self.status.scenario = s0
self._ens_functions = FunctionList(status=self.status) self._ens_distributions = DistributionList(status=self.status)
self._river = River(status=self.status) self._river = River(status=self.status)
else: else:
@@ -399,12 +399,12 @@ class Study(SQLModel):
data["scenario"] = scenario data["scenario"] = scenario
data["loaded_pid"] = set() data["loaded_pid"] = set()
# Get ensemble function # Get ensemble distribution
new._ens_functions = FunctionList._db_load( new._ens_distributions = DistributionList._db_load(
sql_exec, data=data sql_exec, data=data
) )
data["ens_functions"] = new._ens_functions data["ens_distributions"] = new._ens_distributions
# Load river data # Load river data
new._river = River._db_load( new._river = River._db_load(
@@ -468,7 +468,7 @@ class Study(SQLModel):
if self.status.scenario.is_ensemble(): if self.status.scenario.is_ensemble():
lst = [ lst = [
self.scenarios, self._ens_functions, self.scenarios, self._ens_distributions,
self._river._ensembles, self._river._ensembles,
] ]
for solv in self._river._results: for solv in self._river._results:
@@ -484,7 +484,7 @@ class Study(SQLModel):
self._save_submodel( self._save_submodel(
[ [
self.scenarios, self._ens_functions, self.scenarios, self._ens_distributions,
self._river self._river
], ],
data=progress data=progress
@@ -605,7 +605,7 @@ class Study(SQLModel):
"status": self.status, "status": self.status,
"loaded_pid": set(), "loaded_pid": set(),
"scenario": scenario, "scenario": scenario,
"ens_functions": self._ens_functions, "ens_distributions": self._ens_distributions,
} }
self.status.start_loading() self.status.start_loading()
@@ -679,6 +679,7 @@ class Study(SQLModel):
self.status.scenario.y + 100) self.status.scenario.y + 100)
new.name = self.status.scenario.name + " (copy)" new.name = self.status.scenario.name + " (copy)"
new.is_ensemble = self.status.scenario.is_ensemble
self.status.scenario = new self.status.scenario = new
self.river._data_traversal( self.river._data_traversal(
@@ -690,20 +691,23 @@ class Study(SQLModel):
self.status.set_as_editable() self.status.set_as_editable()
return new return new
def create_ensemble_scenario(self, switch=True): def create_ensemble_scenario(self, pos=None, switch=True):
new = self.scenarios.new( new = self.scenarios.new(
self.status.scenario, self.status.scenario,
is_ensemble=True is_ensemble=True
) )
new.name = self.status.scenario.name + " (ensemble)" new.name = self.status.scenario.name + " (ensemble)"
new.set_pos(self.status.scenario.x + 100, if pos is not None:
self.status.scenario.y) new.set_pos(pos.x(), pos.y())
else:
new.set_pos(self.status.scenario.x + 100,
self.status.scenario.y)
if switch: if switch:
self.status.scenario = new self.status.scenario = new
self.status.set_as_editable() self.status.set_as_read_only()
return new return new
@property @property
+7
View File
@@ -185,6 +185,13 @@ class SQLSubModel(PamhyrID):
else: else:
self._owner_scenario = owner_scenario self._owner_scenario = owner_scenario
def __str__(self):
t = type(self)
return (
f"'{t.__module__}.{t.__name__}" +
f"@{self._pamhyr_id}.{self._owner_scenario}'"
)
def must_be_saved(self): def must_be_saved(self):
"""Return True if this object MUST be save in the save file. """Return True if this object MUST be save in the save file.
+20 -24
View File
@@ -1621,25 +1621,33 @@ class Mage8(Mage):
res["z_mean"] = z res["z_mean"] = z
res["q_mean"] = q res["q_mean"] = q
res["z_tmp_M2"] = np.zeros(z.shape)
res["q_tmp_M2"] = np.zeros(q.shape)
res["z_std_derivation"] = np.zeros(z.shape)
res["q_std_derivation"] = np.zeros(q.shape)
res["z_min"] = z res["z_min"] = z
res["z_max"] = z res["z_max"] = z
res["q_min"] = q res["q_min"] = q
res["q_max"] = q res["q_max"] = q
res["res_z_min"] = []
res["res_z_mean"] = []
res["res_z_max"] = []
res["res_q_min"] = []
res["res_q_mean"] = []
res["res_q_max"] = []
res["traces"] = []
else: else:
# Get mean results for Z and Q # Get mean results for Z and Q
res["z_mean"] = (res["z_mean"] + z) / 2 n = run_number
res["q_mean"] = (res["q_mean"] + q) / 2 for x, txt in [(z, "z"), (q, "q")]:
t_mean = f"{txt}_mean"
t_M2 = f"{txt}_tmp_M2"
t_std_deriv = f"{txt}_std_derivation"
delta = x - res[t_mean]
res[t_mean] += delta / n
delta_2 = x - res[t_mean]
res[t_M2] += delta * delta_2
res[t_std_deriv] = np.sqrt(res[t_M2] / n)
# Z and Q min max values # Z and Q min max values
res["z_min"] = np.minimum(z, res["z_min"]) res["z_min"] = np.minimum(z, res["z_min"])
@@ -1647,18 +1655,6 @@ class Mage8(Mage):
res["q_min"] = np.minimum(q, res["q_min"]) res["q_min"] = np.minimum(q, res["q_min"])
res["q_max"] = np.maximum(q, res["q_max"]) res["q_max"] = np.maximum(q, res["q_max"])
# Keep results min, mean and max
res["res_z_min"].append((run_number, np.min(z, axis=0)))
res["res_z_mean"].append((run_number, np.mean(z, axis=0)))
res["res_z_max"].append((run_number, np.max(z, axis=0)))
res["res_q_min"].append((run_number, np.min(q, axis=0)))
res["res_q_mean"].append((run_number, np.mean(q, axis=0)))
res["res_q_max"].append((run_number, np.max(q, axis=0)))
# FIXME: Keep all results ?
res["traces"].append((run_number, tables))
@timer @timer
def results_ensemble(self, study, repertory, def results_ensemble(self, study, repertory,
tables, run_number, tables, run_number,
+64
View File
@@ -0,0 +1,64 @@
# Translate.py -- Pamhyr
# Copyright (C) 2023-2026 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication
from View.Ensembles.Translate import EnsemblesTranslate
_translate = QCoreApplication.translate
class EnsembleEditTranslate(EnsemblesTranslate):
def __init__(self):
super(EnsembleEditTranslate, self).__init__()
self._dict["binomial_n"] = _translate("Ensemble", "n")
self._dict["binomial_p"] = _translate("Ensemble", "p")
self._dict["poisson_lam"] = _translate("Ensemble", "Lam")
self._dict["checker_failed_title"] = _translate(
"Ensemble",
"Invalid parameter"
)
self._dict["checker_failed_text"] = _translate(
"Ensemble",
"This parameter is not valid for selected distribution: "
)
uniform = _translate(
"Ensemble", "(High - Low) must be non-negative."
)
self._dict["uniform_low_tips"] = uniform
self._dict["uniform_high_tips"] = uniform
self._dict["normal_scale_tips"] = _translate(
"Ensemble", "Scale must be non-negative."
)
self._dict["binomial_n_tips"] = _translate(
"Ensemble", "N must be non-negative and must be an integers."
)
self._dict["binomial_p_tips"] = _translate(
"Ensemble", "P must be >= 0 and <=1."
)
self._dict["poisson_lam_tips"] = _translate(
"Ensemble", "Lam must be >= 0."
)
+320
View File
@@ -0,0 +1,320 @@
# Window.py -- Pamhyr
# Copyright (C) 2023-2026 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from tools import trace, timer, logger_exception
from View.Tools.PamhyrWindow import PamhyrWindow, PamhyrDialog
from View.Tools.PamhyrWidget import PamhyrWidget
from View.Tools.PamhyrTree import (
PamhyrModelItem, PamhyrModelListItem,
)
from PyQt5.QtGui import (
QKeySequence,
)
from PyQt5 import QtWidgets
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QRect, QSettings,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QGroupBox, QTreeWidget,
)
from View.Ensembles.Edit.Translate import EnsembleEditTranslate
logger = logging.getLogger()
class StricklersCoefModelItem(PamhyrModelItem):
def get_value(self):
return (self._data, self.parent()._data)
class StricklersModelItem(PamhyrModelItem):
def __init__(self, data=None, trad=None, parent=None):
super(StricklersModelItem, self).__init__(
data=data, trad=trad, parent=parent,
)
def _setup_children(self):
for coef in ["strickler_minor", "strickler_medium"]:
self.addChild(
StricklersCoefModelItem(data=coef, parent=self)
)
def get_value(self):
return self._data
class EnsembleEditDialog(PamhyrDialog):
_pamhyr_ui = "EnsembleEditDialog"
_pamhyr_name = "Parameters"
def __init__(self, study=None, ensemble=None,
parent=None):
trad = EnsembleEditTranslate()
super(EnsembleEditDialog, self).__init__(
title=trad[self._pamhyr_name],
trad=trad,
options=[],
parent=parent
)
self._study = study
self._ensemble = ensemble
self._target_data = None
self._tree = self.find(QTreeWidget, "treeWidget_target_data")
self._tg_group_box = self.find(QGroupBox, "groupBox_target_data")
self._init_tree()
self._init_default_values()
self._init_connection()
def _init_tree(self):
self._tree.setColumnCount(1)
self._tree.setHeaderLabels(["Name"])
stricklers = PamhyrModelListItem(
name="Stricklers",
lst=self._study.river.stricklers,
child_item_ctor=StricklersModelItem,
parent=self._tree
)
self._target_data = (
self._ensemble.data_type,
self._ensemble.target_data
)
self._tg_group_box.setTitle(
self._format_target_data(self._target_data)
)
item = stricklers.find_item_by_value(self._target_data)
if item:
self._tree.setCurrentItem(item)
def _init_default_values(self):
self.set_line_edit_text("lineEdit_name", self._ensemble.name)
self.combobox_add_items(
"comboBox_distribution",
[self._trad["not_defined"]] +
list(
map(
lambda d: d.name,
self._study._ens_distributions.lst
)
)
)
self._params_widget = None
if self._ensemble.distribution is not None:
self.set_combobox_text("comboBox_distribution",
self._ensemble.distribution.name)
self._set_widget(self._ensemble.distribution)
def _init_connection(self):
self.find(QComboBox, "comboBox_distribution")\
.currentTextChanged.connect(self.update_distribution)
self._tree.itemClicked.connect(self._tree_item_select)
def _tree_item_select(self, item):
if type(item) is StricklersCoefModelItem:
self._target_data = item.get_value()
self._tg_group_box.setTitle(
self._format_target_data(self._target_data)
)
def _format_target_data(self, data):
return f" Selection: {str(data[1])}, {data[0]}"
def update_distribution(self, txt):
# txt = self.get_combobox_text("comboBox_distribution")
distribution = next(
filter(
lambda d: d.name == txt,
self._study._ens_distributions.lst
),
None
)
self._set_widget(distribution)
def _set_widget(self, distribution):
box = self.find(QGroupBox, "groupBox_params")
vbox = self.find(QVBoxLayout, "verticalLayout_params")
if self._params_widget is not None:
vbox.removeWidget(self._params_widget)
self._params_widget.setParent(None)
if distribution is None:
return
size = len(distribution._labels)
if size == 1:
self._params_widget = Ensemble1ParamsWidget(
ensemble=self._ensemble,
distribution=distribution,
trad=self._trad, parent=box,
)
vbox.addWidget(self._params_widget)
elif size == 2:
self._params_widget = Ensemble2ParamsWidget(
ensemble=self._ensemble,
distribution=distribution,
trad=self._trad, parent=box,
)
vbox.addWidget(self._params_widget)
else:
self._params_widget = None
@property
def values(self):
return self._values
def accept(self):
txt = self.get_combobox_text("comboBox_distribution")
distribution = next(
filter(
lambda d: d.name == txt,
self._study._ens_distributions.lst
),
None
)
parameters = self._params_widget.values
if distribution is not None:
ok, tips = distribution.is_valid_parameters(parameters)
if not ok:
self.message_box(
text=self._trad["checker_failed_text"],
informative_text="\n".join(
map(lambda t: " - " + self._trad[t], tips)
),
window_title=self._trad["checker_failed_title"],
)
return
self._values = {
"name": self.get_line_edit_text("lineEdit_name"),
"data_type": self._target_data[0],
"target_data": self._target_data[1],
"distribution": distribution,
"params": parameters,
}
super().accept()
def reject(self):
self.close()
class Ensemble1ParamsWidget(PamhyrWidget):
_pamhyr_ui = "Ensemble1ParamsWidget"
_pamhyr_name = "Parameters"
def __init__(self, ensemble=None,
distribution=None,
trad=None, parent=None):
super(Ensemble1ParamsWidget, self).__init__(
parent=parent
)
self._trad = trad
self._ensemble = ensemble
self._distribution = distribution
self._labels = distribution.labels
self._init_default_labels()
self._init_default_values()
def _init_default_labels(self):
self.set_label_text("label_0", self._trad[self._labels[0]])
def _init_default_values(self):
self._values = self._ensemble.params
if len(self._values) != 1:
self._values = self._distribution.default_values.copy()
self.set_double_spin_box("doubleSpinBox_0", self._values[0])
@property
def values(self):
self._values = [
self.get_double_spin_box("doubleSpinBox_0"),
]
return self._values
class Ensemble2ParamsWidget(PamhyrWidget):
_pamhyr_ui = "Ensemble2ParamsWidget"
_pamhyr_name = "Parameters"
def __init__(self, ensemble=None,
distribution=None,
trad=None, parent=None):
super(Ensemble2ParamsWidget, self).__init__(
parent=parent
)
self._trad = trad
self._ensemble = ensemble
self._distribution = distribution
self._labels = distribution.labels
self._init_default_labels()
self._init_default_values()
def _init_default_labels(self):
self.set_label_text("label_0", self._trad[self._labels[0]])
self.set_label_text("label_1", self._trad[self._labels[1]])
def _init_default_values(self):
self._values = self._ensemble.params
if len(self._values) != 2:
self._values = self._distribution.default_values.copy()
self.set_double_spin_box("doubleSpinBox_0", self._values[0])
self.set_double_spin_box("doubleSpinBox_1", self._values[1])
@property
def values(self):
self._values = [
self.get_double_spin_box("doubleSpinBox_0"),
self.get_double_spin_box("doubleSpinBox_1")
]
return self._values
+9 -9
View File
@@ -36,7 +36,7 @@ from PyQt5.QtWidgets import (
from View.Ensembles.UndoCommand import ( from View.Ensembles.UndoCommand import (
SetNameCommand, SetTypeCommand, SetDataCommand, SetNameCommand, SetTypeCommand, SetDataCommand,
SetFunctionCommand, SetDistributionCommand,
AddCommand, DelCommand, AddCommand, DelCommand,
) )
@@ -96,13 +96,13 @@ class ComboBoxDelegate(QItemDelegate):
) )
) )
) )
elif self._mode == "function": elif self._mode == "distribution":
self.editor.addItems( self.editor.addItems(
[self._trad["not_defined"]] + [self._trad["not_defined"]] +
list( list(
map( map(
lambda f: f.name, lambda f: f.name,
self._study._ens_functions.lst self._study._ens_distributions.lst
) )
) )
) )
@@ -133,11 +133,11 @@ class ComboBoxDelegate(QItemDelegate):
), ),
None None
) )
elif self._mode == "function": elif self._mode == "distribution":
value = next( value = next(
filter( filter(
lambda f: f.name == text, lambda f: f.name == text,
self._study._ens_functions.lst self._study._ens_distributions.lst
), ),
None None
) )
@@ -182,8 +182,8 @@ class EnsembleTableModel(PamhyrTableModel):
if value is None: if value is None:
return self._trad["not_defined"] return self._trad["not_defined"]
return str(value) return str(value)
elif self._headers[column] == "function": elif self._headers[column] == "distribution":
value = self._lst.get(row).function value = self._lst.get(row).distribution
if value is None: if value is None:
return self._trad["not_defined"] return self._trad["not_defined"]
return value.name return value.name
@@ -222,9 +222,9 @@ class EnsembleTableModel(PamhyrTableModel):
self._lst, row, value self._lst, row, value
) )
) )
elif self._headers[column] == "function": elif self._headers[column] == "distribution":
self._undo.push( self._undo.push(
SetFunctionCommand( SetDistributionCommand(
self._lst, row, value self._lst, row, value
) )
) )
+2 -2
View File
@@ -1,4 +1,4 @@
# translate.py -- Pamhyr # Translate.py -- Pamhyr
# Copyright (C) 2023-2026 INRAE # Copyright (C) 2023-2026 INRAE
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
@@ -45,6 +45,6 @@ class EnsemblesTranslate(MainTranslate):
"name": self._dict["name"], "name": self._dict["name"],
"type": _translate("Ensembles", "Type"), "type": _translate("Ensembles", "Type"),
"target_data": _translate("Ensembles", "Target data"), "target_data": _translate("Ensembles", "Target data"),
"function": _translate("Ensembles", "Function"), "distribution": _translate("Ensembles", "Distribution"),
"parameters": _translate("Ensembles", "Parameters"), "parameters": _translate("Ensembles", "Parameters"),
} }
+53 -6
View File
@@ -77,20 +77,24 @@ class SetDataCommand(QUndoCommand):
self._ensembles.get(self._index).target_data = self._new self._ensembles.get(self._index).target_data = self._new
class SetFunctionCommand(QUndoCommand): class SetDistributionCommand(QUndoCommand):
def __init__(self, ensembles, index, function): def __init__(self, ensembles, index, distribution):
QUndoCommand.__init__(self) QUndoCommand.__init__(self)
self._ensembles = ensembles self._ensembles = ensembles
self._index = index self._index = index
self._old = self._ensembles.get(self._index).function self._old = self._ensembles.get(self._index).distribution
self._new = function self._old_params = self._ensembles.get(self._index).params
self._new = distribution
def undo(self): def undo(self):
self._ensembles.get(self._index).function = self._old self._ensembles.get(self._index).distribution = self._old
self._ensembles.get(self._index).params = self._old_params
def redo(self): def redo(self):
self._ensembles.get(self._index).function = self._new self._ensembles.get(self._index).distribution = self._new
if self._new is not None:
self._ensembles.get(self._index).params = self._new.default_values
class AddCommand(QUndoCommand): class AddCommand(QUndoCommand):
@@ -111,6 +115,34 @@ class AddCommand(QUndoCommand):
self._ensembles.insert(self._index, self._new) self._ensembles.insert(self._index, self._new)
class ImportCommand(QUndoCommand):
def __init__(self, ensembles, data, index):
QUndoCommand.__init__(self)
self._ensembles = ensembles
self._index = index
self._data = data
self._news = None
def undo(self):
for new in self._news:
new.set_as_deleted()
def redo(self):
if self._news is None:
self._news = list(
map(
lambda d: self._ensembles.new_with_data(
self._index, d
),
self._data,
)
)
else:
for new in self._news:
new.set_as_not_deleted()
class DelCommand(QUndoCommand): class DelCommand(QUndoCommand):
def __init__(self, ensembles, rows): def __init__(self, ensembles, rows):
QUndoCommand.__init__(self) QUndoCommand.__init__(self)
@@ -129,3 +161,18 @@ class DelCommand(QUndoCommand):
def redo(self): def redo(self):
self._ensembles.delete_i(self._rows) self._ensembles.delete_i(self._rows)
class SetAllCommand(QUndoCommand):
def __init__(self, ensemble, data):
QUndoCommand.__init__(self)
self._ensemble = ensemble
self._old = self._ensemble.get_data_dict()
self._new = data
def undo(self):
self._ensemble.set_data_dict(self._old)
def redo(self):
self._ensemble.set_data_dict(self._new)
+82 -36
View File
@@ -17,6 +17,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import json
from tools import trace, timer, logger_exception from tools import trace, timer, logger_exception
@@ -42,10 +43,12 @@ from PyQt5.QtWidgets import (
from Modules import Modules from Modules import Modules
from View.Ensembles.Edit.Window import EnsembleEditDialog
from View.Ensembles.Table import ( from View.Ensembles.Table import (
EnsembleTableModel, ComboBoxDelegate EnsembleTableModel, ComboBoxDelegate
) )
from View.Ensembles.Translate import EnsemblesTranslate from View.Ensembles.Translate import EnsemblesTranslate
from View.Ensembles.UndoCommand import SetAllCommand, ImportCommand
logger = logging.getLogger() logger = logging.getLogger()
@@ -94,10 +97,10 @@ class EnsemblesWindow(PamhyrWindow):
parent=self parent=self
) )
self._delegate_function = ComboBoxDelegate( self._delegate_distribution = ComboBoxDelegate(
data=self._ensembles, data=self._ensembles,
study=self._study, study=self._study,
mode="function", mode="distribution",
trad=self._trad, trad=self._trad,
parent=self parent=self
) )
@@ -105,7 +108,7 @@ class EnsemblesWindow(PamhyrWindow):
return { return {
"type": self._delegate_data_type, "type": self._delegate_data_type,
"target_data": self._delegate_stricklers, "target_data": self._delegate_stricklers,
"function": self._delegate_function, "distribution": self._delegate_distribution,
} }
def setup_table(self): def setup_table(self):
@@ -114,7 +117,7 @@ class EnsemblesWindow(PamhyrWindow):
delegates = self.setup_table_delegate() delegates = self.setup_table_delegate()
editable_headers = [ editable_headers = [
"name", "type", "function", "target_data" "name",
] ]
table = self.find(QTableView, f"tableView") table = self.find(QTableView, f"tableView")
@@ -138,6 +141,12 @@ class EnsemblesWindow(PamhyrWindow):
self.find(QAction, "action_add").triggered.connect(self.add) self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_del").triggered.connect(self.delete)
self.find(QAction, "action_edit").triggered.connect(self.edit) self.find(QAction, "action_edit").triggered.connect(self.edit)
self.find(QAction, "action_export").triggered.connect(
self.export_to_json
)
self.find(QAction, "action_import").triggered.connect(
self.import_from_json
)
table = self.find(QTableView, f"tableView") table = self.find(QTableView, f"tableView")
table.selectionModel()\ table.selectionModel()\
@@ -184,51 +193,88 @@ class EnsemblesWindow(PamhyrWindow):
for row in rows: for row in rows:
try: try:
dlg = MinMaxDialog( ens = self._ensembles.get(row)
ensemble=self._ensembles.get(row), dlg = EnsembleEditDialog(
trad=self._trad, study=self._study,
ensemble=ens,
parent=self parent=self
) )
if dlg.exec(): if dlg.exec():
self._ensembles.get(row).params = dlg._minmax self._undo_stack.push(
SetAllCommand(
ens, dlg.values
)
)
except Exception as e: except Exception as e:
logger_exception(e) logger_exception(e)
return return
def export_to_json(self):
ens = []
for e in self._ensembles.lst:
ens.append(e.get_data_dict(use_ids=True))
class MinMaxDialog(PamhyrDialog): data = {"ensembles": ens}
_pamhyr_ui = "MinMaxDialog"
_pamhyr_name = "Minmax"
def __init__(self, ensemble=None, def aux(filename):
trad=None, parent=None): with open(filename[0], 'w') as f:
super(MinMaxDialog, self).__init__( f.write(json.dumps(data))
title=trad[self._pamhyr_name],
trad=trad, self.file_dialog(
options=[], select_file=None,
parent=parent callback=aux,
default_suffix='.json',
file_filter=[self._trad["file_json"]]
) )
self._ensemble = ensemble def import_from_json(self):
def aux(filename):
with open(filename[0], 'r') as f:
return json.load(f)
self._init_default_values() data = self.file_dialog(
select_file=None,
callback=aux,
default_suffix='.json',
file_filter=[self._trad["file_json"]]
)
def _init_default_values(self): dtarget = dict(
self._minmax = self._ensemble.params map(
if len(self._minmax) > 0: lambda s: (s.pamhyr_id, s),
self.set_double_spin_box("doubleSpinBox_min", self._minmax[0]) self._study.river.stricklers.stricklers
self.set_double_spin_box("doubleSpinBox_max", self._minmax[1]) )
)
@property ddistr = dict(
def minmax(self): map(
return self._minmax lambda d: (d.name, d),
self._study._ens_distributions.lst
)
)
def accept(self): def update(d, values):
self._minmax = [ d.update(values)
self.get_double_spin_box("doubleSpinBox_min"), return d
self.get_double_spin_box("doubleSpinBox_max")
]
super().accept()
def reject(self): updated = list(
self.close() map(
lambda d: update(
d, {"target_data": dtarget[d["target_data"]]}
),
map(
lambda d: update(
d, {"distribution": ddistr[d["distribution"]]}
),
data["ensembles"]
)
)
)
self._undo_stack.push(
ImportCommand(
self._ensembles, updated, 0
)
)
self._table.update()
+5 -2
View File
@@ -1609,10 +1609,13 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
parent=self parent=self
) )
if dlg.exec(): if dlg.exec():
run_number = dlg._number function = dlg.function
number_of_run = dlg.number
seed = dlg.seed
sol = SolverLogEnsWindow( sol = SolverLogEnsWindow(
run_number=run_number, number_of_run=number_of_run,
seed=seed, function=function,
study=self._study, study=self._study,
config=self.conf, config=self.conf,
solver=solver, solver=solver,
+21 -9
View File
@@ -26,10 +26,13 @@ from View.Results.translate import ResultsTranslate
class CustomExportDialog(PamhyrDialog): class CustomExportDialog(PamhyrDialog):
_pamhyr_ui = "CustomExportAdisDialog" _pamhyr_ui = "CustomExportDialog"
_pamhyr_name = "Custom Plot Selection" _pamhyr_name = "Custom Export"
def __init__(self, parent=None): def __init__(self,
tag_x="values_x",
tag_y="values_y",
parent=None):
trad = ResultsTranslate() trad = ResultsTranslate()
super(CustomExportDialog, self).__init__( super(CustomExportDialog, self).__init__(
title=trad[self._pamhyr_name], title=trad[self._pamhyr_name],
@@ -38,8 +41,8 @@ class CustomExportDialog(PamhyrDialog):
parent=parent parent=parent
) )
self._available_values_x = self._trad.get_dict("values_x") self._available_values_x = self._trad.get_dict(tag_x)
self._available_values_y = self._trad.get_dict("values_y") self._available_values_y = self._trad.get_dict(tag_y)
self.setup_radio_buttons_x() self.setup_radio_buttons_x()
self.setup_radio_buttons_res() self.setup_radio_buttons_res()
@@ -66,8 +69,8 @@ class CustomExportDialog(PamhyrDialog):
def setup_radio_buttons_res(self): def setup_radio_buttons_res(self):
self._radio2 = [] self._radio2 = []
layout = self.find(QVBoxLayout, "verticalLayout_pol") layout = self.find(QVBoxLayout, "verticalLayout_solv")
label = self.find(QLabel, "label_3") label = self.find(QLabel, "label_solv")
label.setText(self._trad["solver"]) label.setText(self._trad["solver"])
for solver in self.parent._solvers: for solver in self.parent._solvers:
@@ -89,7 +92,7 @@ class CustomExportDialog(PamhyrDialog):
layout.addStretch() layout.addStretch()
def setup_label(self): def setup_label(self):
self._label = self.find(QLabel, "label_4") self._label = self.find(QLabel, "label_data")
self._label.setFrameStyle(QFrame.StyledPanel) self._label.setFrameStyle(QFrame.StyledPanel)
self._label.setStyleSheet('background-color: white') self._label.setStyleSheet('background-color: white')
self.set_label() self.set_label()
@@ -163,6 +166,15 @@ class CustomExportDialog(PamhyrDialog):
res = i res = i
break break
self.value = x, y, self._envelop.isChecked(), res self.value = (x, y, self._envelop.isChecked(), res)
super().accept() super().accept()
class CustomExportEnsembleDialog(CustomExportDialog):
def __init__(self, parent=None):
super(CustomExportEnsembleDialog, self).__init__(
tag_x="values_x_ens",
tag_y="values_y_ens",
parent=parent
)
+261
View File
@@ -0,0 +1,261 @@
# PlotHEnsemble.py -- Pamhyr
# Copyright (C) 2023-2026 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from functools import reduce
from datetime import datetime
from tools import timer, trace, logger_exception
from View.Tools.PamhyrPlot import PamhyrPlot
from PyQt5.QtCore import (
QCoreApplication
)
_translate = QCoreApplication.translate
logger = logging.getLogger()
class PlotHEnsemble(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None,
results=None, reach_id=0, profile_id=[0], res_id=[0],
parent=None):
super(PlotHEnsemble, self).__init__(
canvas=canvas,
trad=trad,
data=results,
toolbar=toolbar,
parent=parent
)
self._mode = "time"
self._parent = parent
self._current_reach_id = reach_id
self._current_profile_id = profile_id
self._current_res_id = res_id
self._timestamps = parent._timestamps
self._current_timestamp = max(self._timestamps)
self.label_x = self._trad["unit_time_s"]
self.label_y = self._trad["unit_discharge"]
self.label_discharge = _translate("Results", "Cross-section discharge")
self.label_discharge_max = _translate("Results", "Max discharge")
self.label_timestamp = _translate("Results", "Current timestamp")
self._isometric_axis = False
# self._auto_relim_update = False
# self._autoscale_update = False
self._line_max = []
self._line = []
self._add_data_lines = []
@property
def results(self):
return self.data
@results.setter
def results(self, results):
self.data = results
self._current_timestamp = max(self._timestamps)
@timer
def draw(self, highlight=None):
self.canvas.axes.cla()
self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5)
if self.results is None:
return
if len(self._current_res_id) < 1:
return
for res_id in self._current_res_id:
if self.results[res_id] is None:
continue
results = self.results[res_id]
reach = results.river.reach(self._current_reach_id)
if reach.geometry.number_profiles == 0:
self._init = False
return
self.draw_data(res_id)
self.draw_data_std_derivation(res_id)
self.canvas.axes.set_xlabel(
self._trad["unit_time_s"],
color='black', fontsize=10
)
self.canvas.axes.set_ylabel(
self._trad["unit_discharge"],
color='black', fontsize=10
)
self.set_ticks_time_formater()
self.draw_additional_data(0)
self.enable_legend()
self.canvas.draw_idle()
self.update_idle()
self.draw_current()
self._init = True
@timer
def draw_data(self, res_id):
results = self.results[res_id]
reach = results.river.reach(self._current_reach_id)
colors = [
("q_min", self.color_plot_ensemble_min),
("q_mean", self.color_plot_ensemble_mean),
("q_max", self.color_plot_ensemble_max),
]
for t, c in colors:
q = results.get("table")[t]
profile = reach.profile(self._current_profile_id[0])
x = self._timestamps
y = q[:, profile.global_index]
# y = profile.get_key("Q")
if res_id == 2:
label = f"Δ {self.label_discharge} {t} {profile.name}"
else:
label = f"{self.label_discharge} {t} {profile.name}"
line, = self.canvas.axes.plot(
x, y,
label=label,
color=c,
# linestyle=self.linestyle[i % len(self.linestyle)],
**self.plot_default_kargs
)
self._line.append(line)
@timer
def draw_data_std_derivation(self, res_id):
results = self.results[res_id]
reach = results.river.reach(self._current_reach_id)
q = results.get("table")["q_mean"]
std_deriv = results.get("table")["q_std_derivation"]
profile = reach.profile(self._current_profile_id[0])
x = self._timestamps
y = q[:, profile.global_index]
std = std_deriv[:, profile.global_index]
y_b = y - std
y_t = y + std
plot = self.canvas.axes.fill_between(
x, y_b, y_t,
label=f"Standard derivation {profile.name}",
color=self.color_plot_ensemble_std_deriv,
alpha=0.3,
)
def draw_additional_data(self, res_id):
results = self.results[res_id]
self._add_data_lines = []
for data in results.get("additional_data"):
data = data._data
x, y = data['x'], data['y']
legend = data['legend']
unit = data['unit']
if data['type_x'] == 'time' and data['type_y'] == 'discharge':
line, = self.canvas.axes.plot(
x, y, marker="+",
label=legend + ' ' + unit
)
self._add_data_lines.append(line)
self.enable_legend()
self.canvas.draw_idle()
def draw_current(self):
self._current, = self.canvas.axes.plot(
[self._current_timestamp, self._current_timestamp],
self.canvas.axes.get_ylim(),
# label=self.label_timestamp,
color="grey",
linestyle="dashed",
lw=1.,
)
def set_reach(self, reach_id):
self._current_reach_id = reach_id
self._current_profile_id = [0]
self.draw()
def set_profile(self, profile_id):
self._current_profile_id = profile_id
# self.update()
self.draw()
def set_result(self, res_id):
self._current_res_id = res_id
self.draw()
def set_timestamp(self, timestamp):
self._current_timestamp = timestamp
self.update_current()
self.update_idle()
def update(self):
if not self._init:
self.draw()
self.update_current()
self.update_idle()
def update_additional_data(self):
self.draw_additional_data(0)
self.update_idle()
def update_all(self):
self._current_reach_id = self._parent._get_current_reach()
self._current_profile_id = self._parent._get_current_profiles_list()
self._current_res_id = self._parent._get_current_results()
self._current_timestamp = self._parent._get_current_timestamp()
self._init = False
self.update()
def update_current(self):
y = self._current.get_ydata()
self._current.set_data(
[self._current_timestamp, self._current_timestamp],
y
)
+6 -5
View File
@@ -110,7 +110,7 @@ class PlotRKC(PamhyrPlot):
ts = results.get_timestamp_id(self._current_timestamp) ts = results.get_timestamp_id(self._current_timestamp)
gi_0, gi_1 = reach.global_index gi_0, gi_1 = reach.global_index
water_z = table[ts, gi_0 : gi_1 + 1] zfd = table[ts, gi_0:gi_1 + 1]
self.line_bottom, = self.canvas.axes.plot( self.line_bottom, = self.canvas.axes.plot(
rk, zfd, rk, zfd,
@@ -175,7 +175,7 @@ class PlotRKC(PamhyrPlot):
ts = result.get_timestamp_id(self._current_timestamp) ts = result.get_timestamp_id(self._current_timestamp)
gi_0, gi_1 = reach.global_index gi_0, gi_1 = reach.global_index
water_z = table[ts, gi_0 : gi_1 + 1] water_z = table[ts, gi_0:gi_1 + 1]
self.water = self.canvas.axes.plot( self.water = self.canvas.axes.plot(
rk, water_z, rk, water_z,
@@ -200,7 +200,7 @@ class PlotRKC(PamhyrPlot):
ts = result.get_timestamp_id(self._current_timestamp) ts = result.get_timestamp_id(self._current_timestamp)
gi_0, gi_1 = reach.global_index gi_0, gi_1 = reach.global_index
water_z = table[ts, gi_0 : gi_1 + 1] water_z = table[ts, gi_0:gi_1 + 1]
self.canvas.axes.plot( self.canvas.axes.plot(
rk, water_z, lw=1., rk, water_z, lw=1.,
@@ -337,7 +337,7 @@ class PlotRKC(PamhyrPlot):
ts = result.get_timestamp_id(self._current_timestamp) ts = result.get_timestamp_id(self._current_timestamp)
gi_0, gi_1 = reach.global_index gi_0, gi_1 = reach.global_index
water_z = table[ts, gi_0 : gi_1 + 1] water_z = table[ts, gi_0:gi_1 + 1]
self.water[0].set_data( self.water[0].set_data(
rk, water_z rk, water_z
@@ -378,7 +378,8 @@ class PlotRKC(PamhyrPlot):
table = results.get("table")["zfd"] table = results.get("table")["zfd"]
ts = results.get_timestamp_id(self._current_timestamp) ts = results.get_timestamp_id(self._current_timestamp)
zfd = table[ts, reach.global_index] gi_0, gi_1 = reach.global_index
zfd = table[ts, gi_0:gi_1 + 1]
self.line_bottom.remove() self.line_bottom.remove()
+90 -27
View File
@@ -33,7 +33,7 @@ logger = logging.getLogger()
class PlotRKCEnsemble(PamhyrPlot): class PlotRKCEnsemble(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None, def __init__(self, canvas=None, trad=None, toolbar=None,
results=None, reach_id=0, profile_id=0, run_id=0, results=None, reach_id=0, profile_id=0,
parent=None): parent=None):
super(PlotRKCEnsemble, self).__init__( super(PlotRKCEnsemble, self).__init__(
canvas=canvas, canvas=canvas,
@@ -46,7 +46,6 @@ class PlotRKCEnsemble(PamhyrPlot):
self._parent = parent self._parent = parent
self._current_reach_id = reach_id self._current_reach_id = reach_id
self._current_profile_id = profile_id self._current_profile_id = profile_id
self._current_run_id = run_id[0]
self._timestamps = parent._timestamps self._timestamps = parent._timestamps
self._current_timestamp = max(self._timestamps) self._current_timestamp = max(self._timestamps)
@@ -59,6 +58,8 @@ class PlotRKCEnsemble(PamhyrPlot):
self.label_water_min = self._trad["label_water_min"] self.label_water_min = self._trad["label_water_min"]
self.label_water_max = self._trad["label_water_max"] self.label_water_max = self._trad["label_water_max"]
self.water = []
self._isometric_axis = False self._isometric_axis = False
self._auto_relim_update = True self._auto_relim_update = True
@@ -80,13 +81,14 @@ class PlotRKCEnsemble(PamhyrPlot):
if self.results is None: if self.results is None:
return return
if self.results[self._current_run_id] is None: if self.results[0] is None:
return return
results = self.results[self._current_run_id] results = self.results[0]
reach = results.river.reach(self._current_reach_id) reach = results.river.reach(self._current_reach_id)
self.draw_bottom(reach) self.draw_bottom(reach)
self.draw_std_derivation(reach)
self.draw_water_elevation(reach) self.draw_water_elevation(reach)
self.draw_current(reach) self.draw_current(reach)
self.draw_profiles_hs(reach) self.draw_profiles_hs(reach)
@@ -100,7 +102,7 @@ class PlotRKCEnsemble(PamhyrPlot):
self.draw_bottom_geometry(reach) self.draw_bottom_geometry(reach)
def draw_profiles_hs(self, reach): def draw_profiles_hs(self, reach):
results = self.results[self._current_run_id] results = self.results[0]
lhs = filter( lhs = filter(
lambda hs: hs._input_reach.reach is reach.geometry, lambda hs: hs._input_reach.reach is reach.geometry,
filter( filter(
@@ -146,13 +148,14 @@ class PlotRKCEnsemble(PamhyrPlot):
def draw_water_elevation(self, reach): def draw_water_elevation(self, reach):
if len(reach.geometry.profiles) != 0: if len(reach.geometry.profiles) != 0:
result = self.results[self._current_run_id] result = self.results[0]
rk = reach.geometry.get_rk() rk = reach.geometry.get_rk()
z_min = reach.geometry.get_z_min()
self.water = []
colors = [ colors = [
("z_min", self.color_plot_ensemble_min), ("z_min", self.color_plot_ensemble_min),
("Z", self.color_plot_ensemble_mean), ("z_mean", self.color_plot_ensemble_mean),
("z_max", self.color_plot_ensemble_max), ("z_max", self.color_plot_ensemble_max),
] ]
@@ -161,17 +164,47 @@ class PlotRKCEnsemble(PamhyrPlot):
ts = result.get_timestamp_id(self._current_timestamp) ts = result.get_timestamp_id(self._current_timestamp)
gi_0, gi_1 = reach.global_index gi_0, gi_1 = reach.global_index
water_z = table[ts, gi_0 : gi_1 + 1] water_z = table[ts, gi_0:gi_1 + 1]
self.water = self.canvas.axes.plot( plot, = self.canvas.axes.plot(
rk, water_z, rk, water_z,
lw=1., color=color, lw=1., color=color,
linestyle='dotted',
label=f"Elevation: {t} (m)", label=f"Elevation: {t} (m)",
) )
def draw_additional_data(self, run_id): self.water.append(plot)
results = self.results[run_id]
def draw_std_derivation(self, reach):
if len(reach.geometry.profiles) != 0:
result = self.results[0]
rk = reach.geometry.get_rk()
z_min = reach.geometry.get_z_min()
color = self.color_plot_ensemble_std_deriv
table = result.get("table")["z_mean"]
std_deriv = result.get("table")["z_std_derivation"]
ts = result.get_timestamp_id(self._current_timestamp)
gi_0, gi_1 = reach.global_index
water_z = table[ts, gi_0:gi_1 + 1]
std = std_deriv[ts, gi_0:gi_1 + 1]
water_z_b = water_z - std
water_z_t = water_z + std
plot = self.canvas.axes.fill_between(
rk, water_z_b, water_z_t,
lw=1., color=color, alpha=0.3,
label=f"Standard derivation",
)
self.water_std_deriv = plot
def draw_additional_data(self):
results = self.results[0]
self._add_data_lines = [] self._add_data_lines = []
@@ -231,10 +264,6 @@ class PlotRKCEnsemble(PamhyrPlot):
self._current_profile_id = profile_id self._current_profile_id = profile_id
self.update_current() self.update_current()
def set_result(self, run_id):
self._current_run_id = run_id[0]
self.update()
def set_timestamp(self, timestamp): def set_timestamp(self, timestamp):
self._current_timestamp = timestamp self._current_timestamp = timestamp
self.update() self.update()
@@ -243,42 +272,74 @@ class PlotRKCEnsemble(PamhyrPlot):
if not self._init: if not self._init:
self.draw() self.draw()
results = self.results[self._current_run_id] results = self.results[0]
reach = results.river.reach(self._current_reach_id) reach = results.river.reach(self._current_reach_id)
self.update_water_elevation() self.update_water_elevation()
self.update_std_derivation()
self.update_idle() self.update_idle()
def update_all(self): def update_all(self):
self._current_reach_id = self._parent._get_current_reach() self._current_reach_id = self._parent._get_current_reach()
self._current_profile_id = self._parent._get_current_profile() self._current_profile_id = self._parent._get_current_profile()
self._current_run_id = self._parent._get_current_results()[0]
self._current_timestamp = self._parent._get_current_timestamp() self._current_timestamp = self._parent._get_current_timestamp()
self._init = False self._init = False
self.update() self.update()
def update_water_elevation(self): def update_water_elevation(self):
result = self.results[self._current_run_id] result = self.results[0]
reach = result.river.reach(self._current_reach_id) reach = result.river.reach(self._current_reach_id)
rk = reach.geometry.get_rk() rk = reach.geometry.get_rk()
z_min = reach.geometry.get_z_min() z_min = reach.geometry.get_z_min()
table = result.get("table")["Z"] table = result.get("table")["z_mean"]
ts = result.get_timestamp_id(self._current_timestamp) ts = result.get_timestamp_id(self._current_timestamp)
gi_0, gi_1 = reach.global_index gi_0, gi_1 = reach.global_index
water_z = table[ts, gi_0 : gi_1 + 1] water_z = table[ts, gi_0:gi_1 + 1]
self.water[0].set_data( colors = [
rk, water_z (0, "z_min", self.color_plot_ensemble_min),
) (1, "z_mean", self.color_plot_ensemble_mean),
(2, "z_max", self.color_plot_ensemble_max),
]
for i, t, color in colors:
table = result.get("table")[t]
ts = result.get_timestamp_id(self._current_timestamp)
gi_0, gi_1 = reach.global_index
water_z = table[ts, gi_0:gi_1 + 1]
self.water[i].set_data(rk, water_z)
def update_std_derivation(self):
result = self.results[0]
reach = result.river.reach(self._current_reach_id)
rk = reach.geometry.get_rk()
table = result.get("table")["z_mean"]
std_deriv = result.get("table")["z_std_derivation"]
ts = result.get_timestamp_id(self._current_timestamp)
gi_0, gi_1 = reach.global_index
water_z = table[ts, gi_0:gi_1 + 1]
std = std_deriv[ts, gi_0:gi_1 + 1]
water_z_b = water_z - std
water_z_t = water_z + std
self.water_std_deriv.set_data(rk, water_z_b, water_z_t)
def update_additional_data(self): def update_additional_data(self):
self.draw_additional_data(0) self.draw_additional_data()
self.update_idle() self.update_idle()
def update_current(self): def update_current(self):
results = self.results[self._current_run_id] results = self.results[0]
reach = results.river.reach(self._current_reach_id) reach = results.river.reach(self._current_reach_id)
rk = reach.geometry.get_rk() rk = reach.geometry.get_rk()
z_min = reach.geometry.get_z_min() z_min = reach.geometry.get_z_min()
@@ -289,4 +350,6 @@ class PlotRKCEnsemble(PamhyrPlot):
[rk[cid], rk[cid]], [rk[cid], rk[cid]],
[z_max[cid], z_min[cid]] [z_max[cid], z_min[cid]]
) )
self.canvas.figure.canvas.draw_idle() self.canvas.figure.canvas.draw_idle()
self.update_idle()
+1 -1
View File
@@ -97,7 +97,7 @@ class TableModel(PamhyrTableModel):
return str(v) return str(v)
elif self._opt_data == "runs": elif self._opt_data == "runs":
if self._headers[column] == "runs": if self._headers[column] == "runs":
p = self._lst[row,:] p = self._lst[row, :]
return f"{row}: {p}" return f"{row}: {p}"
elif self._opt_data == "raw_data": elif self._opt_data == "raw_data":
table = self._data[0].get("table") table = self._data[0].get("table")
+477 -419
View File
File diff suppressed because it is too large. Load diff
+20 -1
View File
@@ -130,6 +130,25 @@ class ResultsTranslate(MainTranslate):
"wet_area": self._dict["unit_wet_area"], "wet_area": self._dict["unit_wet_area"],
} }
self._sub_dict["values_x_ens"] = {
"rk": self._dict["unit_rk"],
"time": self._dict["unit_time_s"],
}
self._sub_dict["values_y_ens"] = {
"z_mean": self._dict["unit_mean_water_elevation"],
"q_mean": self._dict["unit_mean_discharge"],
"z_standard_derivation": self._dict["unit_std_deriv_elevation"],
"q_standard_derivation": self._dict["unit_std_deriv_discharge"],
}
self._sub_dict["values_y_envelop_ens"] = {
"z_min": self._dict["unit_min_water_elevation"],
"z_max": self._dict["unit_max_water_elevation"],
"q_min": self._dict["unit_min_discharge"],
"q_max": self._dict["unit_max_discharge"],
}
if self.pollutants is not None: if self.pollutants is not None:
self._sub_dict["table_headers_raw_data"] = { self._sub_dict["table_headers_raw_data"] = {
"name": _translate("Results", "Profile"), "name": _translate("Results", "Profile"),
@@ -157,7 +176,7 @@ class ResultsTranslate(MainTranslate):
"max_velocity": self._dict["unit_max_velocity"], "max_velocity": self._dict["unit_max_velocity"],
"min_depth": self._dict["unit_min_depth"], "min_depth": self._dict["unit_min_depth"],
"max_depth": self._dict["unit_max_depth"], "max_depth": self._dict["unit_max_depth"],
} }
self._sub_dict["values_y_pol"] = { self._sub_dict["values_y_pol"] = {
"unit_C": self._dict["unit_concentration"], "unit_C": self._dict["unit_concentration"],
+50 -28
View File
@@ -42,7 +42,7 @@ from PyQt5.QtWidgets import (
QFileDialog, QTableView, QAbstractItemView, QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate, QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget, QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QTextEdit, QProgressBar, QTextEdit, QProgressBar, QCheckBox,
) )
from View.RunSolver.Log.Window import SolverLogFileWindow from View.RunSolver.Log.Window import SolverLogFileWindow
@@ -71,18 +71,45 @@ class EnsembleRunNumberDialog(PamhyrDialog):
parent=parent parent=parent
) )
self._function = "Random"
self._number = 100
self._seed = -1
self._init_default_values() self._init_default_values()
self._init_connection()
def _init_default_values(self): def _init_default_values(self):
self.combobox_add_items("comboBox", ["Random"])
self.set_combobox_text("comboBox", "Random")
self.set_spin_box("spinBox", 100) self.set_spin_box("spinBox", 100)
def _init_connection(self):
self._checkbox = self.find(QCheckBox, f"checkBox")
self._checkbox.clicked.connect(self._set_random_seed_spin_box_state)
def _set_random_seed_spin_box_state(self):
self.set_spin_box_enable("spinBox_seed", self._checkbox.isChecked())
@property @property
def number(self): def number(self):
return self._number return self._number
@property
def seed(self):
return self._seed
@property
def function(self):
return self._function
def accept(self): def accept(self):
self._function = self.get_combobox_text("comboBox")
self._number = self.get_spin_box("spinBox") self._number = self.get_spin_box("spinBox")
if self._checkbox.isChecked():
self._seed = self.get_spin_box("spinBox_seed")
super().accept() super().accept()
def reject(self): def reject(self):
@@ -463,18 +490,29 @@ class SolverLogEnsWindow(SolverLogWindow):
_pamhyr_ui = "SolverLogEnsemble" _pamhyr_ui = "SolverLogEnsemble"
_pamhyr_name = "Solver Log Ensemble" _pamhyr_name = "Solver Log Ensemble"
def __init__(self, run_number=100, def __init__(self, number_of_run=100,
seed=-1, function="Random",
study=None, config=None, study=None, config=None,
solver=None, parent=None): solver=None, parent=None):
self._run_number = run_number self._number_of_run = number_of_run
self._last_run = 0 self._last_run = 0
if seed != -1:
np.random.seed(seed)
self._seed = np.random.get_state()[1][0]
logger.info(
"solver: Set ensemble with " +
f"{number_of_run} {function} and " +
f"{self._seed} as random seed"
)
self._samples = {} self._samples = {}
for ens in study.river._ensembles.lst: for ens in study.river._ensembles.lst:
if ens.function is None: if ens.distribution is None:
continue continue
self._samples[ens] = ens.get_sample(self._run_number) self._samples[ens] = ens.random(self._number_of_run)
self._results = None self._results = None
self._run_samples = [] self._run_samples = []
@@ -494,7 +532,7 @@ class SolverLogEnsWindow(SolverLogWindow):
def setup_progress_bar(self): def setup_progress_bar(self):
self._progress_bar = self.find(QProgressBar, "progressBar") self._progress_bar = self.find(QProgressBar, "progressBar")
self._progress_bar.setRange(0, self._run_number) self._progress_bar.setRange(0, self._number_of_run)
self._progress_bar.setValue(0) self._progress_bar.setValue(0)
def _log_int(self, int_code, color=None): def _log_int(self, int_code, color=None):
@@ -521,7 +559,7 @@ class SolverLogEnsWindow(SolverLogWindow):
qlog=self._output, qlog=self._output,
) )
if self._last_run < self._run_number: if self._last_run < self._number_of_run:
self.next() self.next()
else: else:
self._results = self.fill_results_from_tables() self._results = self.fill_results_from_tables()
@@ -537,32 +575,16 @@ class SolverLogEnsWindow(SolverLogWindow):
table = {} table = {}
table["Z"] = results.new_table_data("Z", tables["z_mean"])
table["Q"] = results.new_table_data("Q", tables["q_mean"])
table["z_min"] = results.new_table_data("z_min", tables["z_min"])
table["q_min"] = results.new_table_data("q_min", tables["q_min"])
table["z_max"] = results.new_table_data("z_max", tables["z_max"])
table["q_max"] = results.new_table_data("q_max", tables["q_max"])
for key in ["z", "q"]: for key in ["z", "q"]:
for func in ["min", "mean", "max"]: for func in ["min", "mean", "max", "std_derivation"]:
name = f"res_{key}_{func}" name = f"{key}_{func}"
table[name] = results.new_table_data( table[name] = results.new_table_data(name, tables[name])
name,
np.array(
list(map(lambda e: e[1], tables[name]))
)
)
for nb, data in tables["traces"]:
name = f"{nb}_"
table[name + "z"] = results.new_table_data(name + "z", data["Z"])
table[name + "q"] = results.new_table_data(name + "q", data["Q"])
table["samples"] = results.new_table_data( table["samples"] = results.new_table_data(
"samples", np.array(self._run_samples) "samples", np.array(self._run_samples)
) )
results.set("number_of_run", self._number_of_run)
results.set("seed", self._seed) # Keep random seed
results.set("table", table) results.set("table", table)
+15 -16
View File
@@ -47,11 +47,19 @@ class AbstractMenu(object):
class DefaultMenu(AbstractMenu): class DefaultMenu(AbstractMenu):
def run(self): def run(self):
current_scenario = self._graph.status.scenario
if current_scenario.is_ensemble():
return
add_node = self._menu.addAction(self._trad["menu_add_scenario"]) add_node = self._menu.addAction(self._trad["menu_add_scenario"])
ensemble = self._menu.addAction(self._trad["menu_ens_scenario"])
action = self._exec() action = self._exec()
if action == add_node: if action == add_node:
self._parent.new_scenario(self._pos) self._parent.new_scenario(self._pos)
if action == ensemble:
self._parent.create_new_ensemble_scenario(self._pos)
class ScenarioMenu(AbstractMenu): class ScenarioMenu(AbstractMenu):
@@ -59,17 +67,16 @@ class ScenarioMenu(AbstractMenu):
item = self._items[0] item = self._items[0]
scenarios = item.graph._study.scenarios scenarios = item.graph._study.scenarios
if len(scenarios) == 1:
return
current_scenario = item.graph._study.status.scenario.id current_scenario = item.graph._study.status.scenario.id
select = self._menu.addAction(self._trad["menu_select_scenario"]) select = None
duplicate = None duplicate = None
delete = None delete = None
ensemble = None
edit_ens = None edit_ens = None
if len(scenarios) > 1:
select = self._menu.addAction(self._trad["menu_select_scenario"])
if item.scenario.id != 0: if item.scenario.id != 0:
if scenarios.is_leaf(item.scenario): if scenarios.is_leaf(item.scenario):
delete = self._menu.addAction(self._trad["menu_del_scenario"]) delete = self._menu.addAction(self._trad["menu_del_scenario"])
@@ -79,17 +86,11 @@ class ScenarioMenu(AbstractMenu):
self._trad["menu_dup_scenario"] self._trad["menu_dup_scenario"]
) )
if not item.scenario.is_ensemble(): if item.scenario.is_ensemble():
if item.scenario.id == current_scenario: edit_ens = self._menu.addAction(
ensemble = self._menu.addAction( self._trad["menu_edit_ens_scenario"]
self._trad["menu_ens_scenario"]
) )
if item.scenario.is_ensemble():
edit_ens = self._menu.addAction(
self._trad["menu_edit_ens_scenario"]
)
action = self._exec() action = self._exec()
if action is None: if action is None:
return return
@@ -99,7 +100,5 @@ class ScenarioMenu(AbstractMenu):
self._parent.delete_scenario(item) self._parent.delete_scenario(item)
elif action == duplicate: elif action == duplicate:
self._parent.duplicate_scenario(item) self._parent.duplicate_scenario(item)
elif action == ensemble:
self._parent.create_ensemble_scenario(item)
elif action == edit_ens: elif action == edit_ens:
self._parent.edit_ensemble_scenario(item) self._parent.edit_ensemble_scenario(item)
+20
View File
@@ -550,6 +550,26 @@ class GraphWidget(QGraphicsView):
self.exec_with_waiting_window(fn, "create_ensemble_scenario") self.exec_with_waiting_window(fn, "create_ensemble_scenario")
self.changeScenario.emit(self.sender()) self.changeScenario.emit(self.sender())
def create_new_ensemble_scenario(self, pos):
must_save = self.dialog_save()
if must_save == "Cancel":
return
def fn():
self._close_other_window()
if must_save == "Save":
self._study.save()
self._undo.push(
CreateEnsembleScenarioCommand(
self._study, pos=pos,
)
)
self.exec_with_waiting_window(fn, "create_ensemble_scenario")
self.changeScenario.emit(self.sender())
def edit_ensemble_scenario(self, item): def edit_ensemble_scenario(self, item):
self.parent\ self.parent\
.parent\ .parent\
+3 -2
View File
@@ -99,11 +99,12 @@ class DuplicateScenariosCommand(QUndoCommand):
class CreateEnsembleScenarioCommand(QUndoCommand): class CreateEnsembleScenarioCommand(QUndoCommand):
def __init__(self, study): def __init__(self, study, pos=None):
QUndoCommand.__init__(self) QUndoCommand.__init__(self)
self._study = study self._study = study
self._new = None self._new = None
self._pos = pos
def undo(self): def undo(self):
self._study.scenarios.delete(self._new.id) self._study.scenarios.delete(self._new.id)
@@ -111,7 +112,7 @@ class CreateEnsembleScenarioCommand(QUndoCommand):
def redo(self): def redo(self):
if self._new is None: if self._new is None:
self._new = self._study.create_ensemble_scenario() self._new = self._study.create_ensemble_scenario(self._pos)
else: else:
self._new.set_as_not_deleted() self._new.set_as_not_deleted()
self._study.reload_from_scenario(self._new) self._study.reload_from_scenario(self._new)
+1 -1
View File
@@ -44,7 +44,7 @@ class ScenariosTranslate(MainTranslate):
"Scenarios", "Duplicate this scenario" "Scenarios", "Duplicate this scenario"
) )
self._dict["menu_ens_scenario"] = _translate( self._dict["menu_ens_scenario"] = _translate(
"Scenarios", "Create ensemble scenario from this scenario" "Scenarios", "Create ensemble scenario from current scenario"
) )
self._dict["menu_edit_ens_scenario"] = _translate( self._dict["menu_edit_ens_scenario"] = _translate(
"Scenarios", "Edit this ensemble scenario" "Scenarios", "Edit this ensemble scenario"
+13 -1
View File
@@ -107,7 +107,7 @@ class WindowToolKit(object):
dialog = QFileDialog(self, options=options) dialog = QFileDialog(self, options=options)
if select_file == "Existing_file": if select_file == "ExistingFile":
mode = QFileDialog.FileMode.ExistingFile mode = QFileDialog.FileMode.ExistingFile
elif select_file == "Directory": elif select_file == "Directory":
mode = QFileDialog.FileMode.Directory mode = QFileDialog.FileMode.Directory
@@ -390,6 +390,18 @@ class ASubWindowFeatures(object):
""" """
return self.find(QSpinBox, name).value() return self.find(QSpinBox, name).value()
def set_spin_box_enable(self, name: str, value: bool):
"""Set status of spinbox component
Args:
name: The spinbox component name
value: The boolean value
Returns:
Nothing
"""
self.find(QSpinBox, name).setEnabled(value)
def set_double_spin_box(self, name: str, value: int): def set_double_spin_box(self, name: str, value: int):
"""Set value of spinbox component """Set value of spinbox component
+1
View File
@@ -46,6 +46,7 @@ class PamhyrPlot(APlot):
color_plot_ensemble_min = "green" color_plot_ensemble_min = "green"
color_plot_ensemble_mean = "black" color_plot_ensemble_mean = "black"
color_plot_ensemble_max = "red" color_plot_ensemble_max = "red"
color_plot_ensemble_std_deriv = "black"
colors = list(mplcolors.TABLEAU_COLORS) colors = list(mplcolors.TABLEAU_COLORS)
linestyle = ['solid', 'dashed', 'dashdot', 'dotted'] linestyle = ['solid', 'dashed', 'dashdot', 'dotted']
+113
View File
@@ -0,0 +1,113 @@
# PamhyrTree.py -- Pamhyr abstract table model
# Copyright (C) 2023-2026 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
import traceback
from tools import trace, timer
from Model.Except import NotImplementedMethodeError
from PyQt5.QtCore import (
Qt, QVariant, QAbstractItemModel,
QCoreApplication, QModelIndex, pyqtSlot,
)
from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem,
)
logger = logging.getLogger()
class PamhyrModelItem(QTreeWidgetItem):
def __init__(self, data=None, trad=None, parent=None):
self._data = data
self._name = self._get_name()
super(PamhyrModelItem, self).__init__(
parent, [self._name],
)
self._setup_children()
def _get_name(self):
return str(self._data)
def _setup_children(self):
return
def get_value(self):
return self._data
def find_item_by_value(self, data):
if self.get_value() == data:
return self
for i in range(self.childCount()):
item = self.child(i)
if item.get_value() == data:
return item
found = item.find_item_by_value(data)
if found:
return found
return None
class PamhyrModelListItem(QTreeWidgetItem):
def __init__(self, name=None, lst=None,
child_item_ctor=PamhyrModelItem,
trad=None, parent=None):
super(PamhyrModelListItem, self).__init__(
parent, [name],
)
self._name = name
self._lst = lst
self._trad = trad
self._child_item_ctor = child_item_ctor
self._setup_children()
def _setup_children(self):
for el in self._lst.lst:
self.addChild(
self._child_item_ctor(
data=el, trad=self._trad, parent=self
)
)
def get_value(self):
return None
def find_item_by_value(self, data):
if self.get_value() == data:
return self
for i in range(self.childCount()):
item = self.child(i)
if item.get_value() == data:
return item
found = item.find_item_by_value(data)
if found:
return found
return None
+25 -2
View File
@@ -40,6 +40,12 @@ class CommonWordTranslate(PamhyrTranslate):
self._dict["time"] = _translate("CommonWord", "Time") self._dict["time"] = _translate("CommonWord", "Time")
self._dict["date"] = _translate("CommonWord", "Date") self._dict["date"] = _translate("CommonWord", "Date")
self._dict["low"] = _translate("CommonWord", "Low")
self._dict["high"] = _translate("CommonWord", "High")
self._dict["loc"] = _translate("CommonWord", "Loc")
self._dict["scale"] = _translate("CommonWord", "Scale")
self._dict["reach"] = _translate("CommonWord", "Reach") self._dict["reach"] = _translate("CommonWord", "Reach")
self._dict["reaches"] = _translate("CommonWord", "Reaches") self._dict["reaches"] = _translate("CommonWord", "Reaches")
self._dict["Select reach"] = _translate("CommonWord", "Select reach") self._dict["Select reach"] = _translate("CommonWord", "Select reach")
@@ -65,6 +71,9 @@ class CommonWordTranslate(PamhyrTranslate):
self._dict["file_csv"] = _translate( self._dict["file_csv"] = _translate(
"CommonWord", "CSV file (*.csv)" "CommonWord", "CSV file (*.csv)"
) )
self._dict["file_json"] = _translate(
"CommonWord", "JSON file (*.json)"
)
class UnitTranslate(CommonWordTranslate): class UnitTranslate(CommonWordTranslate):
@@ -103,6 +112,12 @@ class UnitTranslate(CommonWordTranslate):
self._dict["unit_water_elevation"] = _translate( self._dict["unit_water_elevation"] = _translate(
"Unit", "Water Elevation (m)" "Unit", "Water Elevation (m)"
) )
self._dict["unit_mean_water_elevation"] = _translate(
"Unit", "Mean water elevation (m)"
)
self._dict["unit_std_deriv_elevation"] = _translate(
"Unit", "Elevation standard derivation (m)"
)
self._dict["unit_water_elevation_envelop"] = _translate( self._dict["unit_water_elevation_envelop"] = _translate(
"Unit", "Water Elevation Envelop (m)" "Unit", "Water Elevation Envelop (m)"
) )
@@ -122,8 +137,16 @@ class UnitTranslate(CommonWordTranslate):
self._dict["unit_min_velocity"] = _translate( self._dict["unit_min_velocity"] = _translate(
"Unit", "Min Velocity (m/s)" "Unit", "Min Velocity (m/s)"
) )
self._dict["unit_discharge"] = _translate( self._dict["unit_discharge"] = (
"Unit", "Discharge") + f" (m³/s)" _translate("Unit", "Discharge") + f" (m³/s)"
)
self._dict["unit_mean_discharge"] = (
_translate("Unit", "Mean discharge") + f" (m³/s)"
)
self._dict["unit_std_deriv_discharge"] = (
_translate("Unit", "Discharge standard derivation")
+ f" (m³/s)"
)
self._dict["unit_discharge_envelop"] = _translate( self._dict["unit_discharge_envelop"] = _translate(
"Unit", "Discharge Envelop") + " (m³/s)" "Unit", "Discharge Envelop") + " (m³/s)"
self._dict["unit_max_discharge"] = _translate( self._dict["unit_max_discharge"] = _translate(
+111
View File
@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>418</width>
<height>220</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_x">
<item>
<widget class="QLabel" name="label_x">
<property name="text">
<string>X axis:</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_y">
<item>
<widget class="QLabel" name="label_y">
<property name="text">
<string>Y axis:</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_3">
<layout class="QVBoxLayout" name="verticalLayout_solv">
<item>
<widget class="QLabel" name="label_solv">
<property name="text">
<string>Solver:</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="5" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_data">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+85
View File
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>416</width>
<height>81</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_0">
<property name="text">
<string>@param0</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBox_0">
<property name="minimum">
<double>-100000.000000000000000</double>
</property>
<property name="maximum">
<double>100000.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
@@ -17,14 +17,14 @@
<item row="0" column="0"> <item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label_0">
<property name="text"> <property name="text">
<string>Min:</string> <string>@param0</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDoubleSpinBox" name="doubleSpinBox_min"> <widget class="QDoubleSpinBox" name="doubleSpinBox_0">
<property name="minimum"> <property name="minimum">
<double>-100000.000000000000000</double> <double>-100000.000000000000000</double>
</property> </property>
@@ -38,14 +38,14 @@
<item row="0" column="1"> <item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_1">
<property name="text"> <property name="text">
<string>Max:</string> <string>@param1</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDoubleSpinBox" name="doubleSpinBox_max"> <widget class="QDoubleSpinBox" name="doubleSpinBox_1">
<property name="minimum"> <property name="minimum">
<double>-100000.000000000000000</double> <double>-100000.000000000000000</double>
</property> </property>
+157
View File
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>875</width>
<height>415</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_name"/>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Distribution</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="comboBox_distribution"/>
</item>
<item row="0" column="4">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>313</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" rowspan="2" colspan="2">
<widget class="QGroupBox" name="groupBox_target_data">
<property name="title">
<string>Target data</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTreeWidget" name="treeWidget_target_data">
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="2" colspan="3">
<widget class="QGroupBox" name="groupBox_params">
<property name="title">
<string>Parameters</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_params"/>
</item>
</layout>
</widget>
</item>
<item row="2" column="2" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="2" colspan="3">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+58 -19
View File
@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>400</width>
<height>79</height> <height>152</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -15,26 +15,65 @@
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="text"> <item>
<string>Run number</string> <widget class="QLabel" name="label_2">
</property> <property name="text">
</widget> <string>Draw function</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox"/>
</item>
</layout>
</item> </item>
<item row="0" column="1"> <item row="1" column="0">
<widget class="QSpinBox" name="spinBox"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="minimum"> <item>
<number>1</number> <widget class="QLabel" name="label">
</property> <property name="text">
<property name="maximum"> <string>Number of run</string>
<number>1000000</number> </property>
</property> </widget>
<property name="value"> </item>
<number>100</number> <item>
</property> <widget class="QSpinBox" name="spinBox">
</widget> <property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1000000</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</item> </item>
<item row="1" column="0" colspan="2"> <item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Custom seed</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_seed">
<property name="enabled">
<bool>false</bool>
</property>
<property name="maximum">
<number>2147483647</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
+27
View File
@@ -33,6 +33,9 @@
<addaction name="action_add"/> <addaction name="action_add"/>
<addaction name="action_del"/> <addaction name="action_del"/>
<addaction name="action_edit"/> <addaction name="action_edit"/>
<addaction name="separator"/>
<addaction name="action_export"/>
<addaction name="action_import"/>
</widget> </widget>
<action name="action_add"> <action name="action_add">
<property name="icon"> <property name="icon">
@@ -79,6 +82,30 @@
<enum>QAction::NoRole</enum> <enum>QAction::NoRole</enum>
</property> </property>
</action> </action>
<action name="action_export">
<property name="icon">
<iconset>
<normaloff>ressources/export.png</normaloff>ressources/export.png</iconset>
</property>
<property name="text">
<string>Export</string>
</property>
<property name="toolTip">
<string>Export all ensemble configuration into json file</string>
</property>
</action>
<action name="action_import">
<property name="icon">
<iconset>
<normaloff>ressources/import.png</normaloff>ressources/import.png</iconset>
</property>
<property name="text">
<string>Import</string>
</property>
<property name="toolTip">
<string>Import ensemble configuration from json file</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>
-13
View File
@@ -196,22 +196,9 @@
<attribute name="toolBarBreak"> <attribute name="toolBarBreak">
<bool>false</bool> <bool>false</bool>
</attribute> </attribute>
<addaction name="action_add"/>
<addaction name="action_export"/> <addaction name="action_export"/>
<addaction name="action_import_data"/> <addaction name="action_import_data"/>
</widget> </widget>
<action name="action_add">
<property name="icon">
<iconset>
<normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
</property>
<property name="text">
<string>Add</string>
</property>
<property name="toolTip">
<string>Add customized visualization</string>
</property>
</action>
<action name="action_export"> <action name="action_export">
<property name="icon"> <property name="icon">
<iconset> <iconset>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>482</width>
<height>48</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_0">
<property name="text">
<string>Parameter 1</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBox_0">
<property name="minimum">
<double>-10000000000.000000000000000</double>
</property>
<property name="maximum">
<double>10000000000.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>416</width>
<height>80</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_0">
<property name="text">
<string>Parameter 1</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_0">
<property name="minimum">
<double>-10000000000.000000000000000</double>
</property>
<property name="maximum">
<double>10000000000.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_1">
<property name="text">
<string>Parameter 2</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="doubleSpinBox_1">
<property name="minimum">
<double>-10000000000.000000000000000</double>
</property>
<property name="maximum">
<double>10000000000.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
Binary file not shown.