Ensemble: Add sequential runs with RUG variation into mage export.

scenario-dev-pa
Pierre-Antoine 2026-08-20 15:41:23 +02:00
parent 807592eab2
commit b5432498c6
6 changed files with 256 additions and 3 deletions

View File

@ -20,6 +20,8 @@ import struct
import logging import logging
import itertools import itertools
import numpy as np
from Model.Tools.PamhyrDB import SQLSubModel from Model.Tools.PamhyrDB import SQLSubModel
from Model.Scenario import Scenario from Model.Scenario import Scenario
from Model.Ensembles.Function import Uniform from Model.Ensembles.Function import Uniform
@ -200,8 +202,10 @@ class Ensemble(SQLSubModel):
return struct.unpack(data_format, bparams) return struct.unpack(data_format, bparams)
def get_sample(self, nb): def get_sample(self, nb):
return self._function.get_sample( return np.nditer(
self._params, nb self._function.get_sample(
self._params, nb
)
) )
@property @property

View File

@ -420,6 +420,19 @@ class Study(SQLModel):
def _save(self, progress=None): def _save(self, progress=None):
progress = progress if progress is not None else lambda: None progress = progress if progress is not None else lambda: None
if self.status.scenario.is_ensemble():
self._save_submodel(
[
self.scenarios, self._ens_functions,
self._river._ensembles
],
data=progress
)
progress()
self.commit()
return
self.execute( self.execute(
"INSERT OR REPLACE INTO info VALUES ('study_release', " + "INSERT OR REPLACE INTO info VALUES ('study_release', " +
f"'{self.status.version}')" f"'{self.status.version}')"

View File

@ -1027,6 +1027,68 @@ class Mage8(Mage):
logger_exception(e) logger_exception(e)
return False return False
def export_ensemble_variation(self, study, repertory,
qlog=None, name="0",
samples=None):
self._study = study
name = study.name.replace(" ", "_")
self._export_ensemble_RUG(
study, repertory,
qlog, name=name,
samples=samples,
)
return True
def _export_ensemble_RUG(self, study, repertory,
qlog, name="0",
samples={}):
files = []
for ens in samples:
if ens.target_data is None:
continue
if ens.data_type == "strickler_minor":
ens.target_data.minor = next(samples[ens])
elif ens.data_type == "strickler_medium":
ens.target_data.medium = next(samples[ens])
if qlog is not None:
qlog.put("Export variation of RUG file")
# Write header
with mage_file_open(os.path.join(repertory, f"{name}.RUG"), "w+") as f:
files.append(f"{name}.RUG")
edges = study.river.enable_edges()
id = 1
for edge in edges:
frictions = edge.frictions
for friction in frictions.frictions:
if friction.begin_strickler is None:
continue
if friction.is_deleted():
continue
num = f"{id:>3}"
brk = f"{friction.begin_rk:>10.3f}"
erk = f"{friction.end_rk:>10.3f}"
strickler = friction.begin_strickler
coef_1 = f"{strickler.minor:>10.3f}"
coef_2 = f"{strickler.medium:>10.3f}"
f.write(f"K{num} {brk}{erk}{coef_1}{coef_2}\n")
id += 1
return files
########### ###########
# RESULTS # # RESULTS #
########### ###########

View File

@ -199,6 +199,7 @@ class EnsemblesWindow(PamhyrWindow):
logger_exception(e) logger_exception(e)
return return
class MinMaxDialog(PamhyrDialog): class MinMaxDialog(PamhyrDialog):
_pamhyr_ui = "MinMaxDialog" _pamhyr_ui = "MinMaxDialog"
_pamhyr_name = "Minmax" _pamhyr_name = "Minmax"

View File

@ -83,10 +83,13 @@ from View.GeoTIFF.Window import GeoTIFFListWindow
from View.REPLines.Window import REPLineListWindow from View.REPLines.Window import REPLineListWindow
from View.SolverParameters.Window import SolverParametersWindow from View.SolverParameters.Window import SolverParametersWindow
from View.RunSolver.Window import ( from View.RunSolver.Window import (
EnsembleRunNumberDialog,
SelectSolverWindow, SelectSolverWindow,
SolverLogWindow, SolverLogWindow,
SolverLogEnsWindow,
) )
from View.Results.Window import ResultsWindow from View.Results.Window import ResultsWindow
from View.Results.CompareDialog import ( from View.Results.CompareDialog import (
CompareScenariosWindow CompareScenariosWindow
@ -1587,6 +1590,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
sol.show() sol.show()
def solver_log(self, solver): def solver_log(self, solver):
if self._study.status.scenario.is_ensemble():
return self._solver_log_ensemble(solver)
sol = SolverLogWindow( sol = SolverLogWindow(
study=self._study, study=self._study,
config=self.conf, config=self.conf,
@ -1595,6 +1601,25 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
) )
sol.show() sol.show()
def _solver_log_ensemble(self, solver):
run_number = 1
dlg = EnsembleRunNumberDialog(
trad=self._trad,
parent=self
)
if dlg.exec():
run_number = dlg._number
sol = SolverLogEnsWindow(
run_number=run_number,
study=self._study,
config=self.conf,
solver=solver,
parent=self
)
sol.show()
def msg_box_results_continue(self): def msg_box_results_continue(self):
dlg = QMessageBox(self) dlg = QMessageBox(self)

View File

@ -40,7 +40,7 @@ from PyQt5.QtWidgets import (
QFileDialog, QTableView, QAbstractItemView, QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate, QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget, QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QTextEdit, QTextEdit, QProgressBar,
) )
from View.RunSolver.Log.Window import SolverLogFileWindow from View.RunSolver.Log.Window import SolverLogFileWindow
@ -57,6 +57,36 @@ _translate = QCoreApplication.translate
logger = logging.getLogger() logger = logging.getLogger()
class EnsembleRunNumberDialog(PamhyrDialog):
_pamhyr_ui = "EnsembleRunNumberDialog"
_pamhyr_name = "EnsembleRunNumber"
def __init__(self, trad=None, parent=None):
super(EnsembleRunNumberDialog, self).__init__(
title=trad[self._pamhyr_name],
trad=trad,
options=[],
parent=parent
)
self._init_default_values()
def _init_default_values(self):
self.set_spin_box("spinBox", 100)
@property
def number(self):
return self._number
def accept(self):
self._number = self.get_spin_box("spinBox")
super().accept()
def reject(self):
self.close()
class SelectSolverWindow(PamhyrDialog): class SelectSolverWindow(PamhyrDialog):
_pamhyr_ui = "SelectSolver" _pamhyr_ui = "SelectSolver"
_pamhyr_name = "Select solver" _pamhyr_name = "Select solver"
@ -425,3 +455,121 @@ class SolverLogWindow(PamhyrWindow):
parent=self, parent=self,
) )
log.show() log.show()
class SolverLogEnsWindow(SolverLogWindow):
_pamhyr_ui = "SolverLogEnsemble"
_pamhyr_name = "Solver Log Ensemble"
def __init__(self, run_number=100,
study=None, config=None,
solver=None, parent=None):
self._run_number = run_number
self._last_run = 0
self._samples = {}
for ens in study.river._ensembles.lst:
if ens.function is None:
continue
self._samples[ens] = ens.get_sample(self._run_number)
super(SolverLogEnsWindow, self).__init__(
solver=solver,
study=study,
config=config,
parent=parent,
)
def setup_connections(self):
self.setup_progress_bar()
super(SolverLogEnsWindow, self).setup_connections()
def setup_progress_bar(self):
self._progress_bar = self.find(QProgressBar, "progressBar")
self._progress_bar.setRange(0, self._run_number)
self._progress_bar.setValue(0)
def _log_int(self, int_code, color=None):
logger.info(f"solver: Returns {int_code}")
color = "blue" if int_code == 0 else "red"
self.find(QTextEdit, "textEdit")\
.append(
f"<font color=\"{color}\">" +
f" *** Finished with code {int_code}" +
"</font>"
)
self.statusbar.showMessage(
"Done" if int_code == 0 else "Failed",
3000
)
if self._last_run < self._run_number:
self.next()
else:
self._progress_bar.setValue(self._last_run)
def next(self):
self._process = self.new_process(self._parent)
self._log(f" *** Run #{self._last_run}", color="blue")
self._solver.export_ensemble_variation(
self._study, self._workdir, qlog=self._output,
samples=self._samples,
)
self._solver.run(
self._study,
process=self._process,
output_queue=self._output
)
self._last_run += 1
self._progress_bar.setValue(self._last_run - 1)
def run(self):
self._log(f" *** Run solver {self._solver.name}", color="blue")
self.next()
def start(self):
if self._solver.is_stoped():
self._log(f" *** Export study {self._solver.name}", color="blue")
ok = self._solver.export(
self._study, self._workdir, qlog=self._output
)
if not ok:
self._log(f" *** Failed to export", color="red")
self.update()
return
else:
self.update()
self._process = self.new_process(self._parent)
self._log(" *** Start", color="blue")
self._results = None
self.next()
self.find(QAction, "action_start").setEnabled(False)
if _signal:
self.find(QAction, "action_pause").setEnabled(True)
else:
self.find(QAction, "action_pause").setEnabled(False)
self.find(QAction, "action_stop").setEnabled(True)
self.find(QAction, "action_log_file").setEnabled(False)
self.find(QAction, "action_results").setEnabled(False)
def stop(self):
self._log(" *** Stop", color="blue")
self._solver.kill()
self.find(QAction, "action_start").setEnabled(True)
self.find(QAction, "action_pause").setEnabled(False)
self.find(QAction, "action_stop").setEnabled(False)
self.find(QAction, "action_results").setEnabled(True)
if self._solver.log_file() != "":
self.find(QAction, "action_log_file").setEnabled(True)