diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index 139dc64a..c5326085 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -175,6 +175,8 @@ class Rubar3(CommandLineSolver): saves = self._save_data(values, hydraulics) laterals = self._lateral_data( study.river, hydraulics, values, hydraulics["warnings"]) + structures = self._structure_data( + study.river, hydraulics, hydraulics["warnings"]) except RubarExportError as error: self._cancel(qlog, str(error)) return False @@ -194,6 +196,7 @@ class Rubar3(CommandLineSolver): 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) @@ -630,6 +633,114 @@ class Rubar3(CommandLineSolver): return [block[:3] for block in blocks] + ############## + # 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 # ######### @@ -1149,6 +1260,23 @@ class Rubar3(CommandLineSolver): 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)""" diff --git a/src/View/RunSolver/Translate.py b/src/View/RunSolver/Translate.py index 600addbd..0a275143 100644 --- a/src/View/RunSolver/Translate.py +++ b/src/View/RunSolver/Translate.py @@ -129,6 +129,64 @@ class RunSolverTranslate(PamhyrTranslate): "Export cancelled: several {kind} boundary conditions at node " "{node}." ) + self._dict["structure_section"] = _translate( + "Solver", + "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 "