mirror of
https://gitlab.com/pamhyr/pamhyr2.git
synced 2026-10-30 03:20:41 +01:00
Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e5153d63d | ||
|
|
797de09683 | ||
|
|
ef10588880 | ||
|
|
c2a8e17bf3 | ||
|
|
03979a8bea | ||
|
|
a4eee962f7 | ||
|
|
d8d1b4cd1f | ||
|
|
cdeca8a2e8 | ||
|
|
4d9c4cb5d0 | ||
|
|
618477b384 | ||
|
|
70a26d0036 | ||
|
|
c3afc4dd58 | ||
|
|
ef72e41b14 | ||
|
|
be7b5b892a | ||
|
|
5f64362f65 | ||
|
|
357b39758c | ||
|
|
a2f215984b | ||
|
|
b1cf45497b | ||
|
|
ee416e1059 | ||
|
|
3fee3b0bb3 | ||
|
|
a06c2eaa7e | ||
|
|
4e767d86d6 | ||
|
|
f43d108871 | ||
|
|
33a2107876 | ||
|
|
7103fdcb46 | ||
|
|
ace85ccb40 | ||
|
|
ec81a998cb | ||
|
|
a18accb404 |
No files matched your search
@@ -44,6 +44,7 @@ __old__
|
||||
mage8/
|
||||
adists/
|
||||
rubarbe/
|
||||
rubar/
|
||||
|
||||
linux/
|
||||
windows/
|
||||
|
||||
@@ -254,3 +254,46 @@ for each reach"
|
||||
return False
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
class MageLateralSolidChecker(AbstractModelChecker):
|
||||
"""Mage has no solid lateral contribution (notice: only upstream
|
||||
solid discharges, QSO): the solid ones are not exported (LAT holds
|
||||
liquid discharges only). Non blocking warning"""
|
||||
|
||||
def __init__(self, version="mage8"):
|
||||
super(MageLateralSolidChecker, self).__init__()
|
||||
|
||||
self._name = _translate(
|
||||
"Checker", "Mage solid lateral contributions checker")
|
||||
self._description = _translate(
|
||||
"Checker",
|
||||
"Check if the study has solid lateral contributions, which "
|
||||
"Mage does not take into account"
|
||||
)
|
||||
self._solver = version
|
||||
self._modules = Modules.LATERAL_CONTRIBUTION
|
||||
|
||||
def run(self, study):
|
||||
if not self.basic_check(study):
|
||||
return False
|
||||
|
||||
edges = study.river.enable_edges()
|
||||
names = [
|
||||
lc.name
|
||||
for lc in study.river.lateral_contribution.get_tab("solid")
|
||||
if not lc.is_deleted() and lc.reach in edges and len(lc) > 0
|
||||
]
|
||||
|
||||
if len(names) == 0:
|
||||
self._status = STATUS.OK
|
||||
self._summary = "ok"
|
||||
return True
|
||||
|
||||
self._status = STATUS.WARNING
|
||||
self._summary = _translate(
|
||||
"Checker",
|
||||
"Solid lateral contributions not taken into account by Mage: "
|
||||
"{names}"
|
||||
).format(names=", ".join(names))
|
||||
return False
|
||||
@@ -0,0 +1,255 @@
|
||||
# RubarBE.py -- Pamhyr RubarBE checkers
|
||||
# Copyright (C) 2026 INRAE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""RubarBE checkers. Each one runs a step of the checks of the RubarBE
|
||||
export (Solver/RubarBE.py), without writing any file, so that they
|
||||
always give the messages of the export: an error where the export would
|
||||
be cancelled, a warning for the data it ignores (empty boundary
|
||||
conditions, lateral contributions not read, solid lateral contributions
|
||||
without liquid one, structures without RubarBE equivalent...)."""
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from Modules import Modules
|
||||
from tools import logger_exception
|
||||
|
||||
from Checker.Checker import AbstractModelChecker, STATUS
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class RubarBEChecker(AbstractModelChecker):
|
||||
"""Base class: _check(solver, study, context, warnings) runs the
|
||||
export step, raising RubarExportError (error) or appending to
|
||||
warnings (warning). Steps that need the reach are not run when the
|
||||
study has not a single valid reach: RubarBEReachChecker reports it"""
|
||||
|
||||
_needs_reach = True
|
||||
|
||||
def __init__(self, name, description, modules, version="rubarbe"):
|
||||
super(RubarBEChecker, self).__init__()
|
||||
|
||||
self._name = name
|
||||
self._description = description
|
||||
self._solver = version
|
||||
self._modules = modules
|
||||
|
||||
@staticmethod
|
||||
def _context(solver, study):
|
||||
# Read the parameters without creating them (get_params would
|
||||
# add default ones to the study)
|
||||
river = study.river
|
||||
if solver.type in river._parameters:
|
||||
values = solver._parameter_values(study)
|
||||
else:
|
||||
values = {k: str(v).strip()
|
||||
for k, v in solver.default_parameters()}
|
||||
|
||||
return {
|
||||
"values": values,
|
||||
"trased": values.get("rubarbe_trased", "n").lower()
|
||||
in ("y", "o"),
|
||||
}
|
||||
|
||||
def _check(self, solver, study, context, warnings):
|
||||
raise NotImplementedError
|
||||
|
||||
def run(self, study):
|
||||
from Solver.RubarBE import RubarBE, RubarExportError
|
||||
|
||||
if not self.basic_check(study):
|
||||
return False
|
||||
|
||||
solver = RubarBE("checker")
|
||||
warnings = []
|
||||
try:
|
||||
context = self._context(solver, study)
|
||||
if self._needs_reach:
|
||||
try:
|
||||
edge, profiles = solver._single_reach(study.river)
|
||||
except RubarExportError:
|
||||
self._status = STATUS.OK
|
||||
self._summary = "ok"
|
||||
return True
|
||||
context["hydraulics"] = {
|
||||
"edge": edge,
|
||||
"profiles": profiles,
|
||||
"tinit": solver._seconds(
|
||||
context["values"].get("rubarbe_tinit", "0")),
|
||||
"warnings": warnings,
|
||||
}
|
||||
self._check(solver, study, context, warnings)
|
||||
except (RubarExportError, ValueError) as error:
|
||||
self._status = STATUS.ERROR
|
||||
self._summary = str(error)
|
||||
return False
|
||||
except Exception as error:
|
||||
# Never stop the checker thread
|
||||
logger_exception(error)
|
||||
self._status = STATUS.UNKNOWN
|
||||
self._summary = str(error)
|
||||
return False
|
||||
|
||||
if warnings:
|
||||
self._status = STATUS.WARNING
|
||||
self._summary = " | ".join(warnings)
|
||||
return False
|
||||
|
||||
self._status = STATUS.OK
|
||||
self._summary = "ok"
|
||||
return True
|
||||
|
||||
|
||||
class RubarBEReachChecker(RubarBEChecker):
|
||||
_needs_reach = False
|
||||
|
||||
def __init__(self, version="rubarbe"):
|
||||
super(RubarBEReachChecker, self).__init__(
|
||||
_translate("Checker", "RubarBE reach checker"),
|
||||
_translate(
|
||||
"Checker",
|
||||
"Check that the study has a single enabled reach, with at "
|
||||
"least two profiles of increasing rk"
|
||||
),
|
||||
Modules.GEOMETRY, version,
|
||||
)
|
||||
|
||||
def _check(self, solver, study, context, warnings):
|
||||
solver._single_reach(study.river)
|
||||
|
||||
|
||||
class RubarBEGeometryChecker(RubarBEChecker):
|
||||
def __init__(self, version="rubarbe"):
|
||||
super(RubarBEGeometryChecker, self).__init__(
|
||||
_translate("Checker", "RubarBE geometry and sediment checker"),
|
||||
_translate(
|
||||
"Checker",
|
||||
"Check the main channel and active bed limits of the "
|
||||
"profiles and, with sediment transport, their sediment "
|
||||
"layers"
|
||||
),
|
||||
# Rerun on changes of the geometry, which impact the sediment
|
||||
# layers
|
||||
Modules.SEDIMENT_LAYER, version,
|
||||
)
|
||||
|
||||
def _check(self, solver, study, context, warnings):
|
||||
solver._geometry_data(context["hydraulics"]["edge"],
|
||||
context["trased"])
|
||||
|
||||
|
||||
class RubarBEBoundaryChecker(RubarBEChecker):
|
||||
def __init__(self, version="rubarbe"):
|
||||
super(RubarBEBoundaryChecker, self).__init__(
|
||||
_translate("Checker", "RubarBE boundary conditions checker"),
|
||||
_translate(
|
||||
"Checker",
|
||||
"Check the liquid and solid boundary conditions used by "
|
||||
"the boundary settings of the solver parameters"
|
||||
),
|
||||
Modules.BOUNDARY_CONDITION, version,
|
||||
)
|
||||
|
||||
def _check(self, solver, study, context, warnings):
|
||||
values = context["values"]
|
||||
edge = context["hydraulics"]["edge"]
|
||||
solver._check_regime(values)
|
||||
solver._check_boundaries(study.river, edge, values, warnings)
|
||||
solver._solid_boundary(study.river, edge, values,
|
||||
context["trased"], warnings)
|
||||
|
||||
|
||||
class RubarBEFrictionChecker(RubarBEChecker):
|
||||
def __init__(self, version="rubarbe"):
|
||||
super(RubarBEFrictionChecker, self).__init__(
|
||||
_translate("Checker", "RubarBE friction checker"),
|
||||
_translate("Checker", "Check that the reach has frictions"),
|
||||
Modules.FRICTION, version,
|
||||
)
|
||||
|
||||
def _check(self, solver, study, context, warnings):
|
||||
solver._interpolated_frictions(context["hydraulics"]["edge"])
|
||||
|
||||
|
||||
class RubarBEInitialConditionsChecker(RubarBEChecker):
|
||||
def __init__(self, version="rubarbe"):
|
||||
super(RubarBEInitialConditionsChecker, self).__init__(
|
||||
_translate("Checker", "RubarBE initial conditions checker"),
|
||||
_translate(
|
||||
"Checker", "Check the initial conditions of the reach"),
|
||||
Modules.INITIAL_CONDITION, version,
|
||||
)
|
||||
|
||||
def _check(self, solver, study, context, warnings):
|
||||
solver._initial_conditions(study.river,
|
||||
context["hydraulics"]["edge"])
|
||||
|
||||
|
||||
class RubarBELateralChecker(RubarBEChecker):
|
||||
def __init__(self, version="rubarbe"):
|
||||
super(RubarBELateralChecker, self).__init__(
|
||||
_translate("Checker", "RubarBE lateral contributions checker"),
|
||||
_translate(
|
||||
"Checker",
|
||||
"Check the lateral contributions: read only with lateral "
|
||||
"overflows, solid ones attached to the liquid contribution "
|
||||
"of the same sections"
|
||||
),
|
||||
Modules.LATERAL_CONTRIBUTION, version,
|
||||
)
|
||||
|
||||
def _check(self, solver, study, context, warnings):
|
||||
solver._lateral_data(study.river, context["hydraulics"],
|
||||
context["values"], context["trased"], warnings)
|
||||
|
||||
|
||||
class RubarBEStructureChecker(RubarBEChecker):
|
||||
def __init__(self, version="rubarbe"):
|
||||
super(RubarBEStructureChecker, self).__init__(
|
||||
_translate("Checker", "RubarBE hydraulic structures checker"),
|
||||
_translate(
|
||||
"Checker",
|
||||
"Check the hydraulic structures: weirs and rectangular and "
|
||||
"circular orifices only"
|
||||
),
|
||||
Modules.HYDRAULIC_STRUCTURES, version,
|
||||
)
|
||||
|
||||
def _check(self, solver, study, context, warnings):
|
||||
solver._structure_data(study.river, context["hydraulics"], warnings)
|
||||
|
||||
|
||||
class RubarBESavesChecker(RubarBEChecker):
|
||||
def __init__(self, version="rubarbe"):
|
||||
super(RubarBESavesChecker, self).__init__(
|
||||
_translate("Checker", "RubarBE saves checker"),
|
||||
_translate(
|
||||
"Checker",
|
||||
"Check the save abscissas (ABSHYD) and times (TNPROF) of "
|
||||
"the solver parameters"
|
||||
),
|
||||
# The solver parameters have no module: rerun on any change
|
||||
Modules.NONE, version,
|
||||
)
|
||||
|
||||
def _check(self, solver, study, context, warnings):
|
||||
solver._save_data(context["values"], context["hydraulics"])
|
||||
@@ -17,6 +17,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import queue
|
||||
import types
|
||||
import random
|
||||
import logging
|
||||
import numpy as np
|
||||
@@ -39,9 +40,9 @@ logger = logging.getLogger()
|
||||
|
||||
@dataclass
|
||||
class Tabulation:
|
||||
z: np.array([])
|
||||
A: np.array([])
|
||||
L: np.array([])
|
||||
z: np.ndarray
|
||||
A: np.ndarray
|
||||
L: np.ndarray
|
||||
|
||||
|
||||
class ProfileXYZ(Profile, SQLSubModel):
|
||||
@@ -842,7 +843,11 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
return poly
|
||||
|
||||
def wet_points(self, z):
|
||||
left, right = self.get_water_limits(z)
|
||||
limits = self.get_water_limits(z)
|
||||
|
||||
if isinstance(limits, types.GeneratorType):
|
||||
limits = next(limits)
|
||||
left, right = limits
|
||||
|
||||
points = list(filter(lambda p: p.z < z, self._points))
|
||||
points = [left] + points + [right]
|
||||
|
||||
@@ -224,6 +224,9 @@ class LateralContribution(SQLSubModel):
|
||||
self._header = []
|
||||
self._types = [float, float]
|
||||
|
||||
self._d50 = 0.002
|
||||
self._sigma = 1.0
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
execute(f"""
|
||||
@@ -236,6 +239,8 @@ class LateralContribution(SQLSubModel):
|
||||
reach INTEGER,
|
||||
begin_section INTEGER,
|
||||
end_section INTEGER,
|
||||
d50 REAL DEFAULT 0.002,
|
||||
sigma REAL DEFAULT 1,
|
||||
{Scenario.create_db_add_scenario()},
|
||||
{Scenario.create_db_add_scenario_fk()},
|
||||
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
|
||||
@@ -286,8 +291,32 @@ class LateralContribution(SQLSubModel):
|
||||
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
|
||||
)
|
||||
|
||||
if major == "0" and (int(minor) < 2 or
|
||||
(minor == "2" and int(release) < 11)):
|
||||
cls._db_update_to_0_2_11(execute)
|
||||
|
||||
return cls._update_submodel(execute, version, data)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_11(cls, execute):
|
||||
# The table may already have been recreated with these columns
|
||||
# by _db_update_to_0_2_0
|
||||
columns = [
|
||||
row[0] for row in execute(
|
||||
"SELECT name FROM pragma_table_info('lateral_contribution')"
|
||||
)
|
||||
]
|
||||
if "d50" not in columns:
|
||||
execute(
|
||||
"ALTER TABLE lateral_contribution " +
|
||||
"ADD COLUMN d50 REAL DEFAULT 0.002"
|
||||
)
|
||||
if "sigma" not in columns:
|
||||
execute(
|
||||
"ALTER TABLE lateral_contribution " +
|
||||
"ADD COLUMN sigma REAL DEFAULT 1"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _db_update_to_0_2_0(cls, execute, data):
|
||||
table = "lateral_contribution"
|
||||
@@ -339,7 +368,7 @@ class LateralContribution(SQLSubModel):
|
||||
|
||||
table = execute(
|
||||
"SELECT pamhyr_id, deleted, name, type, " +
|
||||
"reach, begin_section, end_section, scenario " +
|
||||
"reach, begin_section, end_section, d50, sigma, scenario " +
|
||||
"FROM lateral_contribution " +
|
||||
f"WHERE tab = '{tab}'" +
|
||||
f"AND scenario = {scenario.id} " +
|
||||
@@ -356,6 +385,8 @@ class LateralContribution(SQLSubModel):
|
||||
reach = next(it)
|
||||
b_section = next(it)
|
||||
e_section = next(it)
|
||||
d50 = next(it)
|
||||
sigma = next(it)
|
||||
owner_scenario = next(it)
|
||||
|
||||
ctor = cls._get_ctor_from_type(t)
|
||||
@@ -367,6 +398,10 @@ class LateralContribution(SQLSubModel):
|
||||
lc.reach = None
|
||||
lc._begin_section = None
|
||||
lc._end_section = None
|
||||
if d50 is not None:
|
||||
lc._d50 = float(d50)
|
||||
if sigma is not None:
|
||||
lc._sigma = float(sigma)
|
||||
|
||||
if row[3] != -1:
|
||||
lc.reach = next(
|
||||
@@ -426,12 +461,13 @@ class LateralContribution(SQLSubModel):
|
||||
"INSERT INTO " +
|
||||
"lateral_contribution(" +
|
||||
"pamhyr_id, deleted, name, type, tab, " +
|
||||
"reach, begin_section, end_section, scenario) " +
|
||||
"reach, begin_section, end_section, d50, sigma, scenario) " +
|
||||
"VALUES (" +
|
||||
f"{self.id}, {self._db_format(self.is_deleted())}, " +
|
||||
f"'{self._db_format(self._name)}', " +
|
||||
f"'{self._db_format(self._type)}', '{tab}', {reach}, " +
|
||||
f"{begin_section}, {end_section}, " +
|
||||
f"{self._d50}, {self._sigma}, " +
|
||||
f"{self._status.scenario_id}" +
|
||||
")"
|
||||
)
|
||||
@@ -523,10 +559,30 @@ class LateralContribution(SQLSubModel):
|
||||
new._reach = reach
|
||||
new._begin_section = begin_section
|
||||
new._end_section = end_section
|
||||
new._d50 = self._d50
|
||||
new._sigma = self._sigma
|
||||
new._data = [data.cloned() for data in self.data]
|
||||
new.modified()
|
||||
return new
|
||||
|
||||
@property
|
||||
def d50(self):
|
||||
return self._d50
|
||||
|
||||
@d50.setter
|
||||
def d50(self, value):
|
||||
self._d50 = float(value)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def sigma(self):
|
||||
return self._sigma
|
||||
|
||||
@sigma.setter
|
||||
def sigma(self, value):
|
||||
self._sigma = float(value)
|
||||
self.modified()
|
||||
|
||||
@property
|
||||
def begin_rk(self):
|
||||
if self._begin_section is None:
|
||||
|
||||
@@ -104,7 +104,10 @@ class Profile(SQLSubModel):
|
||||
return any(map(lambda ts: "sl" in self._data[ts], self._data))
|
||||
|
||||
def has_bedload(self):
|
||||
return any(map(lambda ts: "sl" in self._data[ts], self._data))
|
||||
# Sediment layers (Mage) or bed level of the section (RubarBE)
|
||||
return any(map(lambda ts: ("sl" in self._data[ts] or
|
||||
"zfd" in self._data[ts]),
|
||||
self._data))
|
||||
|
||||
@classmethod
|
||||
def _db_create(cls, execute, ext=""):
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
from copy import copy
|
||||
from tools import trace, timer
|
||||
from Solver.Solvers import solver_type_list
|
||||
from Solver.RubarBE import Rubar3
|
||||
|
||||
from Model.Scenario import Scenario
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
@@ -296,14 +297,20 @@ class SolverParametersList(PamhyrModelList):
|
||||
scenario = data["scenario"]
|
||||
loaded = data['loaded_pid']
|
||||
|
||||
solvers = execute("SELECT DISTINCT solver FROM solver_parameter")
|
||||
solvers = [
|
||||
row[0] for row in
|
||||
execute("SELECT DISTINCT solver FROM solver_parameter")
|
||||
]
|
||||
new = {}
|
||||
|
||||
if scenario is None:
|
||||
return new
|
||||
|
||||
for solver in solvers:
|
||||
solver = solver[0]
|
||||
if solver == "rubar3" and "rubarbe" not in solvers:
|
||||
new["rubarbe"] = cls._db_load_rubar3(execute, data)
|
||||
continue
|
||||
|
||||
if solver not in solver_type_list:
|
||||
continue
|
||||
|
||||
@@ -327,6 +334,32 @@ class SolverParametersList(PamhyrModelList):
|
||||
|
||||
return new
|
||||
|
||||
@classmethod
|
||||
def _db_load_rubar3(cls, execute, data):
|
||||
"""Rubar3 is no longer offered: RubarBE, its generic version,
|
||||
takes the parameters of a Rubar3 study (same rubarbe_* keys,
|
||||
RubarBE reads them the same way). Missing parameters take the
|
||||
Rubar3 defaults (no sediment transport), so that the
|
||||
computation is unchanged. New parameters (new ids): saved as
|
||||
RubarBE ones with the study, the Rubar3 rows are then ignored"""
|
||||
status = data["status"]
|
||||
n = cls(solver_type=solver_type_list["rubarbe"], status=status)
|
||||
|
||||
data['solver'] = "rubar3"
|
||||
lst = Parameter._db_load(execute, data)
|
||||
|
||||
n._lst = [
|
||||
Parameter(name=p.name, value=p.value, status=status)
|
||||
for _, p in sorted(lst, key=lambda t: t[0])
|
||||
]
|
||||
names = {p.name for p in n._lst}
|
||||
n._lst += [
|
||||
Parameter(name=name, value=value, status=status)
|
||||
for name, value in Rubar3.default_parameters()
|
||||
if name not in names
|
||||
]
|
||||
return n
|
||||
|
||||
def _db_save(self, execute, data=None):
|
||||
t = self._solver._type
|
||||
execute(
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ logger = logging.getLogger()
|
||||
|
||||
|
||||
class Study(SQLModel):
|
||||
_version = "0.2.10"
|
||||
_version = "0.2.11"
|
||||
|
||||
_sub_classes = [
|
||||
Scenario,
|
||||
|
||||
+3
-1
@@ -28,6 +28,7 @@ from tools import timer, trace, logger_exception
|
||||
from Solver.CommandLine import CommandLineSolver
|
||||
from Checker.Mage import (
|
||||
MageNetworkGraphChecker, MageGeometryGuideLineChecker,
|
||||
MageLateralSolidChecker,
|
||||
)
|
||||
|
||||
from Model.Results.Results import Results
|
||||
@@ -106,6 +107,7 @@ class Mage(CommandLineSolver):
|
||||
MageNetworkGraphChecker(connectivity=True, version=cls._type),
|
||||
MageNetworkGraphChecker(connectivity=False, version=cls._type),
|
||||
MageGeometryGuideLineChecker(version=cls._type),
|
||||
MageLateralSolidChecker(version=cls._type),
|
||||
]
|
||||
|
||||
return lst
|
||||
@@ -391,7 +393,7 @@ class Mage(CommandLineSolver):
|
||||
qlog.put(f"Export LAT file")
|
||||
files.append(f"{name}.LAT")
|
||||
|
||||
for tab in ["liquid", "solid", "suspenssion"]:
|
||||
for tab in ["liquid"]:
|
||||
for lateral in lst.get_tab(tab):
|
||||
self._export_LC(study, lateral, f, qlog)
|
||||
|
||||
|
||||
+1123
-322
File diff suppressed because it is too large.
Load diff
@@ -36,7 +36,6 @@ solver_long_name = {
|
||||
"adistswc": "Adis-TS_WC",
|
||||
"adisttwc": "Adis-TT_WC",
|
||||
"rubarbe": "RubarBE",
|
||||
"rubar3": "Rubar3",
|
||||
}
|
||||
|
||||
solver_type_list = {
|
||||
@@ -47,5 +46,4 @@ solver_type_list = {
|
||||
"adistswc": AdisTSwc,
|
||||
"adisttwc": AdisTTwc,
|
||||
"rubarbe": RubarBE,
|
||||
"rubar3": Rubar3,
|
||||
}
|
||||
@@ -188,7 +188,10 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
self.canvas = MplCanvas(width=5, height=4, dpi=100)
|
||||
self.canvas.setObjectName("canvas")
|
||||
self.toolbar = PamhyrPlotToolbar(
|
||||
self.canvas, self
|
||||
self.canvas, self, items=[
|
||||
"home", "move", "zoom", "save",
|
||||
"iso", "back/forward"
|
||||
]
|
||||
)
|
||||
self.verticalLayout.addWidget(self.toolbar)
|
||||
self.verticalLayout.addWidget(self.canvas)
|
||||
|
||||
@@ -105,9 +105,19 @@ class ComboBoxDelegate(QItemDelegate):
|
||||
strickler
|
||||
)
|
||||
elif self._mode == "rk":
|
||||
self.editor.addItems(
|
||||
list(map(str, self._data.reach.get_rk()))
|
||||
)
|
||||
if self._data is None:
|
||||
self.editor.addItems(
|
||||
["-"]
|
||||
)
|
||||
else:
|
||||
self.editor.addItems(
|
||||
list(
|
||||
map(
|
||||
lambda p: p.display_name(),
|
||||
self._data.reach.profiles
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
|
||||
return self.editor
|
||||
|
||||
@@ -42,7 +42,7 @@ class PlotAC(PamhyrPlot):
|
||||
self._isometric_axis = False
|
||||
|
||||
self._auto_relim_update = True
|
||||
self._autoscale_update = False
|
||||
self._autoscale_update = True
|
||||
|
||||
@property
|
||||
def river(self):
|
||||
|
||||
@@ -31,6 +31,14 @@ _translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class PlotRKC(PamhyrPlot):
|
||||
# Class attributes: PlotRKZ (frictions) does not call this __init__
|
||||
_selected = None # Selected hydraulic structure, in blue
|
||||
profile = None
|
||||
line_rk_zmin = None
|
||||
line_rk_zmin_zmax = None
|
||||
hs_vlines = None
|
||||
_hs_drawn = [] # Structures of hs_vlines, in order
|
||||
|
||||
def __init__(self, canvas=None, trad=None, toolbar=None,
|
||||
river=None, reach=None, profile=None,
|
||||
parent=None):
|
||||
@@ -134,19 +142,14 @@ class PlotRKC(PamhyrPlot):
|
||||
vymin = []
|
||||
vymax = []
|
||||
self.anotate_lst = []
|
||||
self._hs_drawn = []
|
||||
hs_color = []
|
||||
index = self.parent.tableView.selectedIndexes()
|
||||
for i, hs in enumerate(lhs):
|
||||
for hs in lhs:
|
||||
section = hs.input_section
|
||||
if section is None:
|
||||
continue
|
||||
|
||||
if i == index[0].row():
|
||||
color = "blue"
|
||||
elif hs.enabled:
|
||||
color = "red"
|
||||
else:
|
||||
color = "darkgrey"
|
||||
color = self._hs_color(hs)
|
||||
|
||||
x = section.rk
|
||||
if x is not None:
|
||||
@@ -164,6 +167,7 @@ class PlotRKC(PamhyrPlot):
|
||||
vymin.append(min(z_min))
|
||||
vymax.append(max(z_max))
|
||||
hs_color.append(color)
|
||||
self._hs_drawn.append(hs)
|
||||
|
||||
self.hs_vlines = self.canvas.axes.vlines(
|
||||
x=vx, ymin=vymin, ymax=vymax,
|
||||
@@ -172,6 +176,28 @@ class PlotRKC(PamhyrPlot):
|
||||
color=hs_color,
|
||||
)
|
||||
|
||||
def _hs_color(self, hs):
|
||||
if hs is self._selected:
|
||||
return "blue"
|
||||
if hs.enabled:
|
||||
return "red"
|
||||
return "darkgrey"
|
||||
|
||||
def set_selected(self, hs):
|
||||
"""Draw the selected structure in blue, without redrawing the
|
||||
plot (the view is kept)"""
|
||||
self._selected = hs
|
||||
if self.hs_vlines is None:
|
||||
return
|
||||
|
||||
colors = [self._hs_color(h) for h in self._hs_drawn]
|
||||
if colors:
|
||||
self.hs_vlines.set_color(colors)
|
||||
for annotation, color in zip(self.anotate_lst, colors):
|
||||
annotation.set_color(color)
|
||||
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
|
||||
def color_highlight(self, highlight):
|
||||
|
||||
reach = self._current_reach
|
||||
@@ -255,7 +281,8 @@ class PlotRKC(PamhyrPlot):
|
||||
|
||||
closest = self._closest_profile(event)
|
||||
index = self.parent.tableView.selectedIndexes()
|
||||
if self.parent._table is not None:
|
||||
# Moves the selected structure: nothing to do without selection
|
||||
if self.parent._table is not None and len(index) > 2:
|
||||
self.parent._table.setData(index[2], closest)
|
||||
|
||||
return
|
||||
|
||||
@@ -130,11 +130,13 @@ class HydraulicStructuresWindow(PamhyrWindow):
|
||||
self._checkbox = self.find(QCheckBox, f"checkBox")
|
||||
self._set_checkbox_state()
|
||||
|
||||
_toolbar_items = ["home", "move", "zoom", "save", "iso", "back/forward"]
|
||||
|
||||
def setup_plots(self):
|
||||
self.canvas = MplCanvas(width=5, height=4, dpi=100)
|
||||
self.canvas.setObjectName("canvas")
|
||||
self.toolbar = PamhyrPlotToolbar(
|
||||
self.canvas, self
|
||||
self.canvas, self, items=self._toolbar_items
|
||||
)
|
||||
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
|
||||
self.plot_layout.addWidget(self.toolbar)
|
||||
@@ -143,7 +145,7 @@ class HydraulicStructuresWindow(PamhyrWindow):
|
||||
self.plot_rkc = PlotRKC(
|
||||
canvas=self.canvas,
|
||||
river=self._study.river,
|
||||
reach=None,
|
||||
reach=self._displayed_reach(None),
|
||||
profile=None,
|
||||
trad=self._trad,
|
||||
toolbar=self.toolbar,
|
||||
@@ -154,7 +156,7 @@ class HydraulicStructuresWindow(PamhyrWindow):
|
||||
self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
|
||||
self.canvas_2.setObjectName("canvas_2")
|
||||
self.toolbar_2 = PamhyrPlotToolbar(
|
||||
self.canvas_2, self
|
||||
self.canvas_2, self, items=self._toolbar_items
|
||||
)
|
||||
self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
|
||||
self.plot_layout_2.addWidget(self.toolbar_2)
|
||||
@@ -183,7 +185,7 @@ class HydraulicStructuresWindow(PamhyrWindow):
|
||||
table = self.find(QTableView, "tableView")
|
||||
table.selectionModel()\
|
||||
.selectionChanged\
|
||||
.connect(self.update)
|
||||
.connect(self.update_selection)
|
||||
|
||||
self._table.dataChanged.connect(self.update)
|
||||
self._table.layoutChanged.connect(self.update)
|
||||
@@ -283,10 +285,17 @@ class HydraulicStructuresWindow(PamhyrWindow):
|
||||
)
|
||||
|
||||
def update(self):
|
||||
"""Data changed: plots redrawn, fitted to their data"""
|
||||
self._set_checkbox_state()
|
||||
self._update_clear_plot()
|
||||
self._update_plots(redraw=True)
|
||||
|
||||
def _update_clear_plot(self):
|
||||
def update_selection(self):
|
||||
"""Selection changed: the selected structure is drawn in blue,
|
||||
the view is kept (unless its reach is another one)"""
|
||||
self._set_checkbox_state()
|
||||
self._update_plots(redraw=False)
|
||||
|
||||
def _selected_structure(self):
|
||||
rows = self.index_selected_rows()
|
||||
|
||||
if (len(rows) == 0 or
|
||||
|
||||
@@ -384,9 +384,19 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def generate(self, generator, param, option):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
self._undo.beginMacro(f"generate {generator}")
|
||||
self._undo.push(
|
||||
GenerateCommand(
|
||||
self._lst, generator, param, option
|
||||
)
|
||||
)
|
||||
self._undo.push(
|
||||
SortCommand(
|
||||
self._lst, False
|
||||
)
|
||||
)
|
||||
self._undo.endMacro()
|
||||
|
||||
self.layoutChanged.emit()
|
||||
@@ -100,15 +100,19 @@ class SortCommand(QUndoCommand):
|
||||
self._indexes = None
|
||||
|
||||
def undo(self):
|
||||
ll = self._ics.data
|
||||
order = {
|
||||
id(x): self._indexes[i]
|
||||
for i, x in enumerate(self._ics.data)
|
||||
}
|
||||
self._ics.sort(
|
||||
key=lambda x: self._indexes[ll.index(x)]
|
||||
key=lambda x: order.get(id(x), len(order))
|
||||
)
|
||||
|
||||
def redo(self):
|
||||
self._ics.sort(
|
||||
reverse=self._reverse,
|
||||
key=lambda x: x["rk"].rk
|
||||
key=lambda x: ((x["rk"] is None),
|
||||
0.0 if x["rk"] is None else x["rk"].rk)
|
||||
)
|
||||
if self._indexes is None:
|
||||
self._indexes = list(
|
||||
@@ -182,11 +186,19 @@ class GenerateCommand(QUndoCommand):
|
||||
self._ics = ics
|
||||
self._param = param
|
||||
self._option = option
|
||||
self._copy = self._ics.data
|
||||
# All the rows, with their deleted flag: the generation marks the
|
||||
# previous rows as deleted (clear_data)
|
||||
self._copy = list(self._ics._data)
|
||||
self._deleted = [d.is_deleted() for d in self._copy]
|
||||
self._generator = generator
|
||||
|
||||
def undo(self):
|
||||
self._ics.data = self._copy
|
||||
for data, deleted in zip(self._copy, self._deleted):
|
||||
if deleted:
|
||||
data.set_as_deleted()
|
||||
else:
|
||||
data.set_as_not_deleted()
|
||||
self._ics.data = list(self._copy)
|
||||
|
||||
def redo(self):
|
||||
if self._generator == "growing":
|
||||
|
||||
@@ -44,6 +44,22 @@ class SetDataCommand(QUndoCommand):
|
||||
self._data._set_i_c_v(self._index, self._column, self._new)
|
||||
|
||||
|
||||
class SetMetaDataCommand(QUndoCommand):
|
||||
def __init__(self, data, column, new_value):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._data = data
|
||||
self._column = column
|
||||
self._old = getattr(self._data, self._column)
|
||||
self._new = float(new_value)
|
||||
|
||||
def undo(self):
|
||||
setattr(self._data, self._column, self._old)
|
||||
|
||||
def redo(self):
|
||||
setattr(self._data, self._column, self._new)
|
||||
|
||||
|
||||
class AddCommand(QUndoCommand):
|
||||
def __init__(self, data, index):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
@@ -35,7 +35,7 @@ from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QPushButton, QLineEdit,
|
||||
QFileDialog, QTableView, QAbstractItemView,
|
||||
QUndoStack, QShortcut, QAction, QItemDelegate,
|
||||
QHeaderView,
|
||||
QHeaderView, QVBoxLayout,
|
||||
)
|
||||
|
||||
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||
@@ -44,6 +44,8 @@ from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
|
||||
from View.LateralContribution.Edit.translate import LCETranslate
|
||||
from View.LateralContribution.Edit.Table import TableModel
|
||||
from View.LateralContribution.Edit.Plot import Plot
|
||||
from View.LateralContribution.Edit.UndoCommand import SetMetaDataCommand
|
||||
from View.BoundaryCondition.Edit.Window import WD50Sigma
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
@@ -54,10 +56,15 @@ class EditLateralContributionWindow(PamhyrWindow):
|
||||
|
||||
def __init__(self, data=None,
|
||||
study=None, config=None,
|
||||
parent=None):
|
||||
tab="liquid", parent=None):
|
||||
self._data = data
|
||||
# Grain size of the sediment brought: solid contributions only
|
||||
self._is_solid = tab == "solid"
|
||||
|
||||
trad = LCETranslate()
|
||||
if self._is_solid:
|
||||
trad.get_dict("table_headers")["discharge"] = \
|
||||
trad["solid_discharge"]
|
||||
name = trad[self._pamhyr_name]
|
||||
if self._data is not None:
|
||||
edge_name = (self._data.reach.name if self._data.reach is not None
|
||||
@@ -81,6 +88,7 @@ class EditLateralContributionWindow(PamhyrWindow):
|
||||
|
||||
self.setup_table()
|
||||
self.setup_plot()
|
||||
self.setup_d50sigma()
|
||||
self.setup_connections()
|
||||
|
||||
def setup_table(self):
|
||||
@@ -139,7 +147,18 @@ class EditLateralContributionWindow(PamhyrWindow):
|
||||
)
|
||||
self.plot.draw()
|
||||
|
||||
def setup_d50sigma(self):
|
||||
if not self._is_solid:
|
||||
return
|
||||
|
||||
layout = self.find(QVBoxLayout, "verticalLayout_table")
|
||||
self._d50sigma = WD50Sigma(parent=self)
|
||||
layout.addWidget(self._d50sigma)
|
||||
self.widget_update()
|
||||
|
||||
def setup_connections(self):
|
||||
self._undo_stack.indexChanged.connect(self._propagate_lateral_update)
|
||||
|
||||
if self._study.is_editable():
|
||||
self.find(QAction, "action_add").triggered.connect(self.add)
|
||||
self.find(QAction, "action_del").triggered.connect(self.delete)
|
||||
@@ -148,10 +167,37 @@ class EditLateralContributionWindow(PamhyrWindow):
|
||||
self._table.dataChanged.connect(self.update)
|
||||
self._table.layoutChanged.connect(self.update)
|
||||
|
||||
if self._is_solid:
|
||||
if self._study.is_editable():
|
||||
self._d50sigma.d50Changed.connect(self.d50_changed)
|
||||
self._d50sigma.sigmaChanged.connect(self.sigma_changed)
|
||||
else:
|
||||
self._d50sigma.setEnabled(False)
|
||||
|
||||
def _propagate_lateral_update(self, index=None):
|
||||
# Every change of the lateral contributions goes through the undo
|
||||
# stack (push, undo, redo): the main window then reruns their
|
||||
# checkers
|
||||
self._propagate_update(key=Modules.LATERAL_CONTRIBUTION)
|
||||
|
||||
def d50_changed(self, value):
|
||||
self._undo_stack.push(SetMetaDataCommand(self._data, "d50", value))
|
||||
|
||||
def sigma_changed(self, value):
|
||||
self._undo_stack.push(
|
||||
SetMetaDataCommand(self._data, "sigma", value)
|
||||
)
|
||||
|
||||
def widget_update(self):
|
||||
if self._is_solid:
|
||||
self._d50sigma.set_d50(self._data.d50)
|
||||
self._d50sigma.set_sigma(self._data.sigma)
|
||||
|
||||
def _propagated_update(self, key=Modules(0)):
|
||||
if Modules.STUDY in key:
|
||||
self._table.layoutChanged.emit()
|
||||
self.plot.update()
|
||||
self.widget_update()
|
||||
|
||||
def update(self):
|
||||
self.plot.update()
|
||||
@@ -235,7 +281,9 @@ class EditLateralContributionWindow(PamhyrWindow):
|
||||
def _undo(self):
|
||||
self._table.undo()
|
||||
self.plot.update()
|
||||
self.widget_update()
|
||||
|
||||
def _redo(self):
|
||||
self._table.redo()
|
||||
self.plot.update()
|
||||
self.widget_update()
|
||||
@@ -39,3 +39,7 @@ class LCETranslate(LCTranslate):
|
||||
"discharge": self._dict["unit_discharge"],
|
||||
"z": self._dict["unit_elevation"],
|
||||
}
|
||||
# Discharge column of a solid contribution
|
||||
self._dict["solid_discharge"] = _translate(
|
||||
"LateralContribution", "Solid discharge (kg/s)"
|
||||
)
|
||||
@@ -20,6 +20,7 @@ import logging
|
||||
|
||||
from tools import trace, timer
|
||||
|
||||
from Modules import Modules
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
@@ -67,6 +68,7 @@ logger = logging.getLogger()
|
||||
class LateralContributionWindow(PamhyrWindow):
|
||||
_pamhyr_ui = "LateralContributions"
|
||||
_pamhyr_name = "Lateral contribution"
|
||||
_tabs = ["liquid", "solid"]
|
||||
|
||||
def __init__(self, study=None, config=None, parent=None):
|
||||
trad = LCTranslate()
|
||||
@@ -96,7 +98,7 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
else:
|
||||
editable_headers = []
|
||||
|
||||
for t in ["liquid", "solid", "suspenssion"]:
|
||||
for t in self._tabs:
|
||||
self._delegate_type = ComboBoxDelegate(
|
||||
data=self._study.river,
|
||||
mode="type",
|
||||
@@ -158,6 +160,8 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
)
|
||||
|
||||
def setup_connections(self):
|
||||
self._undo_stack.indexChanged.connect(self._propagate_lateral_update)
|
||||
|
||||
if self._study.is_editable():
|
||||
self.find(QAction, "action_import").triggered.connect(
|
||||
self.import_from_file)
|
||||
@@ -168,7 +172,7 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
self.find(QAction, "action_edit").triggered.connect(self.edit)
|
||||
self.tabs.currentChanged.connect(self._set_current_reach)
|
||||
|
||||
for t in ["liquid", "solid", "suspenssion"]:
|
||||
for t in self._tabs:
|
||||
table = self.find(QTableView, f"tableView_{t}")
|
||||
table.selectionModel()\
|
||||
.selectionChanged\
|
||||
@@ -177,6 +181,12 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
self._table[t].dataChanged\
|
||||
.connect(self._set_current_reach)
|
||||
|
||||
def _propagate_lateral_update(self, index=None):
|
||||
# Every change of the lateral contributions goes through the undo
|
||||
# stack (push, undo, redo): the main window then reruns their
|
||||
# checkers
|
||||
self._propagate_update(key=Modules.LATERAL_CONTRIBUTION)
|
||||
|
||||
def current_tab(self):
|
||||
return self.find(QTabWidget, "tabWidget")\
|
||||
.currentWidget()\
|
||||
@@ -303,6 +313,7 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
win = EditLateralContributionWindow(
|
||||
data=data,
|
||||
study=self._study,
|
||||
tab=tab,
|
||||
parent=self
|
||||
)
|
||||
win.show()
|
||||
|
||||
@@ -30,7 +30,7 @@ from platformdirs import user_cache_dir
|
||||
|
||||
from Solver.AdisTS import AdisTS
|
||||
from Solver.Mage import Mage8
|
||||
from Solver.RubarBE import Rubar3
|
||||
from Solver.RubarBE import RubarBE
|
||||
from tools import logger_exception, pamhyr_db_need_update, timer
|
||||
|
||||
from PyQt5 import QtGui
|
||||
@@ -2082,7 +2082,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
|
||||
dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
|
||||
dialog.setDefaultSuffix(".BIN")
|
||||
# dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden)
|
||||
dialog.setNameFilters(['Mage (*.BIN)', 'Rubar3 (profil.*)'])
|
||||
dialog.setNameFilters(['Mage (*.BIN)', 'RubarBE (profil.*)'])
|
||||
|
||||
if self._study.filename is not None:
|
||||
if self._last_solver is None:
|
||||
@@ -2102,7 +2102,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
|
||||
if ".BIN" in file_name[0]:
|
||||
solver = Mage8("Mage8")
|
||||
else:
|
||||
solver = Rubar3("Rubar3")
|
||||
solver = RubarBE("RubarBE")
|
||||
|
||||
self.open_solver_results(
|
||||
solver,
|
||||
|
||||
@@ -286,6 +286,10 @@ class PlotAC(PamhyrPlot):
|
||||
initial_z = np.asarray(profile.geometry.z(), dtype=float)
|
||||
result = self.results[self._current_res_id]
|
||||
|
||||
bed = self._get_bed_from_sections(profile, result, initial_z)
|
||||
if bed is not None:
|
||||
return bed
|
||||
|
||||
try:
|
||||
sediment_timestamps = sorted(result.get("sediment_timestamps"))
|
||||
except (KeyError, TypeError):
|
||||
@@ -324,6 +328,38 @@ class PlotAC(PamhyrPlot):
|
||||
|
||||
return initial_z
|
||||
|
||||
def _get_bed_from_sections(self, profile, result, initial_z):
|
||||
"""Bed of the solvers that give the bed level of each section at
|
||||
each time ('zfd') and exact sections at some times
|
||||
('bed_sections', RubarBE): the last exact section before the
|
||||
current time, or the study geometry, with its moving points
|
||||
('bed_points') shifted by the change of the bed level since then.
|
||||
None for the other results"""
|
||||
try:
|
||||
sections = result.get("bed_sections")
|
||||
moving = result.get("bed_points")[profile.global_index]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
current = profile.get_ts_key(self._current_timestamp, "zfd")
|
||||
if current is None:
|
||||
return None
|
||||
|
||||
index = profile.global_index
|
||||
saved = [ts for ts in sorted(sections)
|
||||
if ts <= self._current_timestamp and index in sections[ts]]
|
||||
if saved:
|
||||
base_timestamp = saved[-1]
|
||||
base = np.asarray(sections[base_timestamp][index], dtype=float)
|
||||
else:
|
||||
base_timestamp = min(result.get("timestamps"))
|
||||
base = initial_z
|
||||
|
||||
level = profile.get_ts_key(base_timestamp, "zfd")
|
||||
if level is None or len(base) != len(moving):
|
||||
return None
|
||||
return base + np.where(moving, current - level, 0.0)
|
||||
|
||||
def _get_wet_mask(self, profile, elevation):
|
||||
result = self.results[self._current_res_id]
|
||||
|
||||
|
||||
@@ -49,12 +49,282 @@ class RunSolverTranslate(PamhyrTranslate):
|
||||
"use a dimensionless critical stress given by the user. "
|
||||
"Fill it in the parameters (tab Solid Transport)."
|
||||
)
|
||||
self._dict["missing_solid_boundary"] = _translate(
|
||||
self._dict["solid_boundary_downstream"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: sediment transport (TRASED) is enabled but "
|
||||
"no solid boundary condition is defined at the upstream node "
|
||||
"of reach(es): {reaches}. Add a solid boundary condition, or "
|
||||
"disable sediment transport in the solver parameters."
|
||||
"Export cancelled: solid boundary condition {name} at node "
|
||||
"{node}: RubarBE only reads a solid boundary condition at the "
|
||||
"upstream node. Remove it."
|
||||
)
|
||||
self._dict["solid_boundary_too_long"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: solid boundary condition {name} at node "
|
||||
"{node} has {count} points, RubarBE accepts at most {maximum}."
|
||||
)
|
||||
self._dict["save_list_invalid"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: {key}: '{value}' is not a valid value. "
|
||||
"Separate the values with spaces or semicolons, use a dot as "
|
||||
"decimal separator, and give the times in seconds or "
|
||||
"days:hours:minutes:seconds."
|
||||
)
|
||||
self._dict["save_list_too_long"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: {key} has {count} values, RubarBE accepts "
|
||||
"at most {maximum}."
|
||||
)
|
||||
self._dict["save_list_required"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: {flag} is enabled but {key} is empty. Fill "
|
||||
"it in the solver parameters (tab Saves), or disable {flag}."
|
||||
)
|
||||
self._dict["abshyd_outside"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: rubarbe_abshyd: abscissa {rk} is outside "
|
||||
"reach {reach}, whose sections go from {first} to {last}."
|
||||
)
|
||||
self._dict["tnprof_outside"] = _translate(
|
||||
"Solver",
|
||||
"rubarbe_tnprof: times {times} s are outside the computation, "
|
||||
"from {tinit} s to {tmax} s. RubarBE saves the earlier ones at "
|
||||
"the first time steps and never reaches the later ones."
|
||||
)
|
||||
self._dict["solid_boundary_invalid"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: solid boundary condition {name} at node "
|
||||
"{node} must have D50 > 0 m and sigma >= 1 (1 for a uniform "
|
||||
"sediment)."
|
||||
)
|
||||
self._dict["multireach_unsupported"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: the Rubar export supports a single reach, "
|
||||
"{count} enabled reaches found."
|
||||
)
|
||||
self._dict["not_enough_profiles"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: reach {reach} needs at least two profiles."
|
||||
)
|
||||
self._dict["rk_not_increasing"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: the profiles of reach {reach} must have "
|
||||
"strictly increasing rk. Sort the profiles of the geometry."
|
||||
)
|
||||
self._dict["boundary_code_unsupported"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: {key}={value} is not supported by the Rubar "
|
||||
"export (single reach)."
|
||||
)
|
||||
self._dict["boundary_missing"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: {setting} requires a {kind} boundary "
|
||||
"condition at node {node}."
|
||||
)
|
||||
self._dict["boundary_unexpected"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: boundary condition {name} ({kind}) at node "
|
||||
"{node} is not used with {setting}. Remove it or change the "
|
||||
"boundary settings in the solver parameters."
|
||||
)
|
||||
self._dict["boundary_ambiguous"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: several {kind} boundary conditions at node "
|
||||
"{node}."
|
||||
)
|
||||
self._dict["structure_section"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: the section of hydraulic structure {name} is "
|
||||
"not a profile of reach {reach}."
|
||||
)
|
||||
self._dict["structure_empty_ignored"] = _translate(
|
||||
"Solver",
|
||||
"Hydraulic structure {name} has no enabled basic structure "
|
||||
"that RubarBE takes into account: it is ignored."
|
||||
)
|
||||
self._dict["structure_too_many"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: hydraulic structure {name} has {count} "
|
||||
"enabled basic structures, RubarBE accepts at most {maximum}."
|
||||
)
|
||||
self._dict["structure_too_many_structures"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: {count} hydraulic structures, RubarBE accepts "
|
||||
"at most {maximum}."
|
||||
)
|
||||
self._dict["structure_same_section"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: hydraulic structures {first} and {second} are "
|
||||
"on the same section (rk {rk}). Put their basic structures in a "
|
||||
"single hydraulic structure."
|
||||
)
|
||||
self._dict["structure_type_ignored"] = _translate(
|
||||
"Solver",
|
||||
"Basic structure {name} of type {type} has no RubarBE "
|
||||
"equivalent: it is not taken into account. RubarBE accepts "
|
||||
"discharge weirs (S1), rectangular orifices (OR) and circular "
|
||||
"orifices (OC)."
|
||||
)
|
||||
self._dict["structure_maximal_loading"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: rectangular orifice {name}: RubarBE has no "
|
||||
"maximal loading elevation. Leave it at 9999.999."
|
||||
)
|
||||
self._dict["structure_siltation"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: circular orifice {name}: RubarBE has no "
|
||||
"siltation height. Set it to 0."
|
||||
)
|
||||
self._dict["structure_invalid"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: basic structure {name} must have a width (or "
|
||||
"diameter) > 0 and a discharge coefficient > 0."
|
||||
)
|
||||
self._dict["structure_loading_below"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: basic structure {name}: the loading elevation "
|
||||
"must be above the weir elevation."
|
||||
)
|
||||
self._dict["structure_format"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: a value of basic structure {name} does not fit "
|
||||
"the 10 columns of RubarBE (3 decimals)."
|
||||
)
|
||||
self._dict["lateral_empty_ignored"] = _translate(
|
||||
"Solver",
|
||||
"Lateral contribution {name} on reach {reach} has no value: it "
|
||||
"is ignored."
|
||||
)
|
||||
self._dict["lateral_solid_no_transport"] = _translate(
|
||||
"Solver",
|
||||
"Solid lateral contribution {name} on reach {reach} is not taken "
|
||||
"into account: sediment transport (rubarbe_trased) is disabled "
|
||||
"in the solver parameters."
|
||||
)
|
||||
self._dict["lateral_solid_unmatched"] = _translate(
|
||||
"Solver",
|
||||
"Solid lateral contribution {name} on reach {reach} is not taken "
|
||||
"into account: RubarBE brings sediment only with water, and no "
|
||||
"liquid lateral contribution has the same sections."
|
||||
)
|
||||
self._dict["lateral_solid_unisol"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: solid lateral contribution {name} on reach "
|
||||
"{reach} gives a solid discharge in kg/s, but the solver "
|
||||
"parameters set the solid unit (rubarbe_sediment_unisol) to a "
|
||||
"concentration. Set it to a mass discharge (kg/s)."
|
||||
)
|
||||
self._dict["lateral_solid_invalid"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: solid lateral contribution {name} on reach "
|
||||
"{reach} must have D50 > 0 m and sigma >= 1 (1 for a uniform "
|
||||
"sediment)."
|
||||
)
|
||||
self._dict["lateral_solid_conflict"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: solid lateral contributions {names} on reach "
|
||||
"{reach} are attached to the same liquid contribution {liquid} "
|
||||
"but have different D50 or sigma. RubarBE accepts a single "
|
||||
"grain size per inflow."
|
||||
)
|
||||
self._dict["lateral_iodev_disabled"] = _translate(
|
||||
"Solver",
|
||||
"Lateral contribution {name} on reach {reach} is not taken into "
|
||||
"account: lateral overflows (rubarbe_iodev) are disabled in the "
|
||||
"solver parameters."
|
||||
)
|
||||
self._dict["lateral_type_unsupported"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: lateral contribution {name} on reach {reach} "
|
||||
"has no type. Choose a type (lateral sources, rain or "
|
||||
"evaporation)."
|
||||
)
|
||||
self._dict["lateral_sections"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: the sections of lateral contribution {name} "
|
||||
"are not profiles of reach {reach}."
|
||||
)
|
||||
self._dict["lateral_not_sorted"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: the times of lateral contribution {name} on "
|
||||
"reach {reach} must be increasing."
|
||||
)
|
||||
self._dict["lateral_overlap"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: lateral contributions {first} and {second} "
|
||||
"on reach {reach} overlap, which RubarBE does not accept. Merge "
|
||||
"them or move one of them."
|
||||
)
|
||||
self._dict["lateral_too_long"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: the lateral contributions give {count} values "
|
||||
"(number of points times number of cells of their zone), "
|
||||
"RubarBE accepts at most {maximum}."
|
||||
)
|
||||
self._dict["boundary_empty_ignored"] = _translate(
|
||||
"Solver",
|
||||
"Boundary condition {name} at node {node} has no value: it is "
|
||||
"ignored."
|
||||
)
|
||||
self._dict["boundary_missing_empty"] = _translate(
|
||||
"Solver",
|
||||
"Empty boundary condition(s) ignored: {names}."
|
||||
)
|
||||
self._dict["boundary_not_sorted"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: the times of boundary condition {name} at "
|
||||
"node {node} must be increasing."
|
||||
)
|
||||
self._dict["upstream_level_unsupported"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: {setting} imposes an upstream water level, "
|
||||
"which the current RubarBE version reads but does not use in "
|
||||
"the computation. Choose another upstream boundary setting."
|
||||
)
|
||||
self._dict["missing_friction"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: no friction defined on reach {reach}."
|
||||
)
|
||||
self._dict["missing_initial_conditions"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: no initial condition defined on reach "
|
||||
"{reach}."
|
||||
)
|
||||
self._dict["discharge_out_of_range"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: initial discharges of reach {reach} must be "
|
||||
"greater than -500 m3/s."
|
||||
)
|
||||
self._dict["duplicate_point_name"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: several points are named '{name}' in "
|
||||
"profile {profile} of reach {reach}."
|
||||
)
|
||||
self._dict["main_channel"] = _translate("Solver", "main channel")
|
||||
self._dict["active_bed"] = _translate("Solver", "active bed")
|
||||
self._dict["bed_limits_invalid"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: in profile {profile} of reach {reach}, the "
|
||||
"{bed} limits (points '{left}' and '{right}', or the profile "
|
||||
"ends) must be ordered from left to right with at least one "
|
||||
"point between them."
|
||||
)
|
||||
self._dict["missing_sediment_layers"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: point {point} of profile {profile} of reach "
|
||||
"{reach} has no sediment layer, which the sediment transport "
|
||||
"requires."
|
||||
)
|
||||
self._dict["invalid_sediment_layer"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: layer '{layer}' of sediment layers '{sl}' "
|
||||
"(profile {profile} of reach {reach}) must have a thickness "
|
||||
">= 0, D50 > 0 m, sigma >= 1 (1 for a uniform sediment) and a "
|
||||
"critical constraint >= 0 Pa."
|
||||
)
|
||||
self._dict["too_many_compartments"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: sediment layers '{sl}' (profile {profile} of "
|
||||
"reach {reach}) give {count} compartments, RubarBE accepts at "
|
||||
"most {maximum}. A non erodible compartment is added under an "
|
||||
"erodible last layer."
|
||||
)
|
||||
self._dict["export_failed"] = _translate(
|
||||
"Solver", "Export failed; task cancelled"
|
||||
|
||||
@@ -42,6 +42,16 @@ OPTION_CHOICES = (
|
||||
)
|
||||
|
||||
|
||||
# Choices read by RubarBE 2021 but never used by its computation: the
|
||||
# upstream level of HAMONT (rubar_init.f90). The export refuses them.
|
||||
UNSUPPORTED_CHOICES = {
|
||||
('rubarbe_condam', '2'): 'La version actuelle de RubarBE ne prend pas '
|
||||
'en compte la cote amont imposée.',
|
||||
('rubarbe_regime', '1'): 'La version actuelle de RubarBE ne prend pas '
|
||||
'en compte le tirant d’eau amont imposé.',
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Field:
|
||||
key: str
|
||||
@@ -89,7 +99,9 @@ def fields():
|
||||
'Nécessite les fichiers de reprise du calcul précédent.', YES_NO)
|
||||
|
||||
choice('condam', 1, 'Condition à la limite amont',
|
||||
'Le choix doit correspondre aux données de la limite amont.',
|
||||
'Le choix doit correspondre aux données de la limite amont. '
|
||||
'La cote imposée n’est pas prise en compte par la version '
|
||||
'actuelle de RubarBE.',
|
||||
{'0': 'Réflexion', '1': 'Débit imposé Q(t)',
|
||||
'2': 'Cote imposée Z(t)', '4': 'Confluence'})
|
||||
choice('condav', 1, 'Condition à la limite aval',
|
||||
@@ -99,8 +111,8 @@ def fields():
|
||||
'3': 'Cote imposée Z(t)', '4': 'Confluence / défluence',
|
||||
'5': 'Régime uniforme', '7': 'Régime critique'})
|
||||
choice('regime', 1, 'Imposer aussi le tirant d’eau amont',
|
||||
'Complète le débit imposé ; incompatible avec une limite '
|
||||
'amont de type "Réflexion".',
|
||||
'Complète le débit imposé ; non pris en compte par la version '
|
||||
'actuelle de RubarBE.',
|
||||
ON_OFF)
|
||||
choice('iodev', 1, 'Déversement latéral',
|
||||
'Nécessite les données de déversements latéraux.',
|
||||
@@ -250,6 +262,17 @@ def fields():
|
||||
'Les abscisses ou instants demandés '
|
||||
'doivent être définis dans les données correspondantes.',
|
||||
YES_NO)
|
||||
add('abshyd', 4, 'Abscisses des hydrogrammes (m)',
|
||||
'Abscisses (PK) où sont enregistrés hydrogrammes et limnigrammes '
|
||||
'au pas de sauvegarde des hydrogrammes, séparées par des espaces '
|
||||
'ou des points-virgules ; 100 au plus. Nécessaires si '
|
||||
'« Hydrogrammes aux abscisses choisies » vaut Oui.',
|
||||
kind='rk_list')
|
||||
add('tnprof', 4, 'Instants de sauvegarde des profils (j:h:min:s ou s)',
|
||||
'Instants séparés par des espaces ou des points-virgules ; 100 au '
|
||||
'plus. Nécessaires si « Profils aux instants choisis » vaut Oui ; '
|
||||
'les sorties complémentaires sont aussi écrites à ces instants.',
|
||||
kind='time_list')
|
||||
for i in range(1, 4):
|
||||
add(f'fdeb{i}', 4, f'Seuil de débit du front {i} (m³/s)',
|
||||
'Débit pour lequel à chaque abscisse, est enregistré le temps '
|
||||
@@ -286,6 +309,18 @@ def validate(field, value):
|
||||
return value
|
||||
if field.kind == 'text':
|
||||
return value
|
||||
if field.kind in ('rk_list', 'time_list'):
|
||||
try:
|
||||
numbers = Rubar3.save_list(value, field.kind == 'time_list')
|
||||
except ValueError as error:
|
||||
raise ValueError(
|
||||
f'Valeur non reconnue : {error}. Séparez les valeurs par '
|
||||
'des espaces ou des points-virgules, avec un point pour '
|
||||
'les décimales.') from None
|
||||
if len(numbers) > Rubar3._max_saves:
|
||||
raise ValueError(f'{Rubar3._max_saves} valeurs au plus '
|
||||
'(limite de RubarBE).')
|
||||
return value
|
||||
if not value and field.optional:
|
||||
return value
|
||||
value = value.replace(',', '.').replace('d', 'E').replace('D', 'E')
|
||||
@@ -352,4 +387,12 @@ def active(field, values):
|
||||
return values.get('rubarbe_' + source) in accepted
|
||||
if key.startswith('fdeb'):
|
||||
return values.get('rubarbe_tf_6', 'n').lower() in ('y', 'o')
|
||||
|
||||
def enabled(flag):
|
||||
return values.get('rubarbe_' + flag, 'n').lower() in ('y', 'o')
|
||||
|
||||
if key == 'abshyd':
|
||||
return enabled('tf_2')
|
||||
if key == 'tnprof':
|
||||
return enabled('tf_4') or enabled('iovis')
|
||||
return True
|
||||
@@ -10,7 +10,8 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from View.SolverParameters.RubarFields import (
|
||||
FIELDS, GROUPS, OPTION_CHOICES, Field, active, canonical, validate,
|
||||
FIELDS, GROUPS, OPTION_CHOICES, UNSUPPORTED_CHOICES, Field, active,
|
||||
canonical, validate,
|
||||
)
|
||||
from View.SolverParameters.UndoCommand import SetCommand
|
||||
|
||||
@@ -293,6 +294,15 @@ class RubarForm(QWidget):
|
||||
index = editor.findData(forbidden)
|
||||
if index >= 0:
|
||||
editor.model().item(index).setEnabled(not blocked)
|
||||
# Unsupported choices stay visible, so a stored value is still
|
||||
# displayed, but cannot be selected anymore
|
||||
for (key, value), reason in UNSUPPORTED_CHOICES.items():
|
||||
editor = self.editors.get(key)
|
||||
index = editor.findData(value) if editor is not None else -1
|
||||
if index >= 0:
|
||||
item = editor.model().item(index)
|
||||
item.setEnabled(False)
|
||||
item.setToolTip(tr(reason))
|
||||
self._refreshing = False
|
||||
|
||||
def _update_context(self, values):
|
||||
@@ -312,6 +322,11 @@ class RubarForm(QWidget):
|
||||
values.get('rubarbe_regime') == '1'):
|
||||
warnings.append(tr('Une limite amont réfléchissante ne permet pas '
|
||||
'd’imposer aussi le tirant d’eau.'))
|
||||
for (key, value), reason in UNSUPPORTED_CHOICES.items():
|
||||
if values.get(key) == value:
|
||||
warnings.append(tr(reason) + tr(
|
||||
' Le calcul sera refusé : choisissez un autre réglage '
|
||||
'de la limite amont.'))
|
||||
try:
|
||||
from tools import old_pamhyr_date_to_timestamp
|
||||
|
||||
|
||||
@@ -35,7 +35,13 @@
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QTableView" name="tableView"/>
|
||||
<widget class="QWidget" name="verticalLayoutWidget_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_table">
|
||||
<item>
|
||||
<widget class="QTableView" name="tableView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout"/>
|
||||
</widget>
|
||||
|
||||
@@ -59,16 +59,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_suspenssion">
|
||||
<attribute name="title">
|
||||
<string>Suspension</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTableView" name="tableView_suspenssion"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout"/>
|
||||
|
||||
+1
-17
@@ -428,15 +428,7 @@ class Config(SQL):
|
||||
new._cmd_solver = f"\"{path}{ext}\" @args @input"
|
||||
self._solvers.append(new)
|
||||
|
||||
# Rubar3
|
||||
ctor = solver_type_list["rubar3"]
|
||||
new = ctor("default-Rubar3")
|
||||
new._description = "Default Pamhyr2 Rubar 3 version"
|
||||
path = os.path.join("@install_dir", "rubar", f"rubarbe{ext}")
|
||||
new._cmd_solver = f""""{path}" @args @input"""
|
||||
self._solvers.append(new)
|
||||
|
||||
# RubarBE (same executable as Rubar3, see notice §10.3)
|
||||
# RubarBE
|
||||
ctor = solver_type_list["rubarbe"]
|
||||
new = ctor("default-RubarBE")
|
||||
new._description = "Default Pamhyr2 RubarBE version"
|
||||
@@ -444,14 +436,6 @@ class Config(SQL):
|
||||
new._cmd_solver = f""""{path}" @args @input"""
|
||||
self._solvers.append(new)
|
||||
|
||||
# Mage fake 7
|
||||
# ctor = solver_type_list["mage_fake7"]
|
||||
# new = ctor("default-mage-fake-7")
|
||||
# new._description = "Default Pamhyr2 mage fake version 7"
|
||||
# new._cmd_solver = f"""\
|
||||
# "@install_dir/mage8/mage_as7{ext}" @args @input"""
|
||||
# self._solvers.append(new)
|
||||
|
||||
# Backup
|
||||
self.backup_enable = True
|
||||
# self.backup_path = ""
|
||||
|
||||
Binary file not shown.
Reference in new issue
Block a user