RubarBE: export solid lateral contributions with their D50 and sigma, attached to the liquid contribution of the same zone (hydevs), and no longer read suspension ones

This commit is contained in:
Dylan Jeannin committed 2026-10-21 14:56:44 +02:00
1 parent be7b5b892a
commit ef72e41b14
5 files changed
+124 -29

No files matched your search

@@ -223,9 +223,7 @@ class LateralContribution(SQLSubModel):
self._data = []
self._header = []
self._types = [float, float]
# Grain size of the sediment brought by a solid contribution:
# median diameter (m) and spread sqrt(d84/d16), as for a solid
# boundary condition
self._d50 = 0.002
self._sigma = 1.0
+86 -22
View File
@@ -174,7 +174,8 @@ class Rubar3(CommandLineSolver):
hydraulics["warnings"])
saves = self._save_data(values, hydraulics)
laterals = self._lateral_data(
study.river, hydraulics, values, hydraulics["warnings"])
study.river, hydraulics, values, trased,
hydraulics["warnings"])
structures = self._structure_data(
study.river, hydraulics, hydraulics["warnings"])
except RubarExportError as error:
@@ -546,7 +547,7 @@ class Rubar3(CommandLineSolver):
# inflow in each cell of its zone, without checking this size
_max_lateral_points = 3000 * 20
def _lateral_data(self, river, hydraulics, values, warnings):
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.
@@ -557,9 +558,17 @@ class Rubar3(CommandLineSolver):
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. Otherwise, solid and suspension contributions have
no RubarBE equivalent (their sediment cannot be linked to an
inflow): they are refused"""
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"]
@@ -580,40 +589,50 @@ class Rubar3(CommandLineSolver):
return kept
lcs = on_reach("liquid")
sediment = on_reach("solid") + on_reach("suspenssion")
solids = on_reach("solid")
# Not read by RubarBE: the user is warned, the computation runs
# without them
if not iodev:
for lc in lcs + sediment:
for lc in lcs + solids:
warnings.append(trad["lateral_iodev_disabled"].format(
name=lc.name, reach=edge.name))
return None
if sediment:
raise RubarExportError(trad["lateral_solid_unsupported"].format(
name=sediment[0].name, reach=edge.name))
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)
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))
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)]))
first, last = (b + 1, e) if b < e else ((min(b + 1, count),) * 2)
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))
@@ -625,13 +644,56 @@ class Rubar3(CommandLineSolver):
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 blocks)
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 [block[:3] for block in blocks]
return result
##############
# Structures #
@@ -1251,13 +1313,15 @@ class Rubar3(CommandLineSolver):
if qlog is not None:
qlog.put("Export HYDEVS file")
lines = []
for first, last, data in laterals:
for first, last, data, (d50, sigma), length in laterals:
if trased:
lines.append(f"{first} {last} {len(data)} 0.0 0.0\n")
lines += [f"{q!r} {t!r} 0.0 {t!r}\n" for t, q in data]
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]
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"):
@@ -62,6 +62,9 @@ class EditLateralContributionWindow(PamhyrWindow):
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
@@ -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)"
)
+30 -4
View File
@@ -192,11 +192,37 @@ class RunSolverTranslate(PamhyrTranslate):
"Lateral contribution {name} on reach {reach} has no value: it "
"is ignored."
)
self._dict["lateral_solid_unsupported"] = _translate(
self._dict["lateral_solid_no_transport"] = _translate(
"Solver",
"Export cancelled: solid or suspension lateral contribution "
"{name} on reach {reach}: RubarBE only reads the sediment of a "
"liquid inflow, which Pamhyr2 cannot link to it. Remove it."
"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",