From 92fb6536768c2191c87ee778ec1ac745866995eb Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Fri, 4 Sep 2026 11:30:30 +0200 Subject: [PATCH 1/5] Geometry: Point: Try to create index at each update. --- src/Model/Geometry/PointXYZ.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Model/Geometry/PointXYZ.py b/src/Model/Geometry/PointXYZ.py index 08e1f0c3..73d4fcd5 100644 --- a/src/Model/Geometry/PointXYZ.py +++ b/src/Model/Geometry/PointXYZ.py @@ -95,12 +95,11 @@ class PointXYZ(Point): "ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE" ) - if major == "0" and minor == "2": - if int(release) < 5: - execute( - "CREATE INDEX idx_point " + - "ON geometry_pointXYZ(profile, scenario);" - ) + # Try to create index at each update + execute( + "CREATE INDEX IF NOT EXISTS idx_point " + + "ON geometry_pointXYZ(profile, scenario);" + ) return cls._update_submodel(execute, version, data) From 2d7409debb28b69e8793084d62fdc308fb3d9d39 Mon Sep 17 00:00:00 2001 From: Dylan Jeannin Date: Fri, 4 Sep 2026 16:45:59 +0200 Subject: [PATCH 2/5] Export csv results: fix export flowrate as a function of time, on a study without sediments transport --- src/View/Results/Window.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 9dcc0bec..480167ac 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -1150,7 +1150,6 @@ class ResultsWindow(PamhyrWindow): z = table["Z"][:, profile.global_index] q = table["Q"][:, profile.global_index] v = table["V"][:, profile.global_index] - zfd = table["zfd"][:, profile.global_index] if self._current_results == 2: reach1 = self._results[0].river.reach(self._reach) @@ -1169,8 +1168,8 @@ class ResultsWindow(PamhyrWindow): v2 = table["V"][:, profile2.global_index] if "bed_elevation" in y: - if reach.has_bedload(): - z_min = zfd + if reach.has_bedload() and "zfd" in table: + z_min = table["zfd"][:, profile.global_index] else: z_min = [profile.geometry.z_min()] * len(self._timestamps) From 5fb36e7fbebd2db372b7998a4f029142e9a6d355 Mon Sep 17 00:00:00 2001 From: Dylan Jeannin Date: Fri, 11 Sep 2026 16:50:47 +0200 Subject: [PATCH 3/5] BoundaryConditions: fix index error in deletion for boundary cond. --- src/View/BoundaryCondition/UndoCommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/View/BoundaryCondition/UndoCommand.py b/src/View/BoundaryCondition/UndoCommand.py index 69891557..2947cea6 100644 --- a/src/View/BoundaryCondition/UndoCommand.py +++ b/src/View/BoundaryCondition/UndoCommand.py @@ -124,7 +124,7 @@ class DelCommand(QUndoCommand): self._bcs.insert(self._tab, row, el) def redo(self): - self._bcs.delete_i(self._tab, self._rows) + self._bcs.delete(self._tab, [el for _, el in self._bc]) class SortCommand(QUndoCommand): From 49eb28056e3d77bfafa3cd7c5aca5b7538663ee6 Mon Sep 17 00:00:00 2001 From: Dylan Jeannin Date: Fri, 18 Sep 2026 03:09:30 +0200 Subject: [PATCH 4/5] Mage: fix missing $ markers in VAR rules and separate exported check valve entries --- src/Solver/CommandLine.py | 4 ++++ src/Solver/Mage.py | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Solver/CommandLine.py b/src/Solver/CommandLine.py index c908ffce..d5c68a43 100644 --- a/src/Solver/CommandLine.py +++ b/src/Solver/CommandLine.py @@ -158,11 +158,15 @@ class CommandLineSolver(AbstractSolver): txt = add_file.text txt = txt.replace("@version", get_version()) txt = txt.replace("@date", datetime.now().isoformat(sep=" ")) + txt = self._prepare_additional_file_text(add_file.path, txt) f.write(txt) return files + def _prepare_additional_file_text(self, path, text): + return text + def export_study_description(self, study, repertory, qlog, name="0"): files = [] diff --git a/src/Solver/Mage.py b/src/Solver/Mage.py index 3ad490ae..23e11719 100644 --- a/src/Solver/Mage.py +++ b/src/Solver/Mage.py @@ -679,6 +679,23 @@ class Mage(CommandLineSolver): return res + def _prepare_additional_file_text(self, path, text): + if os.path.splitext(path)[1].lower() != ".var": + return text + + lines = [] + for line in text.splitlines(keepends=True): + stripped = line.lstrip() + fields = stripped.split() + if stripped.startswith("$"): + # MAGE only recognizes rule markers in the first column. + line = stripped + elif (not stripped.startswith("*") and len(fields) >= 2 + and fields[1] == "W2Z"): + line = "$ " + stripped + lines.append(line) + return "".join(lines) + def _export_VAR(self, study, repertory, qlog, name="0"): files = [] @@ -729,9 +746,11 @@ class Mage(CommandLineSolver): name = bhs.name if name == "": name = f"HS_{bhs.id:>3}".replace(" ", "0") + else: + name = name.replace(" ", "_") f.write( - f"${name} clapet" + f"${name} clapet\n" ) return files From bddaad0193d64e1037418bf1a0b95f4dbc62f678 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Wed, 23 Sep 2026 09:28:42 +0200 Subject: [PATCH 5/5] debug mesher --- src/Meshing/Internal.py | 158 ++++++++++++++++++++++++---------------- 1 file changed, 97 insertions(+), 61 deletions(-) diff --git a/src/Meshing/Internal.py b/src/Meshing/Internal.py index 48a84a94..0e577533 100644 --- a/src/Meshing/Internal.py +++ b/src/Meshing/Internal.py @@ -171,8 +171,8 @@ class InternalMeshing(AMeshingTool): ltot1 = 0.0 ltot2 = 0.0 - alpha = [] - beta = [] + alpha = [0.0] + beta = [0.0] # remove names sect2.points[start2].name = '' sect2.points[end2].name = '' @@ -195,67 +195,38 @@ class InternalMeshing(AMeshingTool): else: # regular case alpha = list(map(lambda x: x/ltot1, alpha)) # target ratios beta = list(map(lambda x: x/ltot2, beta)) # current ratios - for i in range(len1 - len2): - beta.append(1.0) - beta.append(0.0) # beta[-1] - # loop on the points to insert - for k in range(len1-len2): - trouve = False - for j0 in range(len2): - if trouve: - break - undeplus = True - for j1 in range(j0, len2): - if undeplus: - if beta[j1] <= alpha[j1]: - undeplus = False - break - if undeplus: - if j0 == len2: - trouve = True - else: - j1 = j0+1 - if beta[j0]-alpha[j0] > alpha[j1]-beta[j0]: - trouve = True + match = self._match_indices(alpha, beta) - if trouve: - len2 += 1 - for i in reversed(range(j0, len2)): - beta[i+1] = beta[i] - if (beta[j0] == beta[j0-1]): - # rien - pass - else: - ratio = (alpha[j0] - beta[j0-1]) \ - / (beta[j0] - beta[j0-1]) - if ratio < 0.0: - logger.warning(f"ratio négatif {ratio}") - # on double le point a gauche - p = sect2.point(start2+j0-1).copy() - sect2.insert_point(start2+j0-1, p) - sect2.point(start2+j0-1).name = '' - beta[j0] = beta[j0-1] - else: - p = sect2.point(start2+j0).copy() - sect2.insert_point(start2+j0, p) - sect2.point(start2+j0+1).name = '' - sect2.point(start2+j0+1).x = \ - (1.0-ratio) * sect2.point(start2+j0).x + \ - ratio * sect2.point(start2+j0+2).x - sect2.point(start2+j0+1).y = \ - (1.0-ratio) * sect2.point(start2+j0).y + \ - ratio * sect2.point(start2+j0+2).y - sect2.point(start2+j0+1).z = \ - (1.0-ratio) * sect2.point(start2+j0).z + \ - ratio * sect2.point(start2+j0+2).z - beta[j0] = (1.-ratio)*beta[j0-1] \ - + ratio*beta[j0+1] + insertions_per_segment = [] # liste de (index, [points]) + for j in range(len2): + i_start = match[j] + i_end = match[j + 1] + q_start = sect2.point(start2 + j) + q_end = sect2.point(start2 + j + 1) + + new_points = [] + if i_end - i_start > 1: + u_start, u_end = alpha[i_start], alpha[i_end] + span = u_end - u_start + for k in range(i_start + 1, i_end): + ratio = (alpha[k] - u_start) / span # dans [0,1] + p = q_start.copy() + p.name = '' + p.x = (1.0-ratio) * q_start.x + ratio * q_end.x + p.y = (1.0-ratio) * q_start.y + ratio * q_end.y + p.z = (1.0-ratio) * q_start.z + ratio * q_end.z + new_points.append(p) + + # à insérer après l'indice j + insertions_per_segment.append((j, new_points)) + + # Insertion en ordre décroissant de j + # pour ne pas perturber les indices + for j, new_points in reversed(insertions_per_segment): + for offset, p in enumerate(new_points): + sect2.insert_point(start2 + j + 1 + offset, p) + len2 += 1 - if len1 > len2: - for i in range(len1 - len2): - p = sect2.point(start2+len2).copy() - sect2.insert_point(start2+len2, p) - sect2.point(start2+len2).name = '' if tag1 != "un": sect2.point(start2).name = tag1 if tag2 != "np": @@ -263,6 +234,71 @@ class InternalMeshing(AMeshingTool): sect2.modified() + def _match_indices(self, u_ref, v_target): + # appariement optimal (ordre croissant) par programmation dynamique + """ + Trouve, pour chaque v_target[j], un indice i_j dans u_ref tel que + i_0 < i_1 < ... < i_{m-1} et + Sum |u_ref[i_j] - v_target[j]| soit minimal. + + Retourne la liste des indices i_j (longueur m), + avec i_0 = 0 et i_{m-1} = n-1 imposés + (les extrémités se correspondent forcément). + + """ + n = len(u_ref) + m = len(v_target) + + # Les extrémités sont fixées. + fixed_first, fixed_last = 0, n - 1 + + # DP sur les indices disponibles entre fixed_first+1 et fixed_last-1 + # dp[j][i] = coût minimal pour apparier v_target[1..j] aux indices + # se terminant à l'indice i (i croissant strict), j de 1 à m-2. + if m == 2: + return [fixed_first, fixed_last] + + # indices internes possibles + candidates = list(range(fixed_first + 1, fixed_last)) + inner_targets = v_target[1:-1] # cibles internes (j = 1..m-2) + + INF = float("inf") + # dp[j] : dict indice_candidat -> (coût_min, provenance) + dp = [{} for _ in range(len(inner_targets))] + + for j, vt in enumerate(inner_targets): + for idx_pos, i in enumerate(candidates): + cost_here = abs(u_ref[i] - vt) + if j == 0: + # doit être > fixed_first + dp[j][i] = (cost_here, fixed_first) + else: + best = (INF, None) + for i_prev in candidates[:idx_pos]: + if i_prev in dp[j - 1]: + c = dp[j - 1][i_prev][0] + cost_here + if c < best[0]: + best = (c, i_prev) + if best[1] is not None: + dp[j][i] = best + + # Reconstruction : on part du dernier niveau, on choisit le + # meilleur indice tel que la suite jusqu'à fixed_last + # reste croissante et valide. + last_level = dp[-1] + valid = {i: c for i, c in last_level.items() if i < fixed_last} + if not valid: + raise RuntimeError("Alignement impossible avec ces données.") + best_i = min(valid, key=lambda i: valid[i][0]) + + chain = [best_i] + for j in range(len(inner_targets) - 1, 0, -1): + prev_i = dp[j][chain[-1]][1] + chain.append(prev_i) + chain.reverse() + + return [fixed_first] + chain + [fixed_last] + def update_rk(self, reach, begin_rk, end_rk, step: float = 50, limites=[-1, -1],