AdisTT: Stable version of files writer for adisTT solver

monopk-ic-adistt
Dylan Jeannin 2026-07-16 10:44:01 +02:00
parent 4a94c946d1
commit bb2e83b6ce
8 changed files with 1895 additions and 6 deletions

1133
src/Solver/AdisTT.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@ from Solver.Mage import (
Mage7, Mage8, MageFake7, Mage7, Mage8, MageFake7,
) )
from Solver.AdisTS import AdisTSwc from Solver.AdisTS import AdisTSwc
from Solver.AdisTT import AdisTTwc
from Solver.RubarBE import Rubar3, RubarBE from Solver.RubarBE import Rubar3, RubarBE
_translate = QCoreApplication.translate _translate = QCoreApplication.translate
@ -33,6 +34,7 @@ solver_long_name = {
"mage8": "Mage v8", "mage8": "Mage v8",
# "mage_fake7": "Mage fake v7", # "mage_fake7": "Mage fake v7",
"adistswc": "Adis-TS_WC", "adistswc": "Adis-TS_WC",
"adisttwc": "Adis-TT_WC",
# "rubarbe": "RubarBE", # "rubarbe": "RubarBE",
"rubar3": "Rubar3", "rubar3": "Rubar3",
} }
@ -43,6 +45,7 @@ solver_type_list = {
"mage8": Mage8, "mage8": Mage8,
# "mage_fake7": MageFake7, # "mage_fake7": MageFake7,
"adistswc": AdisTSwc, "adistswc": AdisTSwc,
"adisttwc": AdisTTwc,
# "rubarbe": RubarBE, # "rubarbe": RubarBE,
"rubar3": Rubar3, "rubar3": Rubar3,
} }

View File

@ -91,6 +91,11 @@ from View.Results.CompareDialog import (
CompareScenariosWindow CompareScenariosWindow
) )
from View.RunSolver.WindowAdisTT import (
SelectSolverWindowAdisTT,
SolverLogWindowAdisTT
)
from View.RunSolver.WindowAdisTS import ( from View.RunSolver.WindowAdisTS import (
SelectSolverWindowAdisTS, SelectSolverWindowAdisTS,
SolverLogWindowAdisTS SolverLogWindowAdisTS
@ -172,7 +177,7 @@ define_model_action = [
"action_menu_d90", "action_menu_dif", "action_menu_edit_geotiff", "action_menu_d90", "action_menu_dif", "action_menu_edit_geotiff",
"action_menu_boundary_conditions_temperature", "action_menu_boundary_conditions_temperature",
"action_menu_initial_conditions_temperature", "action_menu_initial_conditions_temperature",
"action_menu_weather_parameters", "action_menu_weather_parameters", "action_menu_run_adistt",
] ]
action = ( action = (
@ -291,7 +296,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_dif": self.open_dif, "action_menu_dif": self.open_dif,
"action_menu_d90": self.open_d90, "action_menu_d90": self.open_d90,
"action_menu_pollutants": self.open_pollutants, "action_menu_pollutants": self.open_pollutants,
"action_menu_run_adists": self.select_run_solver_adists, "action_menu_run_adists":
lambda: self.select_run_solver_adists(mode="adists"),
"action_menu_run_adistt":
lambda: self.select_run_solver_adists(mode="adistt"),
"action_menu_output_rk": self.open_output_rk_adists, "action_menu_output_rk": self.open_output_rk_adists,
"action_menu_config": self.open_configure, "action_menu_config": self.open_configure,
"action_menu_new": self.open_new_study, "action_menu_new": self.open_new_study,
@ -1555,7 +1563,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
if run.exec(): if run.exec():
self.run_solver(run.solver) self.run_solver(run.solver)
def select_run_solver_adists(self): def select_run_solver_adists(self, mode="adists"):
if self._study is None: if self._study is None:
return return
@ -1571,9 +1579,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
parent=self parent=self
) )
if run.exec(): if run.exec():
self.run_solver_adists(run.solver, run.mage_rep) self.run_solver_adists(run.solver, run.mage_rep, mode=mode)
def run_solver_adists(self, solver, mage_rep): def run_solver_adists(self, solver, mage_rep, mode="adists"):
if self._study is None: if self._study is None:
return return

View File

@ -0,0 +1,505 @@
# WindowAdisTS.py -- Pamhyr
# Copyright (C) 2023-2025 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import os
import logging
import tempfile
from queue import Queue
from tools import trace, timer, logger_exception
from View.Tools.PamhyrWindow import PamhyrDialog, PamhyrWindow
from PyQt5.QtGui import (
QKeySequence,
)
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QRect, QTimer, QProcess,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
QTextEdit,
)
from View.RunSolver.Log.Window import SolverLogFileWindow
from View.Results.ReadingResultsDialog import ReadingResultsDialog
from View.WaitingDialog import WaitingDialog
try:
from signal import SIGTERM, SIGSTOP, SIGCONT
_signal = True
except Exception:
_signal = False
_translate = QCoreApplication.translate
logger = logging.getLogger()
class SelectSolverWindowAdisTT(PamhyrDialog):
_pamhyr_ui = "SelectSolverAdisTT"
_pamhyr_name = "Select solver"
def __init__(self, study=None, config=None,
parent=None):
self._solver = None
name = _translate("Solver", "Select solver")
super(SelectSolverWindowAdisTT, self).__init__(
title=name,
study=study,
config=config,
options=[],
parent=parent
)
self.setup_combobox()
self.setup_connections()
self.select_last_solver()
def setup_combobox(self):
# solvers = self._config.solvers
# solvers mage
solvers = list(filter(
lambda x: "mage" not in x._type and "rubar" not in x._type,
self._config.solvers
))
solvers_name = list(
map(
self._format_solver_name,
solvers
)
)
solvers_mage = list(filter(
lambda x: "mage" or "rubar" in x._type.lower(),
self._config.solvers
))
solvers_mage_names = list(map(lambda x: x._name, solvers_mage))
solvers_dir = os.path.join(
os.path.dirname(self._study.filename),
"_PAMHYR_",
self._study.name.replace(" ", "_"),
)
dir_solvers_List = os.listdir(solvers_dir)
display_mage_names = list(filter(
lambda x: x in solvers_mage_names, dir_solvers_List
))
self.combobox_add_items("comboBox", solvers_name)
self.combobox_add_items("comboBoxRepMage", display_mage_names)
def setup_connections(self):
self.find(QPushButton, "pushButton_run").clicked.connect(self.accept)
self.find(QPushButton, "pushButton_cancel") \
.clicked.connect(self.reject)
def select_last_solver(self):
solvers = self._config.solvers
last = self._config.last_solver_name
solver = list(
filter(
lambda s: s.name == last,
solvers
)
)
if len(solver) != 0:
self.set_combobox_text(
"comboBox",
self._format_solver_name(solver[0])
)
def _format_solver_name(self, solver):
return f"{solver.name} - ({solver._type})"
@property
def solver(self):
return self._solver
@property
def mage_rep(self):
return self._mage_result_rep
def accept(self):
solver_name = self.get_combobox_text("comboBox")
solver_name = solver_name.rsplit(" - ", 1)[0]
self._mage_result_rep = self.get_combobox_text("comboBoxRepMage")
self._config.update_last_solver_used(solver_name)
self._solver = next(
filter(
lambda s: s.name == solver_name,
self._config.solvers
)
)
super(SelectSolverWindowAdisTT, self).accept()
class SolverLogWindowAdisTT(PamhyrWindow):
_pamhyr_ui = "SolverLogAdisTT"
_pamhyr_name = "Solver Log"
def __init__(self, study=None, config=None,
solver=None, parent=None, mage_rep=None):
self._solver = solver
self._results = None
self._results_mage = None
self._mage_rep = mage_rep
name = _translate("Solver", "Select log")
super(SolverLogWindowAdisTT, self).__init__(
title=name,
study=study,
config=config,
options=[],
parent=parent
)
self._workdir = ""
self._workdir_mage = ""
self.setup_action()
self.setup_alarm()
self.setup_connections()
self.setup_workdir()
self.setup_process()
ok = self.export()
if ok:
self.run()
else:
self._log(
f" *** Failed to export study to {self._solver._type}",
color="red"
)
def setup_action(self):
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 setup_alarm(self):
self._alarm = QTimer()
def setup_connections(self):
self.find(QAction, "action_start").triggered.connect(self.start)
self.find(QAction, "action_pause").triggered.connect(self.pause)
self.find(QAction, "action_stop").triggered.connect(self.stop)
self.find(QAction, "action_log_file").triggered.connect(self.log_file)
self.find(QAction, "action_results").triggered.connect(self.results)
self.find(QAction, "action_results_Mage").triggered.connect(
self.resultsMage
)
self._alarm.timeout.connect(self.update)
def setup_workdir(self):
scenario = self._study.status.scenario
srep = scenario.workdir()
if self._study.filename == "":
self._workdir = os.path.join(
tempfile.TemporaryDirectory(),
srep
)
else:
self._workdir = os.path.join(
os.path.dirname(self._study.filename),
"_PAMHYR_",
self._study.name.replace(" ", "_"),
self._solver.name.replace(" ", "_"),
srep
)
logger.info(f"Create workdir: '{self._workdir}'")
os.makedirs(self._workdir, exist_ok=True)
self.setup_mage_workdir()
def setup_mage_workdir(self):
scenario = self._study.status.scenario
srep = scenario.workdir()
if self._study.filename == "":
self._workdir_mage = os.path.join(
tempfile.TemporaryDirectory(),
srep
)
else:
self._workdir_mage = os.path.join(
os.path.dirname(self._study.filename),
"_PAMHYR_",
self._study.name.replace(" ", "_"),
self._mage_rep.replace(" ", "_"),
srep
)
logger.info(f"Create workdir: '{self._workdir_mage}'")
def setup_process(self):
self._alarm.start(100)
self._output = Queue()
self._process = self.new_process(self._parent)
def new_process(self, parent):
new = QProcess(parent)
new.setWorkingDirectory(self._workdir)
new.setProcessChannelMode(QProcess.MergedChannels)
return new
def export(self):
self._log(f" *** Export study {self._solver.name}", color="blue")
ok = self._solver.export(
self._study, self._workdir, self._workdir_mage,
qlog=self._output
)
self.update()
return ok
def closeEvent(self, event):
self._alarm.stop()
super(SolverLogWindowAdisTT, self).closeEvent(event)
def _copy(self):
self.find(QTextEdit, "textEdit").copy()
#######
# LOG #
#######
def _log(self, msg, color=None):
if type(msg) is str:
self._log_str(msg, color)
elif type(msg) is int:
self._log_int(msg, color)
def _log_str(self, msg, color=None):
if msg == "":
return
logger.info(f"solver: {msg}")
msg = msg.rsplit('\n', 1)[0]
if color is not None:
msg = f"<font color=\"{color}\">" + msg + "</font>"
self.find(QTextEdit, "textEdit").append(msg)
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
)
##########
# UPDATE #
##########
def update(self):
if self._solver.is_stoped():
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)
self._update_logs_all()
# self._update_get_results()
self._update_logs_all()
def _update_get_results(self):
if self._results is None:
def reading_fn():
try:
self._results = self._solver.results(
self._study, self._workdir, qlog=self._output
)
self._parent.set_results(self._solver, self._results)
except Exception as e:
logger.error(f"Failed to open results")
logger_exception(e)
dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
dlg.exec_()
def _update_logs_all(self):
while self._output.qsize() != 0:
s = self._output.get()
try:
if type(s) is str and "[ERROR]" in s:
self._log(s.encode("utf-8"), color="red")
else:
self._log(s)
except Exception as e:
logger_exception(e)
####################
# Process controle #
####################
def run(self):
self._log(f" *** Run solver {self._solver.name}", color="blue")
self._solver.run(
self._study,
process=self._process,
output_queue=self._output
)
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, self._workdir_mage,
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._solver.start(self._study, process=self._process)
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 pause(self):
self._log(" *** Pause", color="blue")
self._solver.pause()
self.find(QAction, "action_start").setEnabled(True)
self.find(QAction, "action_pause").setEnabled(False)
self.find(QAction, "action_stop").setEnabled(True)
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)
###########
# Results #
###########
def results(self):
if self._results is None:
dir_list = os.listdir(self._workdir)
if "resultats" in dir_list:
dir_path = os.path.join(self._workdir, "resultats")
else:
dir_path = self._workdir
def reading_fn():
self._results = self._solver.results(
self._study, dir_path, qlog=self._output
)
dlg = WaitingDialog(payload_fn=reading_fn,
title="waiting_result",
parent=self)
dlg.exec_()
self._parent.set_results(self._solver, self._results)
self._parent.open_solver_results_adistt(self._solver, self._results)
self._solver.has_results_loaded()
def resultsMage(self):
mage_solver = next(filter(
lambda x: x._name == self._mage_rep, self._config.solvers
))
if self._results_mage is None:
workdir_mage = self._workdir_mage
def reading_fn():
self._results_mage = mage_solver.results(
self._study, workdir_mage, qlog=self._output
)
dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
dlg.exec_()
self._parent.set_results(mage_solver, self._results_mage)
self._parent.open_solver_results(mage_solver, self._results_mage)
mage_solver.has_results_loaded()
def log_file(self):
file_name = os.path.join(self._workdir, self._solver.log_file())
log = SolverLogFileWindow(
file_name=file_name,
study=self._study,
config=self._config,
solver=self._solver,
parent=self,
)
log.show()

View File

@ -138,6 +138,7 @@
<addaction name="action_menu_numerical_parameter"/> <addaction name="action_menu_numerical_parameter"/>
<addaction name="action_menu_run_solver"/> <addaction name="action_menu_run_solver"/>
<addaction name="action_menu_run_adists"/> <addaction name="action_menu_run_adists"/>
<addaction name="action_menu_run_adistt"/>
</widget> </widget>
<widget class="QMenu" name="menu_Hydraulics"> <widget class="QMenu" name="menu_Hydraulics">
<property name="locale"> <property name="locale">
@ -853,6 +854,15 @@
<string>Lateral contributions ?</string> <string>Lateral contributions ?</string>
</property> </property>
</action> </action>
<action name="action_menu_run_adistt">
<property name="icon">
<iconset>
<normaloff>ressources/run.png</normaloff>ressources/run.png</iconset>
</property>
<property name="text">
<string>Run AdisTT</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>384</width>
<height>107</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="locale">
<locale language="English" country="Europe"/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>AdisTS Solver:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="comboBox"/>
</item>
<item row="4" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_run">
<property name="text">
<string>Run</string>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/run.png</normaloff>ressources/run.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QComboBox" name="comboBoxRepMage"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Mage Repertory:</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1152</width>
<height>648</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTextEdit" name="textEdit">
<property name="font">
<font>
<family>Monospace</family>
<pointsize>10</pointsize>
</font>
</property>
<property name="locale">
<locale language="English" country="Europe"/>
</property>
<property name="documentTitle">
<string notr="true"/>
</property>
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1152</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="enabled">
<bool>true</bool>
</property>
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_start"/>
<addaction name="action_pause"/>
<addaction name="action_stop"/>
<addaction name="action_log_file"/>
<addaction name="action_results"/>
<addaction name="action_results_Mage"/>
</widget>
<action name="action_stop">
<property name="icon">
<iconset>
<normaloff>ressources/close.png</normaloff>ressources/close.png</iconset>
</property>
<property name="text">
<string>Stop</string>
</property>
</action>
<action name="action_start">
<property name="icon">
<iconset>
<normaloff>ressources/run.png</normaloff>ressources/run.png</iconset>
</property>
<property name="text">
<string>Start</string>
</property>
</action>
<action name="action_pause">
<property name="icon">
<iconset>
<normaloff>ressources/player_pause.png</normaloff>ressources/player_pause.png</iconset>
</property>
<property name="text">
<string>Pause</string>
</property>
</action>
<action name="action_log_file">
<property name="icon">
<iconset>
<normaloff>ressources/zoom.png</normaloff>ressources/zoom.png</iconset>
</property>
<property name="text">
<string>LogFile</string>
</property>
</action>
<action name="action_results">
<property name="text">
<string>results</string>
</property>
</action>
<action name="action_results_Mage">
<property name="text">
<string>results_Mage</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -37,7 +37,7 @@ logger = logging.getLogger()
class Config(SQL): class Config(SQL):
def __init__(self): def __init__(self):
self._version = '0.0.7' self._version = '0.0.8'
self.filename = Config.filename() self.filename = Config.filename()
self.set_default_value() self.set_default_value()
@ -194,6 +194,27 @@ class Config(SQL):
"WHERE name='default-Rubar3'" "WHERE name='default-Rubar3'"
) )
if major == "0" and minor <= "2":
if int(release) < 8:
posix = os.name == 'posix'
ext = "" if posix else ".exe"
path = os.path.join("@install_dir",
"adists", f"adists{ext}")
self.execute(f"""
INSERT OR IGNORE INTO solver VALUES (
'adisttwc',
'default-AdisTT',
'Default Pamhyr2 AdisTT version',
'', '', '',
'',
'{path} @args @input',
''
)
""")
self.execute( self.execute(
f"UPDATE info SET value='{self._version}' " + f"UPDATE info SET value='{self._version}' " +
"WHERE key='version'" "WHERE key='version'"
@ -368,6 +389,14 @@ class Config(SQL):
new._cmd_solver = f""""{path}{ext}" @args @input""" new._cmd_solver = f""""{path}{ext}" @args @input"""
self._solvers.append(new) self._solvers.append(new)
# AdisTT
ctor = solver_type_list["adisttwc"]
new = ctor("default-AdisTT")
new._description = "Default Pamhyr2 AdisTT version"
path = os.path.join("@install_dir", "adists", "adists")
new._cmd_solver = f"\"{path}{ext}\" @args @input"
self._solvers.append(new)
# Rubar3 # Rubar3
ctor = solver_type_list["rubar3"] ctor = solver_type_list["rubar3"]
new = ctor("default-Rubar3") new = ctor("default-Rubar3")