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._data = []
self._header = [] self._header = []
self._types = [float, float] 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._d50 = 0.002
self._sigma = 1.0 self._sigma = 1.0
+86 -22
View File
@@ -174,7 +174,8 @@ class Rubar3(CommandLineSolver):
hydraulics["warnings"]) hydraulics["warnings"])
saves = self._save_data(values, hydraulics) saves = self._save_data(values, hydraulics)
laterals = self._lateral_data( laterals = self._lateral_data(
study.river, hydraulics, values, hydraulics["warnings"]) study.river, hydraulics, values, trased,
hydraulics["warnings"])
structures = self._structure_data( structures = self._structure_data(
study.river, hydraulics, hydraulics["warnings"]) study.river, hydraulics, hydraulics["warnings"])
except RubarExportError as error: except RubarExportError as error:
@@ -546,7 +547,7 @@ class Rubar3(CommandLineSolver):
# inflow in each cell of its zone, without checking this size # inflow in each cell of its zone, without checking this size
_max_lateral_points = 3000 * 20 _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, """Lateral inflows of HYDEVS, read when IODEV=O (rubar_init.f90,
init2). Return [(first cell, last cell, [(t, q), ...]), ...] init2). Return [(first cell, last cell, [(t, q), ...]), ...]
sorted from upstream, or None when IODEV=N. 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 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, section). The first and last cells of the reach are never used,
as RubarBE requires. With IODEV=N, the contributions are ignored as RubarBE requires. With IODEV=N, the contributions are ignored
with a warning. Otherwise, solid and suspension contributions have with a warning.
no RubarBE equivalent (their sediment cannot be linked to an
inflow): they are refused""" 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() trad = RunSolverTranslate()
edge = hydraulics["edge"] edge = hydraulics["edge"]
profiles = hydraulics["profiles"] profiles = hydraulics["profiles"]
@@ -580,40 +589,50 @@ class Rubar3(CommandLineSolver):
return kept return kept
lcs = on_reach("liquid") 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 # Not read by RubarBE: the user is warned, the computation runs
# without them # without them
if not iodev: if not iodev:
for lc in lcs + sediment: for lc in lcs + solids:
warnings.append(trad["lateral_iodev_disabled"].format( warnings.append(trad["lateral_iodev_disabled"].format(
name=lc.name, reach=edge.name)) name=lc.name, reach=edge.name))
return None return None
if sediment: if not trased:
raise RubarExportError(trad["lateral_solid_unsupported"].format( for lc in solids:
name=sediment[0].name, reach=edge.name)) 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)} index = {id(p): i for i, p in enumerate(profiles, 1)}
count = len(profiles) count = len(profiles)
blocks = []
for lc in lcs: def zone_and_data(lc):
if lc.lctype not in self._lateral_signs:
raise RubarExportError(trad["lateral_type_unsupported"].format(
name=lc.name, reach=edge.name))
if (id(lc.begin_section) not in index or if (id(lc.begin_section) not in index or
id(lc.end_section) not in index): id(lc.end_section) not in index):
raise RubarExportError(trad["lateral_sections"].format( raise RubarExportError(trad["lateral_sections"].format(
name=lc.name, reach=edge.name)) name=lc.name, reach=edge.name))
b, e = sorted((index[id(lc.begin_section)], b, e = sorted((index[id(lc.begin_section)],
index[id(lc.end_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] data = [(float(d[0]), float(d[1])) for d in lc.data]
times = [t for t, _ in data] times = [t for t, _ in data]
if any(t1 < t0 for t0, t1 in zip(times, times[1:])): if any(t1 < t0 for t0, t1 in zip(times, times[1:])):
raise RubarExportError(trad["lateral_not_sorted"].format( raise RubarExportError(trad["lateral_not_sorted"].format(
name=lc.name, reach=edge.name)) 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] sign = self._lateral_signs[lc.lctype]
blocks.append((first, last, [(t, sign * q) for t, q in data], blocks.append((first, last, [(t, sign * q) for t, q in data],
lc.name)) lc.name))
@@ -625,13 +644,56 @@ class Rubar3(CommandLineSolver):
raise RubarExportError(trad["lateral_overlap"].format( raise RubarExportError(trad["lateral_overlap"].format(
first=name0, second=name1, reach=edge.name)) 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) 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: if points > self._max_lateral_points:
raise RubarExportError(trad["lateral_too_long"].format( raise RubarExportError(trad["lateral_too_long"].format(
count=points, maximum=self._max_lateral_points)) count=points, maximum=self._max_lateral_points))
return [block[:3] for block in blocks] return result
############## ##############
# Structures # # Structures #
@@ -1251,13 +1313,15 @@ class Rubar3(CommandLineSolver):
if qlog is not None: if qlog is not None:
qlog.put("Export HYDEVS file") qlog.put("Export HYDEVS file")
lines = [] lines = []
for first, last, data in laterals: for first, last, data, (d50, sigma), length in laterals:
if trased: if trased:
lines.append(f"{first} {last} {len(data)} 0.0 0.0\n") lines.append(
lines += [f"{q!r} {t!r} 0.0 {t!r}\n" for t, q in data] 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: else:
lines.append(f"{first} {last} {len(data)}\n") 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) self._write_parameters(repertory, f"hydevs.{name}", lines)
def _export_ouvrag(self, structures, repertory, qlog, name="0"): def _export_ouvrag(self, structures, repertory, qlog, name="0"):
@@ -62,6 +62,9 @@ class EditLateralContributionWindow(PamhyrWindow):
self._is_solid = tab == "solid" self._is_solid = tab == "solid"
trad = LCETranslate() trad = LCETranslate()
if self._is_solid:
trad.get_dict("table_headers")["discharge"] = \
trad["solid_discharge"]
name = trad[self._pamhyr_name] name = trad[self._pamhyr_name]
if self._data is not None: if self._data is not None:
edge_name = (self._data.reach.name if self._data.reach 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"], "discharge": self._dict["unit_discharge"],
"z": self._dict["unit_elevation"], "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 " "Lateral contribution {name} on reach {reach} has no value: it "
"is ignored." "is ignored."
) )
self._dict["lateral_solid_unsupported"] = _translate( self._dict["lateral_solid_no_transport"] = _translate(
"Solver", "Solver",
"Export cancelled: solid or suspension lateral contribution " "Solid lateral contribution {name} on reach {reach} is not taken "
"{name} on reach {reach}: RubarBE only reads the sediment of a " "into account: sediment transport (rubarbe_trased) is disabled "
"liquid inflow, which Pamhyr2 cannot link to it. Remove it." "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( self._dict["lateral_iodev_disabled"] = _translate(
"Solver", "Solver",