From 4e767d86d6734ee7cc8eb8dcfb5674e9fb71630b Mon Sep 17 00:00:00 2001 From: Dylan Jeannin Date: Mon, 19 Oct 2026 11:45:20 +0200 Subject: [PATCH] RubarBE: export the upstream solid boundary condition (hydros), and refuse unused, missing or invalid solid conditions --- src/Solver/RubarBE.py | 89 +++++++++++++++++++++++---------- src/View/RunSolver/Translate.py | 20 ++++++-- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index d1012844..2f1ce90d 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -160,23 +160,13 @@ class Rubar3(CommandLineSolver): 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: hydraulics = self._hydraulic_data(study, values) geometry = self._geometry_data(hydraulics["edge"], trased) + solid = self._solid_boundary( + study.river, hydraulics["edge"], values, trased) except RubarExportError as error: self._cancel(qlog, str(error)) return False @@ -197,6 +187,8 @@ class Rubar3(CommandLineSolver): self._export_condin(hydraulics, repertory, qlog, name=name) self._export_stricklers(hydraulics, repertory, qlog, name=name) self._export_boundaries(hydraulics, repertory, qlog, name=name) + if solid is not None: + self._export_hydros(solid, repertory, qlog, name=name) # self._export_abshyd(study, repertory, qlog, name=name) return True @@ -217,7 +209,10 @@ 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, @@ -452,6 +447,47 @@ class Rubar3(CommandLineSolver): 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 + @staticmethod def _interpolated_frictions(edge): """Minor and medium Strickler at each centre of cell. The model @@ -578,20 +614,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} @@ -930,6 +952,19 @@ class Rubar3(CommandLineSolver): lines = [header(len(bc))] + records(bc, columns) 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 read_profil(self, study, fname, results, qlog=None, name="0"): logger.info(f"read: profil.{name}") with open(fname, "r") as f: diff --git a/src/View/RunSolver/Translate.py b/src/View/RunSolver/Translate.py index 40fd0c7e..6b9caf2f 100644 --- a/src/View/RunSolver/Translate.py +++ b/src/View/RunSolver/Translate.py @@ -49,12 +49,22 @@ 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["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",