mirror of
https://gitlab.com/pamhyr/pamhyr2.git
synced 2026-10-30 03:20:41 +01:00
Compare commits
22
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 |
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
|
||||
@@ -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)
|
||||
|
||||
|
||||
+711
-92
@@ -17,6 +17,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
import math
|
||||
from decimal import Decimal, InvalidOperation, localcontext
|
||||
@@ -76,11 +77,14 @@ class Rubar3(CommandLineSolver):
|
||||
("rubarbe_fdeb2", "10"),
|
||||
("rubarbe_fdeb3", "100"),
|
||||
("rubarbe_tf_1", "y"),
|
||||
("rubarbe_tf_2", "y"),
|
||||
# TF(2) and TF(4) need the abscissas and times below
|
||||
("rubarbe_tf_2", "n"),
|
||||
("rubarbe_tf_3", "y"),
|
||||
("rubarbe_tf_4", "y"),
|
||||
("rubarbe_tf_4", "n"),
|
||||
("rubarbe_tf_5", "y"),
|
||||
("rubarbe_tf_6", "n"),
|
||||
("rubarbe_abshyd", ""),
|
||||
("rubarbe_tnprof", ""),
|
||||
("rubarbe_trased", "n"),
|
||||
("rubarbe_optfpc", "0"),
|
||||
# trased parameters
|
||||
@@ -119,10 +123,21 @@ class Rubar3(CommandLineSolver):
|
||||
|
||||
@classmethod
|
||||
def checkers(cls):
|
||||
lst = [
|
||||
]
|
||||
from Checker.RubarBE import (
|
||||
RubarBEReachChecker, RubarBEGeometryChecker,
|
||||
RubarBEBoundaryChecker, RubarBEFrictionChecker,
|
||||
RubarBEInitialConditionsChecker, RubarBELateralChecker,
|
||||
RubarBEStructureChecker, RubarBESavesChecker,
|
||||
)
|
||||
|
||||
return lst
|
||||
return [
|
||||
checker(version=cls._type) for checker in (
|
||||
RubarBEReachChecker, RubarBEGeometryChecker,
|
||||
RubarBEBoundaryChecker, RubarBEFrictionChecker,
|
||||
RubarBEInitialConditionsChecker, RubarBELateralChecker,
|
||||
RubarBEStructureChecker, RubarBESavesChecker,
|
||||
)
|
||||
]
|
||||
|
||||
##########
|
||||
# Export #
|
||||
@@ -150,33 +165,22 @@ class Rubar3(CommandLineSolver):
|
||||
name = study.name.replace(" ", "_")
|
||||
|
||||
values = self._parameter_values(study)
|
||||
if (values.get("rubarbe_regime") == "1" and
|
||||
values.get("rubarbe_condam") == "0"):
|
||||
trad = RunSolverTranslate()
|
||||
message = trad["incompatible_upstream_boundary"]
|
||||
logger.warning(message)
|
||||
if qlog is not None:
|
||||
qlog.put(trad["warning"].format(message=message))
|
||||
return False
|
||||
|
||||
trased = self._parameter_flag(study, "trased") == "O"
|
||||
if trased:
|
||||
# Notice §3.6: HYDROS is required at each upstream boundary
|
||||
missing = self._reaches_without_solid_boundary(study)
|
||||
if missing:
|
||||
trad = RunSolverTranslate()
|
||||
message = trad["missing_solid_boundary"].format(
|
||||
reaches=", ".join(missing)
|
||||
)
|
||||
logger.warning(message)
|
||||
if qlog is not None:
|
||||
qlog.put(trad["warning"].format(message=message))
|
||||
return False
|
||||
|
||||
# Check the hydraulic data before writing any file
|
||||
try:
|
||||
self._check_regime(values)
|
||||
hydraulics = self._hydraulic_data(study, values)
|
||||
geometry = self._geometry_data(hydraulics["edge"], trased)
|
||||
solid = self._solid_boundary(
|
||||
study.river, hydraulics["edge"], values, trased,
|
||||
hydraulics["warnings"])
|
||||
saves = self._save_data(values, hydraulics)
|
||||
laterals = self._lateral_data(
|
||||
study.river, hydraulics, values, trased,
|
||||
hydraulics["warnings"])
|
||||
structures = self._structure_data(
|
||||
study.river, hydraulics, hydraulics["warnings"])
|
||||
except RubarExportError as error:
|
||||
self._cancel(qlog, str(error))
|
||||
return False
|
||||
@@ -194,10 +198,19 @@ class Rubar3(CommandLineSolver):
|
||||
self._export_geomac_i(geometry, repertory, qlog, name=name)
|
||||
self._export_mail(study, repertory, qlog, name=name)
|
||||
self._export_devers(study, repertory, qlog, name=name)
|
||||
if laterals is not None:
|
||||
self._export_hydevs(laterals, trased, repertory, qlog, name=name)
|
||||
self._export_ouvrag(structures, repertory, qlog, name=name)
|
||||
self._export_condin(hydraulics, repertory, qlog, name=name)
|
||||
self._export_stricklers(hydraulics, repertory, qlog, name=name)
|
||||
self._export_boundaries(hydraulics, repertory, qlog, name=name)
|
||||
# self._export_abshyd(study, repertory, qlog, name=name)
|
||||
if solid is not None:
|
||||
self._export_hydros(solid, hydraulics["tinit"], repertory, qlog,
|
||||
name=name)
|
||||
if trased:
|
||||
self._export_condins(geometry, hydraulics["tinit"], repertory,
|
||||
qlog, name=name)
|
||||
self._export_saves(saves, repertory, qlog, name=name)
|
||||
|
||||
return True
|
||||
|
||||
@@ -217,15 +230,25 @@ class Rubar3(CommandLineSolver):
|
||||
"5": (), "7": ()}
|
||||
_boundary_kinds = {"TD": "hydrograph Q(t)", "TZ": "water level Z(t)",
|
||||
"ZD": "rating curve Q(Z)",
|
||||
"PC": "punctual contribution"}
|
||||
"PC": "punctual contribution",
|
||||
"SL": "solid discharge Qs(t)"}
|
||||
# NCLMAX (variables.f90): points of all the solid hydrographs
|
||||
_max_solid_points = 1000
|
||||
|
||||
def _hydraulic_data(self, study, values):
|
||||
"""Check the single reach data needed by DONNEE, MAIL, CONDIN,
|
||||
FROT, HYDRO and CONDAV, and return them. Raise
|
||||
RubarExportError with a user message"""
|
||||
@staticmethod
|
||||
def _check_regime(values):
|
||||
"""REGIME=1 imposes the water depth upstream, which a reflecting
|
||||
upstream boundary (CONDAM=0) cannot do"""
|
||||
if (values.get("rubarbe_regime") == "1" and
|
||||
values.get("rubarbe_condam") == "0"):
|
||||
raise RubarExportError(
|
||||
RunSolverTranslate()["incompatible_upstream_boundary"])
|
||||
|
||||
@staticmethod
|
||||
def _single_reach(river):
|
||||
"""The single enabled reach and its profiles, with at least two
|
||||
profiles of increasing rk. Raise RubarExportError"""
|
||||
trad = RunSolverTranslate()
|
||||
river = study.river
|
||||
|
||||
edges = river.enable_edges()
|
||||
if len(edges) != 1:
|
||||
raise RubarExportError(trad["multireach_unsupported"].format(
|
||||
@@ -239,9 +262,17 @@ class Rubar3(CommandLineSolver):
|
||||
if any(b <= a for a, b in zip(rks, rks[1:])):
|
||||
raise RubarExportError(trad["rk_not_increasing"].format(
|
||||
reach=edge.name))
|
||||
return edge, profiles
|
||||
|
||||
def _hydraulic_data(self, study, values):
|
||||
"""Check the single reach data needed by DONNEE, MAIL, CONDIN,
|
||||
FROT, HYDRO and CONDAV, and return them. Raise
|
||||
RubarExportError with a user message"""
|
||||
river = study.river
|
||||
edge, profiles = self._single_reach(river)
|
||||
|
||||
warnings = []
|
||||
boundaries = self._check_boundaries(river, edge, values)
|
||||
boundaries = self._check_boundaries(river, edge, values, warnings)
|
||||
|
||||
return {
|
||||
"edge": edge,
|
||||
@@ -307,17 +338,11 @@ class Rubar3(CommandLineSolver):
|
||||
name=names[marker], profile=self._profile_label(profile),
|
||||
reach=edge.name))
|
||||
|
||||
# Defaults of RubarBE: first and last points for G and D, G and D
|
||||
# for g and d. Erosion only applies strictly between g and d
|
||||
def index(marker, default):
|
||||
return markers.index(marker) if marker in markers else default
|
||||
|
||||
left = index("G", 0)
|
||||
right = index("D", len(points) - 1)
|
||||
left, right, active_left, active_right = self._bed_limits(markers)
|
||||
limits = [("main_channel", "rg", "rd", left, right)]
|
||||
if trased:
|
||||
limits.append(("active_bed", "g", "d",
|
||||
index("g", left), index("d", right)))
|
||||
active_left, active_right))
|
||||
for bed, lname, rname, i, j in limits:
|
||||
if j - i < 2:
|
||||
raise RubarExportError(trad["bed_limits_invalid"].format(
|
||||
@@ -325,6 +350,18 @@ class Rubar3(CommandLineSolver):
|
||||
profile=self._profile_label(profile), reach=edge.name))
|
||||
return markers
|
||||
|
||||
@staticmethod
|
||||
def _bed_limits(markers):
|
||||
"""Indices of the G, D, g and d points of a section. Defaults of
|
||||
RubarBE: first and last points for G and D, G and D for g and d.
|
||||
Erosion only applies strictly between g and d"""
|
||||
def index(marker, default):
|
||||
return markers.index(marker) if marker in markers else default
|
||||
|
||||
left = index("G", 0)
|
||||
right = index("D", len(markers) - 1)
|
||||
return left, right, index("g", left), index("d", right)
|
||||
|
||||
def _compartments(self, edge, profile, number, point):
|
||||
"""Sediment layers of a point as RubarBE compartments, from the
|
||||
top: (top elevation, D, S, critical stress). A non erodible
|
||||
@@ -372,7 +409,7 @@ class Rubar3(CommandLineSolver):
|
||||
return float(old_pamhyr_date_to_timestamp(value))
|
||||
return float(value)
|
||||
|
||||
def _check_boundaries(self, river, edge, values):
|
||||
def _check_boundaries(self, river, edge, values, warnings):
|
||||
"""Liquid boundary conditions used for CONDAM, REGIME and CONDAV.
|
||||
Return {'hydro': bc, 'condav': bc} (or None)"""
|
||||
trad = RunSolverTranslate()
|
||||
@@ -408,12 +445,12 @@ class Rubar3(CommandLineSolver):
|
||||
|
||||
upstream = self._select_boundaries(
|
||||
at(edge.node1), self._upstream_types[condam], edge.node1,
|
||||
{"TD": f"CONDAM={condam}"}, f"CONDAM={condam}"
|
||||
{"TD": f"CONDAM={condam}"}, f"CONDAM={condam}", warnings
|
||||
)
|
||||
downstream = self._select_boundaries(
|
||||
at(edge.node2), self._downstream_types[condav], edge.node2,
|
||||
{"ZD": f"CONDAV={condav}", "TZ": f"CONDAV={condav}"},
|
||||
f"CONDAV={condav}"
|
||||
f"CONDAV={condav}", warnings
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -421,9 +458,27 @@ class Rubar3(CommandLineSolver):
|
||||
"condav": downstream.get("ZD", downstream.get("TZ")),
|
||||
}
|
||||
|
||||
def _select_boundaries(self, bcs, expected, node, required_by, setting):
|
||||
@staticmethod
|
||||
def _ignore_empty(bcs, node, warnings, meaningful=()):
|
||||
"""A boundary condition without value carries no data: it is
|
||||
ignored with a warning, except for the types where an empty
|
||||
condition has a meaning. Return (kept, ignored)"""
|
||||
trad = RunSolverTranslate()
|
||||
kept, ignored = [], []
|
||||
for bc in bcs:
|
||||
if len(bc) == 0 and bc.bctype not in meaningful:
|
||||
warnings.append(trad["boundary_empty_ignored"].format(
|
||||
name=bc.name, node=node.name))
|
||||
ignored.append(bc)
|
||||
else:
|
||||
kept.append(bc)
|
||||
return kept, ignored
|
||||
|
||||
def _select_boundaries(self, bcs, expected, node, required_by, setting,
|
||||
warnings, meaningful=()):
|
||||
trad = RunSolverTranslate()
|
||||
selected = {}
|
||||
bcs, ignored = self._ignore_empty(bcs, node, warnings, meaningful)
|
||||
|
||||
for bc in bcs:
|
||||
kind = self._boundary_kinds[bc.bctype]
|
||||
@@ -434,9 +489,6 @@ class Rubar3(CommandLineSolver):
|
||||
if bc.bctype in selected:
|
||||
raise RubarExportError(trad["boundary_ambiguous"].format(
|
||||
kind=kind, node=node.name))
|
||||
if len(bc) == 0:
|
||||
raise RubarExportError(trad["boundary_empty"].format(
|
||||
name=bc.name, node=node.name))
|
||||
times = [d[0] for d in bc.data]
|
||||
if bc.bctype != "ZD" and any(
|
||||
b < a for a, b in zip(times, times[1:])):
|
||||
@@ -446,12 +498,422 @@ class Rubar3(CommandLineSolver):
|
||||
|
||||
for bctype in expected:
|
||||
if bctype not in selected:
|
||||
raise RubarExportError(trad["boundary_missing"].format(
|
||||
message = trad["boundary_missing"].format(
|
||||
setting=required_by[bctype],
|
||||
kind=self._boundary_kinds[bctype], node=node.name))
|
||||
kind=self._boundary_kinds[bctype], node=node.name)
|
||||
# The required condition may be the ignored empty one
|
||||
empty = [bc.name for bc in ignored if bc.bctype == bctype]
|
||||
if empty:
|
||||
message += " " + trad["boundary_missing_empty"].format(
|
||||
names=", ".join(empty))
|
||||
raise RubarExportError(message)
|
||||
|
||||
return selected
|
||||
|
||||
def _solid_boundary(self, river, edge, values, trased, warnings):
|
||||
"""Solid boundary condition written in HYDROS. RubarBE reads it
|
||||
at the upstream node when TRASED is enabled and CONDAM=1 (CONDAM=2
|
||||
is refused), and has no downstream solid condition (rubar_init.f90,
|
||||
inits). Without sediment transport the solid conditions are
|
||||
ignored. At the upstream node, an empty solid condition means a
|
||||
supply at equilibrium in Pamhyr2 (Mage 'default'), written as a
|
||||
negative Qs (_export_hydros). Return the condition or None"""
|
||||
if not trased:
|
||||
return None
|
||||
trad = RunSolverTranslate()
|
||||
condam = values.get("rubarbe_condam", "")
|
||||
|
||||
solid = [
|
||||
bc for bc in river.boundary_condition.get_tab("solid")
|
||||
if not bc.is_deleted() and bc.bctype == "SL"
|
||||
]
|
||||
downstream, _ = self._ignore_empty(
|
||||
[bc for bc in solid if bc.node == edge.node2], edge.node2,
|
||||
warnings)
|
||||
if downstream:
|
||||
raise RubarExportError(
|
||||
trad["solid_boundary_downstream"].format(
|
||||
name=downstream[0].name, node=edge.node2.name))
|
||||
|
||||
expected = ("SL",) if condam == "1" else ()
|
||||
selected = self._select_boundaries(
|
||||
[bc for bc in solid if bc.node == edge.node1], expected,
|
||||
edge.node1, {"SL": f"TRASED=O, CONDAM={condam}"},
|
||||
f"CONDAM={condam}", warnings, meaningful=("SL",)
|
||||
)
|
||||
bc = selected.get("SL")
|
||||
if bc is None:
|
||||
return None
|
||||
|
||||
if len(bc) > self._max_solid_points:
|
||||
raise RubarExportError(trad["solid_boundary_too_long"].format(
|
||||
name=bc.name, node=edge.node1.name, count=len(bc),
|
||||
maximum=self._max_solid_points))
|
||||
# RubarBE stops on a zero D or S
|
||||
if not (bc.d50 > 0 and bc.sigma >= 1):
|
||||
raise RubarExportError(trad["solid_boundary_invalid"].format(
|
||||
name=bc.name, node=edge.node1.name))
|
||||
return bc
|
||||
|
||||
############
|
||||
# Laterals #
|
||||
############
|
||||
|
||||
# Pamhyr2 lateral contribution types; evaporation is a withdrawal
|
||||
_lateral_signs = {"LC": 1.0, "RA": 1.0, "EV": -1.0}
|
||||
# QMU, TMU (variables.f90, LMAX*20): RubarBE copies the series of an
|
||||
# inflow in each cell of its zone, without checking this size
|
||||
_max_lateral_points = 3000 * 20
|
||||
|
||||
def _lateral_data(self, river, hydraulics, values, trased, warnings):
|
||||
"""Lateral inflows of HYDEVS, read when IODEV=O (rubar_init.f90,
|
||||
init2). Return [(first cell, last cell, [(t, q), ...]), ...]
|
||||
sorted from upstream, or None when IODEV=N.
|
||||
|
||||
A contribution from section b to section e (1-based) covers the
|
||||
cells b+1 to e, i.e. from section b to section e (RubarBE divides
|
||||
the discharge by this length); a contribution at a single section
|
||||
b covers the cell downstream of it (upstream of it for the last
|
||||
section). The first and last cells of the reach are never used,
|
||||
as RubarBE requires. With IODEV=N, the contributions are ignored
|
||||
with a warning.
|
||||
|
||||
RubarBE brings sediment only with a liquid inflow, on the same
|
||||
cells: a solid contribution (kg/s, UNISOL=1) is attached to the
|
||||
liquid contribution of the same zone, whose block gets its D50
|
||||
and sigma; the times of both series are merged (linear
|
||||
interpolation, constant outside). Several solid contributions of
|
||||
a zone are summed when they have the same grain size. A solid
|
||||
contribution without liquid one, or without sediment transport,
|
||||
is ignored with a warning. A liquid inflow without solid
|
||||
contribution brings clear water (Qs = 0, D = S = 0)."""
|
||||
trad = RunSolverTranslate()
|
||||
edge = hydraulics["edge"]
|
||||
profiles = hydraulics["profiles"]
|
||||
iodev = values.get("rubarbe_iodev", "n").lower() in ("y", "o")
|
||||
lst = river.lateral_contribution
|
||||
|
||||
def on_reach(tab):
|
||||
# A contribution without value carries no data: ignored
|
||||
kept = []
|
||||
for lc in lst.get_tab(tab):
|
||||
if lc.is_deleted() or lc.reach != edge:
|
||||
continue
|
||||
if len(lc) == 0:
|
||||
warnings.append(trad["lateral_empty_ignored"].format(
|
||||
name=lc.name, reach=edge.name))
|
||||
else:
|
||||
kept.append(lc)
|
||||
return kept
|
||||
|
||||
lcs = on_reach("liquid")
|
||||
solids = on_reach("solid")
|
||||
|
||||
# Not read by RubarBE: the user is warned, the computation runs
|
||||
# without them
|
||||
if not iodev:
|
||||
for lc in lcs + solids:
|
||||
warnings.append(trad["lateral_iodev_disabled"].format(
|
||||
name=lc.name, reach=edge.name))
|
||||
return None
|
||||
|
||||
if not trased:
|
||||
for lc in solids:
|
||||
warnings.append(trad["lateral_solid_no_transport"].format(
|
||||
name=lc.name, reach=edge.name))
|
||||
solids = []
|
||||
if solids and values.get("rubarbe_sediment_unisol", "1") != "1":
|
||||
raise RubarExportError(trad["lateral_solid_unisol"].format(
|
||||
name=solids[0].name, reach=edge.name))
|
||||
|
||||
index = {id(p): i for i, p in enumerate(profiles, 1)}
|
||||
count = len(profiles)
|
||||
|
||||
def zone_and_data(lc):
|
||||
if (id(lc.begin_section) not in index or
|
||||
id(lc.end_section) not in index):
|
||||
raise RubarExportError(trad["lateral_sections"].format(
|
||||
name=lc.name, reach=edge.name))
|
||||
b, e = sorted((index[id(lc.begin_section)],
|
||||
index[id(lc.end_section)]))
|
||||
zone = (b + 1, e) if b < e else ((min(b + 1, count),) * 2)
|
||||
|
||||
data = [(float(d[0]), float(d[1])) for d in lc.data]
|
||||
times = [t for t, _ in data]
|
||||
if any(t1 < t0 for t0, t1 in zip(times, times[1:])):
|
||||
raise RubarExportError(trad["lateral_not_sorted"].format(
|
||||
name=lc.name, reach=edge.name))
|
||||
return zone, data
|
||||
|
||||
blocks = []
|
||||
for lc in lcs:
|
||||
if lc.lctype not in self._lateral_signs:
|
||||
raise RubarExportError(trad["lateral_type_unsupported"].format(
|
||||
name=lc.name, reach=edge.name))
|
||||
(first, last), data = zone_and_data(lc)
|
||||
sign = self._lateral_signs[lc.lctype]
|
||||
blocks.append((first, last, [(t, sign * q) for t, q in data],
|
||||
lc.name))
|
||||
|
||||
blocks.sort(key=lambda block: block[0])
|
||||
for (_, last, _, name0), (first, _, _, name1) in zip(
|
||||
blocks, blocks[1:]):
|
||||
if first <= last:
|
||||
raise RubarExportError(trad["lateral_overlap"].format(
|
||||
first=name0, second=name1, reach=edge.name))
|
||||
|
||||
# Solid contributions grouped by the zone of their liquid one
|
||||
zones = {(first, last) for first, last, _, _ in blocks}
|
||||
sediment = {}
|
||||
for lc in solids:
|
||||
zone, data = zone_and_data(lc)
|
||||
if not (lc.d50 > 0 and lc.sigma >= 1):
|
||||
raise RubarExportError(trad["lateral_solid_invalid"].format(
|
||||
name=lc.name, reach=edge.name))
|
||||
if zone not in zones:
|
||||
warnings.append(trad["lateral_solid_unmatched"].format(
|
||||
name=lc.name, reach=edge.name))
|
||||
continue
|
||||
sediment.setdefault(zone, []).append((lc, data))
|
||||
|
||||
result = []
|
||||
for first, last, data, name in blocks:
|
||||
attached = sediment.get((first, last), [])
|
||||
grains = {(lc.d50, lc.sigma) for lc, _ in attached}
|
||||
if len(grains) > 1:
|
||||
raise RubarExportError(trad["lateral_solid_conflict"].format(
|
||||
names=", ".join(lc.name for lc, _ in attached),
|
||||
liquid=name, reach=edge.name))
|
||||
|
||||
times = sorted({t for t, _ in data} |
|
||||
{t for _, solid in attached for t, _ in solid})
|
||||
|
||||
def at(series):
|
||||
return np.interp(times, [t for t, _ in series],
|
||||
[v for _, v in series])
|
||||
|
||||
q = at(data)
|
||||
qs = sum((at(solid) for _, solid in attached),
|
||||
np.zeros(len(times)))
|
||||
length = (self._section_rk(profiles[last - 1]) -
|
||||
self._section_rk(profiles[first - 2]))
|
||||
result.append((
|
||||
first, last,
|
||||
[(float(t), float(v), float(vs))
|
||||
for t, v, vs in zip(times, q, qs)],
|
||||
next(iter(grains), (0.0, 0.0)),
|
||||
length,
|
||||
))
|
||||
|
||||
points = sum((last - first + 1) * len(data)
|
||||
for first, last, data, _, _ in result)
|
||||
if points > self._max_lateral_points:
|
||||
raise RubarExportError(trad["lateral_too_long"].format(
|
||||
count=points, maximum=self._max_lateral_points))
|
||||
|
||||
return result
|
||||
|
||||
##############
|
||||
# Structures #
|
||||
##############
|
||||
|
||||
# Pamhyr2 basic structures (those of Mage) with a RubarBE equivalent
|
||||
# (notice, annex 5): weir and rectangular orifice as a weir-orifice D,
|
||||
# circular orifice as O
|
||||
_structure_types = {"S1": "D", "OR": "D", "OC": "O"}
|
||||
# Loading elevation of a weir that never loads (default of Pamhyr2)
|
||||
_no_loading = 9999.999
|
||||
# nou1Dmax, noe1Dmax (variables.f90)
|
||||
_max_structures = 50
|
||||
_max_elementary = 10
|
||||
|
||||
def _structure_data(self, river, hydraulics, warnings):
|
||||
"""Structures of OUVRAG (rubar_init.f90, LOUVR1D). A Pamhyr2
|
||||
structure sits on a section: RubarBE structure on this single
|
||||
interface, with reference -2 (the discharge is that of the
|
||||
structure only), as for Mage. Disabled structures are skipped, as
|
||||
for Mage. Return [(rk, [(type, L, zd, zm, coefficient), ...]), ...]
|
||||
sorted from upstream. Raise RubarExportError with a user message"""
|
||||
trad = RunSolverTranslate()
|
||||
edge = hydraulics["edge"]
|
||||
sections = {id(p) for p in hydraulics["profiles"]}
|
||||
|
||||
structures = {}
|
||||
for hs in river.hydraulic_structures.lst:
|
||||
if (hs.is_deleted() or not hs.enabled
|
||||
or hs.input_reach is None or hs.input_section is None
|
||||
or hs.input_reach != edge):
|
||||
continue
|
||||
if id(hs.input_section) not in sections:
|
||||
raise RubarExportError(trad["structure_section"].format(
|
||||
name=hs.name, reach=edge.name))
|
||||
|
||||
# Basic structures without RubarBE equivalent are not taken
|
||||
# into account: the user is warned
|
||||
lines = []
|
||||
for bhs in hs.basic_structures:
|
||||
if not bhs.enabled or bhs.is_deleted():
|
||||
continue
|
||||
if bhs.type not in self._structure_types:
|
||||
warnings.append(trad["structure_type_ignored"].format(
|
||||
name=self._basic_label(hs, bhs), type=bhs.type))
|
||||
continue
|
||||
lines.append(self._elementary_structure(hs, bhs))
|
||||
if not lines:
|
||||
warnings.append(trad["structure_empty_ignored"].format(
|
||||
name=hs.name))
|
||||
continue
|
||||
if len(lines) > self._max_elementary:
|
||||
raise RubarExportError(trad["structure_too_many"].format(
|
||||
name=hs.name, count=len(lines),
|
||||
maximum=self._max_elementary))
|
||||
|
||||
rk = self._section_rk(hs.input_section)
|
||||
if rk in structures:
|
||||
raise RubarExportError(trad["structure_same_section"].format(
|
||||
first=structures[rk][1], second=hs.name, rk=f"{rk:g}"))
|
||||
structures[rk] = (lines, hs.name)
|
||||
|
||||
if len(structures) > self._max_structures:
|
||||
raise RubarExportError(trad["structure_too_many_structures"]
|
||||
.format(count=len(structures),
|
||||
maximum=self._max_structures))
|
||||
return [(rk, structures[rk][0]) for rk in sorted(structures)]
|
||||
|
||||
@staticmethod
|
||||
def _basic_label(hs, bhs):
|
||||
return f"{bhs.name or bhs.type} ({hs.name})"
|
||||
|
||||
def _elementary_structure(self, hs, bhs):
|
||||
"""RubarBE line of a basic structure of a type of _structure_types:
|
||||
(type, L, zd, zm, coefficient) for D (zm: loading elevation),
|
||||
(type, L, zd, D, coefficient) for O (L: pipe length, none in
|
||||
Pamhyr2: 0, a thin wall orifice)"""
|
||||
trad = RunSolverTranslate()
|
||||
label = self._basic_label(hs, bhs)
|
||||
values = [float(v.value) for v in bhs.parameters]
|
||||
if bhs.type == "S1":
|
||||
width, elevation, coefficient = values
|
||||
line = ("D", width, elevation, self._no_loading, coefficient)
|
||||
elif bhs.type == "OR":
|
||||
width, elevation, loading, coefficient, maximal = values
|
||||
if maximal < self._no_loading:
|
||||
raise RubarExportError(trad["structure_maximal_loading"]
|
||||
.format(name=label))
|
||||
line = ("D", width, elevation, loading, coefficient)
|
||||
else:
|
||||
diameter, elevation, siltation, coefficient = values
|
||||
if siltation != 0:
|
||||
raise RubarExportError(trad["structure_siltation"].format(
|
||||
name=label))
|
||||
line = ("O", 0.0, elevation, diameter, coefficient)
|
||||
|
||||
kind, size, zd, zm, coefficient = line
|
||||
if (size <= 0 if kind == "D" else zm <= 0) or coefficient <= 0:
|
||||
raise RubarExportError(trad["structure_invalid"].format(
|
||||
name=label))
|
||||
if kind == "D" and zm <= zd:
|
||||
raise RubarExportError(trad["structure_loading_below"].format(
|
||||
name=label))
|
||||
# Fixed columns (A1, A9, 4F10.0)
|
||||
if any(len(f"{v:10.3f}") > 10 for v in line[1:]):
|
||||
raise RubarExportError(trad["structure_format"].format(
|
||||
name=label))
|
||||
return line
|
||||
|
||||
#########
|
||||
# Saves #
|
||||
#########
|
||||
|
||||
# NBHYPR (variables.f90): abscissas of ABSHYD and times of TNPROF.
|
||||
# RubarBE does not check this size
|
||||
_max_saves = 100
|
||||
|
||||
@staticmethod
|
||||
def save_list(text, times=False):
|
||||
"""Numbers of a list parameter (ABSHYD abscissas, TNPROF times),
|
||||
separated by spaces or semicolons. Times are in seconds or in
|
||||
days:hours:minutes:seconds. Raise ValueError with the invalid
|
||||
token"""
|
||||
values = []
|
||||
for token in re.split(r"[\s;]+", text.strip()):
|
||||
if token == "":
|
||||
continue
|
||||
try:
|
||||
if times and ":" in token:
|
||||
parts = token.split(":")
|
||||
if len(parts) != 4 or not all(
|
||||
p.isascii() and p.isdigit() for p in parts):
|
||||
raise ValueError(token)
|
||||
value = float(sum(
|
||||
int(p) * factor for p, factor in
|
||||
zip(parts, (86400, 3600, 60, 1))))
|
||||
else:
|
||||
value = float(token)
|
||||
except ValueError:
|
||||
raise ValueError(token) from None
|
||||
if not math.isfinite(value):
|
||||
raise ValueError(token)
|
||||
values.append(value)
|
||||
return values
|
||||
|
||||
def _save_data(self, values, hydraulics):
|
||||
"""Abscissas of ABSHYD, read when TF(2)=O, and times of TNPROF,
|
||||
always read: they give the saves of PROFIL and PROFILS when
|
||||
TF(4)=O and of the complementary outputs when IOVIS=O
|
||||
(rubar_init.f90, init2; rubarbe.f90, SAVEVISU). Return
|
||||
{'abshyd': [rk] or None, 'tnprof': [t]}"""
|
||||
trad = RunSolverTranslate()
|
||||
lists = {}
|
||||
for key, times in (("abshyd", False), ("tnprof", True)):
|
||||
key = f"rubarbe_{key}"
|
||||
try:
|
||||
lists[key] = self.save_list(values.get(key, ""), times)
|
||||
except ValueError as error:
|
||||
raise RubarExportError(trad["save_list_invalid"].format(
|
||||
key=key, value=str(error)))
|
||||
if len(lists[key]) > self._max_saves:
|
||||
raise RubarExportError(trad["save_list_too_long"].format(
|
||||
key=key, count=len(lists[key]),
|
||||
maximum=self._max_saves))
|
||||
|
||||
def enabled(flag):
|
||||
return values.get(f"rubarbe_{flag}", "n").lower() in ("y", "o")
|
||||
|
||||
for flag, key in (("tf_2", "rubarbe_abshyd"),
|
||||
("tf_4", "rubarbe_tnprof")):
|
||||
if enabled(flag) and not lists[key]:
|
||||
raise RubarExportError(trad["save_list_required"].format(
|
||||
flag=f"rubarbe_{flag}", key=key))
|
||||
|
||||
# RubarBE looks for each abscissa between two centres of cells,
|
||||
# from the first to the last section
|
||||
abshyd = None
|
||||
if enabled("tf_2"):
|
||||
edge = hydraulics["edge"]
|
||||
first = self._section_rk(hydraulics["profiles"][0])
|
||||
last = self._section_rk(hydraulics["profiles"][-1])
|
||||
for rk in lists["rubarbe_abshyd"]:
|
||||
if not first <= round(rk, 3) <= last:
|
||||
raise RubarExportError(trad["abshyd_outside"].format(
|
||||
rk=f"{rk:g}", first=f"{first:g}", last=f"{last:g}",
|
||||
reach=edge.name))
|
||||
abshyd = lists["rubarbe_abshyd"]
|
||||
|
||||
# Times outside the computation do not falsify it: RubarBE saves
|
||||
# the earlier ones at the first time steps and never reaches the
|
||||
# later ones
|
||||
tinit = hydraulics["tinit"]
|
||||
tmax = self._seconds(values.get("rubarbe_tmax", "0"))
|
||||
tnprof = sorted(lists["rubarbe_tnprof"])
|
||||
outside = [t for t in tnprof if not tinit <= t <= tmax]
|
||||
if outside:
|
||||
hydraulics["warnings"].append(trad["tnprof_outside"].format(
|
||||
times=" ".join(f"{t:g}" for t in outside),
|
||||
tinit=f"{tinit:g}", tmax=f"{tmax:g}"))
|
||||
|
||||
return {"abshyd": abshyd, "tnprof": tnprof}
|
||||
|
||||
@staticmethod
|
||||
def _interpolated_frictions(edge):
|
||||
"""Minor and medium Strickler at each centre of cell. The model
|
||||
@@ -578,20 +1040,6 @@ class Rubar3(CommandLineSolver):
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _reaches_without_solid_boundary(study):
|
||||
river = study.river
|
||||
solid = [
|
||||
bc for bc in river.boundary_condition.get_tab("solid")
|
||||
if not bc.is_deleted() and bc.bctype == "SL" and len(bc) > 0
|
||||
]
|
||||
|
||||
return [
|
||||
edge.name for edge in river.enable_edges()
|
||||
if river.is_upstream_node(edge.node1)
|
||||
and not any(bc.node == edge.node1 for bc in solid)
|
||||
]
|
||||
|
||||
def _parameter_values(self, study):
|
||||
return {p.name: str(p.value).strip()
|
||||
for p in study.river.get_params(self.type).parameters}
|
||||
@@ -825,29 +1273,33 @@ class Rubar3(CommandLineSolver):
|
||||
|
||||
self._write_parameters(repertory, f"mail.{name}", lines)
|
||||
|
||||
def _export_abshyd(self, study, repertory, qlog, name="0"):
|
||||
def _export_saves(self, saves, repertory, qlog, name="0"):
|
||||
"""ABSHYD (number, then I4, 1X, F11.3 per abscissa; single reach,
|
||||
its number is omitted) when TF(2)=O, and TNPROF (number, then
|
||||
index and time per line). TNPROF is always read by RubarBE: an
|
||||
empty list is written as 0, so that a previous TNPROF of the
|
||||
directory is not used"""
|
||||
if saves["abshyd"] is not None:
|
||||
if qlog is not None:
|
||||
qlog.put("Export ABSHYD file")
|
||||
lines = [f"{len(saves['abshyd']):>4}\n"] + [
|
||||
f"{i:>4} {rk:>11.3f}\n"
|
||||
for i, rk in enumerate(saves["abshyd"], 1)
|
||||
]
|
||||
self._write_parameters(repertory, f"abshyd.{name}", lines)
|
||||
|
||||
if qlog is not None:
|
||||
qlog.put("Export ABSHYD file")
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
repertory, f"abshyd.{name}"
|
||||
), "w+"
|
||||
) as f:
|
||||
reach_ind = 1
|
||||
for edge in study.river.enable_edges():
|
||||
reach = edge.reach
|
||||
lm = len(reach) + 1
|
||||
f.write(f"{lm:>13}\n")
|
||||
|
||||
ind = 1
|
||||
for mail in reach.inter_profiles_rk():
|
||||
f.write(f"{ind:>4} {mail:15.3f} {reach_ind:>4}\n")
|
||||
ind += 1
|
||||
|
||||
reach_ind += 1
|
||||
qlog.put("Export TNPROF file")
|
||||
lines = [f"{len(saves['tnprof']):>12}\n"] + [
|
||||
f"{i:>12} {t:>24.3f}\n"
|
||||
for i, t in enumerate(saves["tnprof"], 1)
|
||||
]
|
||||
self._write_parameters(repertory, f"tnprof.{name}", lines)
|
||||
|
||||
def _export_devers(self, study, repertory, qlog, name="0"):
|
||||
"""DEVERS, required by RubarBE when IODEV=O: per cell, weir
|
||||
level, coefficient and exponent (notice annex 1). Pamhyr2 has no
|
||||
lateral weir data: a zero coefficient everywhere (no overflow)"""
|
||||
if qlog is not None:
|
||||
qlog.put("Export DEVERS file")
|
||||
|
||||
@@ -869,6 +1321,44 @@ class Rubar3(CommandLineSolver):
|
||||
|
||||
reach_ind += 1
|
||||
|
||||
def _export_hydevs(self, laterals, trased, repertory, qlog, name="0"):
|
||||
"""HYDEVS: per inflow, first and last cells and number of points,
|
||||
then (Q, t) per point (rubar_init.f90, init2). With sediment
|
||||
transport, D and S on the first line and (Q, t, Qs, ts) per point:
|
||||
Pamhyr2 gives no sediment for a liquid inflow, so Qs = 0 (and
|
||||
D = S = 0, the sediment of the main channel). Written, possibly
|
||||
empty, so that a previous HYDEVS of the directory is not used"""
|
||||
if qlog is not None:
|
||||
qlog.put("Export HYDEVS file")
|
||||
lines = []
|
||||
for first, last, data, (d50, sigma), length in laterals:
|
||||
if trased:
|
||||
lines.append(
|
||||
f"{first} {last} {len(data)} {d50!r} {sigma!r}\n")
|
||||
lines += [f"{q!r} {t!r} {qs / length!r} {t!r}\n"
|
||||
for t, q, qs in data]
|
||||
else:
|
||||
lines.append(f"{first} {last} {len(data)}\n")
|
||||
lines += [f"{q!r} {t!r}\n" for t, q, _ in data]
|
||||
self._write_parameters(repertory, f"hydevs.{name}", lines)
|
||||
|
||||
def _export_ouvrag(self, structures, repertory, qlog, name="0"):
|
||||
"""OUVRAG (notice annex 1, rubar_init.f90, LOUVR1D): per
|
||||
structure, upstream abscissa, reference and reach, downstream
|
||||
abscissa, number of basic structures and reach, then one line per
|
||||
basic structure in (A1, A9, 4F10.0). Written, possibly empty, so
|
||||
that a previous OUVRAG of the directory is not used"""
|
||||
if qlog is not None:
|
||||
qlog.put("Export OUVRAG file")
|
||||
lines = []
|
||||
for rk, elementary in structures:
|
||||
lines.append(f"{rk:11.3f} {-2:4d} {1:4d}\n")
|
||||
lines.append(f"{rk:11.3f} {len(elementary):4d} {1:4d}\n")
|
||||
for kind, size, zd, zm, coefficient in elementary:
|
||||
lines.append(f"{kind}{'':9}{size:10.3f}{zd:10.3f}"
|
||||
f"{zm:10.3f}{coefficient:10.3f}\n")
|
||||
self._write_parameters(repertory, f"ouvrag.{name}", lines)
|
||||
|
||||
def _export_stricklers(self, hydraulics, repertory, qlog, name="0"):
|
||||
"""FROT (minor bed) and FROT2 (medium bed): LM, then one Strickler
|
||||
per cell (notice, annex 1)"""
|
||||
@@ -930,6 +1420,45 @@ class Rubar3(CommandLineSolver):
|
||||
lines = [header(len(bc))] + records(bc, columns)
|
||||
self._write_parameters(repertory, f"{filename}.{name}", lines)
|
||||
|
||||
def _export_hydros(self, bc, tinit, repertory, qlog, name="0"):
|
||||
"""HYDROS: number of points, then Qs, t, D, S (notice annex 1).
|
||||
Qs is a solid discharge (kg/s) or a concentration (kg/m3)
|
||||
according to UNISOL, D and S are those of the condition. An
|
||||
empty condition (supply at equilibrium) is written as a single
|
||||
negative Qs: RubarBE then takes the transport capacity of the
|
||||
upstream section at each time step (notice §3.6; rubarbe.f90,
|
||||
'si Hydros.etude contient un Qs<0')"""
|
||||
if qlog is not None:
|
||||
qlog.put("Export HYDROS file")
|
||||
d50, sigma = float(bc.d50), float(bc.sigma)
|
||||
data = [(t, qs) for t, qs in bc.data] or [(tinit, -1.0)]
|
||||
lines = [f"{len(data)}\n"] + [
|
||||
f"{float(qs)!r} {float(t)!r} {d50!r} {sigma!r}\n"
|
||||
for t, qs in data
|
||||
]
|
||||
self._write_parameters(repertory, f"hydros.{name}", lines)
|
||||
|
||||
def _export_condins(self, geometry, tinit, repertory, qlog, name="0"):
|
||||
"""CONDINS: TINIT, then for each section (intermaille) its number,
|
||||
Qs, D and S of the active layer (notice annex 1, rubar_init.f90,
|
||||
inits). Pamhyr2 holds no initial solid condition: Qs = 0 (empty
|
||||
active layer) and D, S are the mean of the top compartments of the
|
||||
points strictly between g and d, as advised by the notice §3.5"""
|
||||
if qlog is not None:
|
||||
qlog.put("Export CONDINS file")
|
||||
lines = [f"{tinit!r}\n"]
|
||||
for i, (_, rows) in enumerate(geometry, 1):
|
||||
markers = [marker for marker, _, _ in rows]
|
||||
_, _, left, right = self._bed_limits(markers)
|
||||
tops = [compartments[0] for _, _, compartments
|
||||
in rows[left + 1:right]]
|
||||
# Rounded to 10 significant digits, so that a mean of equal
|
||||
# values is written as these values
|
||||
d = float(f"{sum(c[1] for c in tops) / len(tops):.10g}")
|
||||
s = float(f"{sum(c[2] for c in tops) / len(tops):.10g}")
|
||||
lines.append(f"{i} 0.0 {d!r} {s!r}\n")
|
||||
self._write_parameters(repertory, f"condins.{name}", lines)
|
||||
|
||||
def read_profil(self, study, fname, results, qlog=None, name="0"):
|
||||
logger.info(f"read: profil.{name}")
|
||||
with open(fname, "r") as f:
|
||||
@@ -949,10 +1478,16 @@ class Rubar3(CommandLineSolver):
|
||||
else:
|
||||
return (0.0, 0.0, 0.0, 0.0)
|
||||
|
||||
def set_and_compute_limites(reach, ind, z, q, s):
|
||||
# Bed level of each section (mean of the CTDF of the
|
||||
# neighbouring cells): {(reach, ind): {timestamp: level}}
|
||||
bed = {}
|
||||
|
||||
def set_and_compute_limites(reach, ind, z, q, s, zf=None):
|
||||
reach.set(ind, timestamp, "Z", z)
|
||||
reach.set(ind, timestamp, "Q", q)
|
||||
reach.set(ind, timestamp, "V", s)
|
||||
if zf is not None:
|
||||
bed.setdefault((reach, ind), {})[timestamp] = zf
|
||||
|
||||
profile = reach.profile(ind)
|
||||
limits = profile.geometry.get_water_limits(z)
|
||||
@@ -990,7 +1525,9 @@ class Rubar3(CommandLineSolver):
|
||||
if line == "":
|
||||
break
|
||||
|
||||
timestamp = float(line)
|
||||
# Saves at the times of TNPROF are marked 'tnprof'
|
||||
# (rubarbe.f90, SAVEVISU)
|
||||
timestamp = float(line.split()[0])
|
||||
ts.add(timestamp)
|
||||
|
||||
logger.info(f"read: profil.{name}: timestamp = {timestamp}")
|
||||
@@ -998,7 +1535,7 @@ class Rubar3(CommandLineSolver):
|
||||
for reach, lm in reachs:
|
||||
# First profile
|
||||
h, s, q, z = read_data_line(f)
|
||||
set_and_compute_limites(reach, 0, z+h, q, s)
|
||||
set_and_compute_limites(reach, 0, z+h, q, s, z)
|
||||
|
||||
# For each profile
|
||||
ind = 1
|
||||
@@ -1009,7 +1546,8 @@ class Rubar3(CommandLineSolver):
|
||||
reach, ind,
|
||||
(pz + z + ph + h) / 2,
|
||||
(pq + q) / 2,
|
||||
(ps + s) / 2
|
||||
(ps + s) / 2,
|
||||
(pz + z) / 2
|
||||
)
|
||||
|
||||
ph = h
|
||||
@@ -1020,7 +1558,7 @@ class Rubar3(CommandLineSolver):
|
||||
|
||||
# Last profile
|
||||
h, s, q, z = read_data_line(f)
|
||||
set_and_compute_limites(reach, ind, z+h, q, s)
|
||||
set_and_compute_limites(reach, ind, z+h, q, s, z)
|
||||
|
||||
# Tables and plots use timestamp rows and global profile columns.
|
||||
timestamps = sorted(ts)
|
||||
@@ -1030,6 +1568,21 @@ class Rubar3(CommandLineSolver):
|
||||
profiles.extend(reach.profiles)
|
||||
reach.set_global_index(range(offset, len(profiles)))
|
||||
|
||||
# Kept only when the bed moves (sediment transport), so that
|
||||
# the plots otherwise show the study geometry. The initial
|
||||
# condition of the study has no bed level: the first one read
|
||||
# in PROFIL is used
|
||||
keys = ["Z", "Q", "V"]
|
||||
moves = any(max(levels.values()) != min(levels.values())
|
||||
for levels in bed.values())
|
||||
if moves and len(bed) == len(profiles):
|
||||
for (reach, ind), levels in bed.items():
|
||||
first = levels[min(levels)]
|
||||
for t in timestamps:
|
||||
first = levels.get(t, first)
|
||||
reach.set(ind, t, "zfd", first)
|
||||
keys.append("zfd")
|
||||
|
||||
results.set("timestamps", timestamps)
|
||||
results.compute_timestamp_index()
|
||||
results.set("table", {
|
||||
@@ -1037,7 +1590,7 @@ class Rubar3(CommandLineSolver):
|
||||
[profile.get_ts_key(t, key) for profile in profiles]
|
||||
for t in timestamps
|
||||
], dtype=float)
|
||||
for key in ("Z", "Q", "V")
|
||||
for key in keys
|
||||
})
|
||||
|
||||
@timer
|
||||
@@ -1155,8 +1708,74 @@ class Rubar3(CommandLineSolver):
|
||||
logger_exception(e)
|
||||
return None
|
||||
|
||||
# Without them, the hydraulic results stay readable
|
||||
try:
|
||||
self.read_bed_sections(results, repertory, name)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read the bed sections")
|
||||
logger_exception(e)
|
||||
|
||||
return results
|
||||
|
||||
def read_bed_sections(self, results, repertory, name):
|
||||
"""Bed sections for the cross section plot of the results window:
|
||||
- 'bed_points' {global index: [bool, ...]}: the points whose level
|
||||
changes, strictly between g and d (step 4);
|
||||
- 'bed_sections' {timestamp: {global index: [z, ...]}}: exact
|
||||
sections saved at the times of TNPROF when IOVIS=O ('geomac-N',
|
||||
GEOMAC-I format; rubarbe.f90, SAVEVISU).
|
||||
Between two exact sections, the plot shifts the points of the last
|
||||
one by the change of the section bed level 'zfd' since then"""
|
||||
if "zfd" not in results.get("table") or len(results.river) != 1:
|
||||
return
|
||||
profiles = results.river.reach(0).profiles
|
||||
|
||||
moving = {}
|
||||
for profile in profiles:
|
||||
markers = [self._point_markers.get(p.name.strip(), " ")
|
||||
for p in profile.geometry.points]
|
||||
_, _, left, right = self._bed_limits(markers)
|
||||
moving[profile.global_index] = [
|
||||
left < i < right for i in range(len(markers))
|
||||
]
|
||||
results.set("bed_points", moving)
|
||||
|
||||
timestamps = results.get("timestamps")
|
||||
sections = {}
|
||||
pattern = re.compile(rf"geomac-\d+\.{re.escape(name)}")
|
||||
for fname in sorted(os.listdir(repertory)):
|
||||
if not pattern.fullmatch(fname):
|
||||
continue
|
||||
t, levels = self._read_geomac(os.path.join(repertory, fname))
|
||||
if (len(levels) != len(profiles) or
|
||||
any(len(z) != len(p.geometry.points)
|
||||
for z, p in zip(levels, profiles))):
|
||||
logger.warning(f"{fname}: sections differ from the study "
|
||||
"geometry, ignored")
|
||||
continue
|
||||
# Same save as in PROFIL, with fewer digits (F15.3)
|
||||
timestamp = min(timestamps, key=lambda ts: abs(ts - t))
|
||||
if abs(timestamp - t) > 0.01:
|
||||
continue
|
||||
sections[timestamp] = {
|
||||
p.global_index: np.array(z) for p, z in zip(profiles, levels)
|
||||
}
|
||||
results.set("bed_sections", sections)
|
||||
|
||||
@staticmethod
|
||||
def _read_geomac(path):
|
||||
"""Time and level of each point of each section of a GEOMAC file
|
||||
(GEOMAC-I format: top of the first compartment)"""
|
||||
with open(path) as f:
|
||||
lines = iter([line for line in f if line.strip()])
|
||||
count, time = next(lines).split()[:2]
|
||||
levels = []
|
||||
for _ in range(int(count)):
|
||||
points = int(next(lines).split()[2])
|
||||
levels.append([float(next(lines)[1:].split()[1])
|
||||
for _ in range(points)])
|
||||
return float(time), levels
|
||||
|
||||
|
||||
class RubarBE(Rubar3):
|
||||
_type = "rubarbe"
|
||||
|
||||
@@ -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,50 @@ 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",
|
||||
@@ -91,10 +129,143 @@ class RunSolverTranslate(PamhyrTranslate):
|
||||
"Export cancelled: several {kind} boundary conditions at node "
|
||||
"{node}."
|
||||
)
|
||||
self._dict["boundary_empty"] = _translate(
|
||||
self._dict["structure_section"] = _translate(
|
||||
"Solver",
|
||||
"Export cancelled: boundary condition {name} at node {node} "
|
||||
"has no value."
|
||||
"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",
|
||||
|
||||
@@ -262,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 '
|
||||
@@ -298,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')
|
||||
@@ -364,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
|
||||
@@ -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