mirror of
https://gitlab.com/pamhyr/pamhyr2.git
synced 2026-10-30 03:20:41 +01:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee416e1059 | ||
|
|
3fee3b0bb3 | ||
|
|
a06c2eaa7e | ||
|
|
4e767d86d6 |
No files matched your search
@@ -104,7 +104,10 @@ class Profile(SQLSubModel):
|
|||||||
return any(map(lambda ts: "sl" in self._data[ts], self._data))
|
return any(map(lambda ts: "sl" in self._data[ts], self._data))
|
||||||
|
|
||||||
def has_bedload(self):
|
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
|
@classmethod
|
||||||
def _db_create(cls, execute, ext=""):
|
def _db_create(cls, execute, ext=""):
|
||||||
|
|||||||
+318
-64
@@ -17,6 +17,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
from decimal import Decimal, InvalidOperation, localcontext
|
from decimal import Decimal, InvalidOperation, localcontext
|
||||||
@@ -76,11 +77,14 @@ class Rubar3(CommandLineSolver):
|
|||||||
("rubarbe_fdeb2", "10"),
|
("rubarbe_fdeb2", "10"),
|
||||||
("rubarbe_fdeb3", "100"),
|
("rubarbe_fdeb3", "100"),
|
||||||
("rubarbe_tf_1", "y"),
|
("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_3", "y"),
|
||||||
("rubarbe_tf_4", "y"),
|
("rubarbe_tf_4", "n"),
|
||||||
("rubarbe_tf_5", "y"),
|
("rubarbe_tf_5", "y"),
|
||||||
("rubarbe_tf_6", "n"),
|
("rubarbe_tf_6", "n"),
|
||||||
|
("rubarbe_abshyd", ""),
|
||||||
|
("rubarbe_tnprof", ""),
|
||||||
("rubarbe_trased", "n"),
|
("rubarbe_trased", "n"),
|
||||||
("rubarbe_optfpc", "0"),
|
("rubarbe_optfpc", "0"),
|
||||||
# trased parameters
|
# trased parameters
|
||||||
@@ -160,23 +164,14 @@ class Rubar3(CommandLineSolver):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
trased = self._parameter_flag(study, "trased") == "O"
|
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
|
# Check the hydraulic data before writing any file
|
||||||
try:
|
try:
|
||||||
hydraulics = self._hydraulic_data(study, values)
|
hydraulics = self._hydraulic_data(study, values)
|
||||||
geometry = self._geometry_data(hydraulics["edge"], trased)
|
geometry = self._geometry_data(hydraulics["edge"], trased)
|
||||||
|
solid = self._solid_boundary(
|
||||||
|
study.river, hydraulics["edge"], values, trased)
|
||||||
|
saves = self._save_data(values, hydraulics)
|
||||||
except RubarExportError as error:
|
except RubarExportError as error:
|
||||||
self._cancel(qlog, str(error))
|
self._cancel(qlog, str(error))
|
||||||
return False
|
return False
|
||||||
@@ -197,7 +192,12 @@ class Rubar3(CommandLineSolver):
|
|||||||
self._export_condin(hydraulics, repertory, qlog, name=name)
|
self._export_condin(hydraulics, repertory, qlog, name=name)
|
||||||
self._export_stricklers(hydraulics, repertory, qlog, name=name)
|
self._export_stricklers(hydraulics, repertory, qlog, name=name)
|
||||||
self._export_boundaries(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, 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
|
return True
|
||||||
|
|
||||||
@@ -217,7 +217,10 @@ class Rubar3(CommandLineSolver):
|
|||||||
"5": (), "7": ()}
|
"5": (), "7": ()}
|
||||||
_boundary_kinds = {"TD": "hydrograph Q(t)", "TZ": "water level Z(t)",
|
_boundary_kinds = {"TD": "hydrograph Q(t)", "TZ": "water level Z(t)",
|
||||||
"ZD": "rating curve Q(Z)",
|
"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):
|
def _hydraulic_data(self, study, values):
|
||||||
"""Check the single reach data needed by DONNEE, MAIL, CONDIN,
|
"""Check the single reach data needed by DONNEE, MAIL, CONDIN,
|
||||||
@@ -307,17 +310,11 @@ class Rubar3(CommandLineSolver):
|
|||||||
name=names[marker], profile=self._profile_label(profile),
|
name=names[marker], profile=self._profile_label(profile),
|
||||||
reach=edge.name))
|
reach=edge.name))
|
||||||
|
|
||||||
# Defaults of RubarBE: first and last points for G and D, G and D
|
left, right, active_left, active_right = self._bed_limits(markers)
|
||||||
# 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)
|
|
||||||
limits = [("main_channel", "rg", "rd", left, right)]
|
limits = [("main_channel", "rg", "rd", left, right)]
|
||||||
if trased:
|
if trased:
|
||||||
limits.append(("active_bed", "g", "d",
|
limits.append(("active_bed", "g", "d",
|
||||||
index("g", left), index("d", right)))
|
active_left, active_right))
|
||||||
for bed, lname, rname, i, j in limits:
|
for bed, lname, rname, i, j in limits:
|
||||||
if j - i < 2:
|
if j - i < 2:
|
||||||
raise RubarExportError(trad["bed_limits_invalid"].format(
|
raise RubarExportError(trad["bed_limits_invalid"].format(
|
||||||
@@ -325,6 +322,18 @@ class Rubar3(CommandLineSolver):
|
|||||||
profile=self._profile_label(profile), reach=edge.name))
|
profile=self._profile_label(profile), reach=edge.name))
|
||||||
return markers
|
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):
|
def _compartments(self, edge, profile, number, point):
|
||||||
"""Sediment layers of a point as RubarBE compartments, from the
|
"""Sediment layers of a point as RubarBE compartments, from the
|
||||||
top: (top elevation, D, S, critical stress). A non erodible
|
top: (top elevation, D, S, critical stress). A non erodible
|
||||||
@@ -452,6 +461,140 @@ class Rubar3(CommandLineSolver):
|
|||||||
|
|
||||||
return selected
|
return selected
|
||||||
|
|
||||||
|
def _solid_boundary(self, river, edge, values, trased):
|
||||||
|
"""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. 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"
|
||||||
|
]
|
||||||
|
for bc in solid:
|
||||||
|
if bc.node == edge.node2:
|
||||||
|
raise RubarExportError(
|
||||||
|
trad["solid_boundary_downstream"].format(
|
||||||
|
name=bc.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}"
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|
||||||
|
#########
|
||||||
|
# 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
|
@staticmethod
|
||||||
def _interpolated_frictions(edge):
|
def _interpolated_frictions(edge):
|
||||||
"""Minor and medium Strickler at each centre of cell. The model
|
"""Minor and medium Strickler at each centre of cell. The model
|
||||||
@@ -578,20 +721,6 @@ class Rubar3(CommandLineSolver):
|
|||||||
return False
|
return False
|
||||||
return True
|
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):
|
def _parameter_values(self, study):
|
||||||
return {p.name: str(p.value).strip()
|
return {p.name: str(p.value).strip()
|
||||||
for p in study.river.get_params(self.type).parameters}
|
for p in study.river.get_params(self.type).parameters}
|
||||||
@@ -825,27 +954,28 @@ class Rubar3(CommandLineSolver):
|
|||||||
|
|
||||||
self._write_parameters(repertory, f"mail.{name}", lines)
|
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:
|
if qlog is not None:
|
||||||
qlog.put("Export ABSHYD file")
|
qlog.put("Export TNPROF file")
|
||||||
|
lines = [f"{len(saves['tnprof']):>12}\n"] + [
|
||||||
with open(
|
f"{i:>12} {t:>24.3f}\n"
|
||||||
os.path.join(
|
for i, t in enumerate(saves["tnprof"], 1)
|
||||||
repertory, f"abshyd.{name}"
|
]
|
||||||
), "w+"
|
self._write_parameters(repertory, f"tnprof.{name}", lines)
|
||||||
) 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
|
|
||||||
|
|
||||||
def _export_devers(self, study, repertory, qlog, name="0"):
|
def _export_devers(self, study, repertory, qlog, name="0"):
|
||||||
if qlog is not None:
|
if qlog is not None:
|
||||||
@@ -930,6 +1060,40 @@ class Rubar3(CommandLineSolver):
|
|||||||
lines = [header(len(bc))] + records(bc, columns)
|
lines = [header(len(bc))] + records(bc, columns)
|
||||||
self._write_parameters(repertory, f"{filename}.{name}", lines)
|
self._write_parameters(repertory, f"{filename}.{name}", lines)
|
||||||
|
|
||||||
|
def _export_hydros(self, bc, 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"""
|
||||||
|
if qlog is not None:
|
||||||
|
qlog.put("Export HYDROS file")
|
||||||
|
d50, sigma = float(bc.d50), float(bc.sigma)
|
||||||
|
lines = [f"{len(bc)}\n"] + [
|
||||||
|
f"{float(qs)!r} {float(t)!r} {d50!r} {sigma!r}\n"
|
||||||
|
for t, qs in bc.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"):
|
def read_profil(self, study, fname, results, qlog=None, name="0"):
|
||||||
logger.info(f"read: profil.{name}")
|
logger.info(f"read: profil.{name}")
|
||||||
with open(fname, "r") as f:
|
with open(fname, "r") as f:
|
||||||
@@ -949,10 +1113,16 @@ class Rubar3(CommandLineSolver):
|
|||||||
else:
|
else:
|
||||||
return (0.0, 0.0, 0.0, 0.0)
|
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, "Z", z)
|
||||||
reach.set(ind, timestamp, "Q", q)
|
reach.set(ind, timestamp, "Q", q)
|
||||||
reach.set(ind, timestamp, "V", s)
|
reach.set(ind, timestamp, "V", s)
|
||||||
|
if zf is not None:
|
||||||
|
bed.setdefault((reach, ind), {})[timestamp] = zf
|
||||||
|
|
||||||
profile = reach.profile(ind)
|
profile = reach.profile(ind)
|
||||||
limits = profile.geometry.get_water_limits(z)
|
limits = profile.geometry.get_water_limits(z)
|
||||||
@@ -990,7 +1160,9 @@ class Rubar3(CommandLineSolver):
|
|||||||
if line == "":
|
if line == "":
|
||||||
break
|
break
|
||||||
|
|
||||||
timestamp = float(line)
|
# Saves at the times of TNPROF are marked 'tnprof'
|
||||||
|
# (rubarbe.f90, SAVEVISU)
|
||||||
|
timestamp = float(line.split()[0])
|
||||||
ts.add(timestamp)
|
ts.add(timestamp)
|
||||||
|
|
||||||
logger.info(f"read: profil.{name}: timestamp = {timestamp}")
|
logger.info(f"read: profil.{name}: timestamp = {timestamp}")
|
||||||
@@ -998,7 +1170,7 @@ class Rubar3(CommandLineSolver):
|
|||||||
for reach, lm in reachs:
|
for reach, lm in reachs:
|
||||||
# First profile
|
# First profile
|
||||||
h, s, q, z = read_data_line(f)
|
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
|
# For each profile
|
||||||
ind = 1
|
ind = 1
|
||||||
@@ -1009,7 +1181,8 @@ class Rubar3(CommandLineSolver):
|
|||||||
reach, ind,
|
reach, ind,
|
||||||
(pz + z + ph + h) / 2,
|
(pz + z + ph + h) / 2,
|
||||||
(pq + q) / 2,
|
(pq + q) / 2,
|
||||||
(ps + s) / 2
|
(ps + s) / 2,
|
||||||
|
(pz + z) / 2
|
||||||
)
|
)
|
||||||
|
|
||||||
ph = h
|
ph = h
|
||||||
@@ -1020,7 +1193,7 @@ class Rubar3(CommandLineSolver):
|
|||||||
|
|
||||||
# Last profile
|
# Last profile
|
||||||
h, s, q, z = read_data_line(f)
|
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.
|
# Tables and plots use timestamp rows and global profile columns.
|
||||||
timestamps = sorted(ts)
|
timestamps = sorted(ts)
|
||||||
@@ -1030,6 +1203,21 @@ class Rubar3(CommandLineSolver):
|
|||||||
profiles.extend(reach.profiles)
|
profiles.extend(reach.profiles)
|
||||||
reach.set_global_index(range(offset, len(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.set("timestamps", timestamps)
|
||||||
results.compute_timestamp_index()
|
results.compute_timestamp_index()
|
||||||
results.set("table", {
|
results.set("table", {
|
||||||
@@ -1037,7 +1225,7 @@ class Rubar3(CommandLineSolver):
|
|||||||
[profile.get_ts_key(t, key) for profile in profiles]
|
[profile.get_ts_key(t, key) for profile in profiles]
|
||||||
for t in timestamps
|
for t in timestamps
|
||||||
], dtype=float)
|
], dtype=float)
|
||||||
for key in ("Z", "Q", "V")
|
for key in keys
|
||||||
})
|
})
|
||||||
|
|
||||||
@timer
|
@timer
|
||||||
@@ -1155,8 +1343,74 @@ class Rubar3(CommandLineSolver):
|
|||||||
logger_exception(e)
|
logger_exception(e)
|
||||||
return None
|
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
|
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):
|
class RubarBE(Rubar3):
|
||||||
_type = "rubarbe"
|
_type = "rubarbe"
|
||||||
|
|||||||
@@ -286,6 +286,10 @@ class PlotAC(PamhyrPlot):
|
|||||||
initial_z = np.asarray(profile.geometry.z(), dtype=float)
|
initial_z = np.asarray(profile.geometry.z(), dtype=float)
|
||||||
result = self.results[self._current_res_id]
|
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:
|
try:
|
||||||
sediment_timestamps = sorted(result.get("sediment_timestamps"))
|
sediment_timestamps = sorted(result.get("sediment_timestamps"))
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
@@ -324,6 +328,38 @@ class PlotAC(PamhyrPlot):
|
|||||||
|
|
||||||
return initial_z
|
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):
|
def _get_wet_mask(self, profile, elevation):
|
||||||
result = self.results[self._current_res_id]
|
result = self.results[self._current_res_id]
|
||||||
|
|
||||||
|
|||||||
@@ -49,12 +49,50 @@ class RunSolverTranslate(PamhyrTranslate):
|
|||||||
"use a dimensionless critical stress given by the user. "
|
"use a dimensionless critical stress given by the user. "
|
||||||
"Fill it in the parameters (tab Solid Transport)."
|
"Fill it in the parameters (tab Solid Transport)."
|
||||||
)
|
)
|
||||||
self._dict["missing_solid_boundary"] = _translate(
|
self._dict["solid_boundary_downstream"] = _translate(
|
||||||
"Solver",
|
"Solver",
|
||||||
"Export cancelled: sediment transport (TRASED) is enabled but "
|
"Export cancelled: solid boundary condition {name} at node "
|
||||||
"no solid boundary condition is defined at the upstream node "
|
"{node}: RubarBE only reads a solid boundary condition at the "
|
||||||
"of reach(es): {reaches}. Add a solid boundary condition, or "
|
"upstream node. Remove it."
|
||||||
"disable sediment transport in the solver parameters."
|
)
|
||||||
|
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(
|
self._dict["multireach_unsupported"] = _translate(
|
||||||
"Solver",
|
"Solver",
|
||||||
|
|||||||
@@ -262,6 +262,17 @@ def fields():
|
|||||||
'Les abscisses ou instants demandés '
|
'Les abscisses ou instants demandés '
|
||||||
'doivent être définis dans les données correspondantes.',
|
'doivent être définis dans les données correspondantes.',
|
||||||
YES_NO)
|
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):
|
for i in range(1, 4):
|
||||||
add(f'fdeb{i}', 4, f'Seuil de débit du front {i} (m³/s)',
|
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 '
|
'Débit pour lequel à chaque abscisse, est enregistré le temps '
|
||||||
@@ -298,6 +309,18 @@ def validate(field, value):
|
|||||||
return value
|
return value
|
||||||
if field.kind == 'text':
|
if field.kind == 'text':
|
||||||
return value
|
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:
|
if not value and field.optional:
|
||||||
return value
|
return value
|
||||||
value = value.replace(',', '.').replace('d', 'E').replace('D', 'E')
|
value = value.replace(',', '.').replace('d', 'E').replace('D', 'E')
|
||||||
@@ -364,4 +387,12 @@ def active(field, values):
|
|||||||
return values.get('rubarbe_' + source) in accepted
|
return values.get('rubarbe_' + source) in accepted
|
||||||
if key.startswith('fdeb'):
|
if key.startswith('fdeb'):
|
||||||
return values.get('rubarbe_tf_6', 'n').lower() in ('y', 'o')
|
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
|
return True
|
||||||
Binary file not shown.
Reference in new issue
Block a user