Hooked up simulation to BEsim
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QObject, Qt
|
||||
from PySide6.QtCore import QObject, Signal, Qt
|
||||
from PySide6.QtWidgets import QApplication, QDialog, QFileDialog, QInputDialog, QMessageBox
|
||||
|
||||
from bedit_gui.services import document_files, simulation_files
|
||||
@@ -17,6 +17,8 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
class SimulationFileController(QObject):
|
||||
runtime_changed = Signal()
|
||||
|
||||
def __init__(self, window: SimulationWindow) -> None:
|
||||
super().__init__(window)
|
||||
self.window = window
|
||||
@@ -34,6 +36,7 @@ class SimulationFileController(QObject):
|
||||
self.compiled_model = None
|
||||
self.path = None
|
||||
self._update_window()
|
||||
self.runtime_changed.emit()
|
||||
logger.info("Created new simulation")
|
||||
|
||||
def open(self, path: str | Path, *, component: str | None = None, simulation: str | None = None, backed_by_file: bool = True, compiled_model: CompiledModel | None = None) -> None:
|
||||
@@ -51,6 +54,7 @@ class SimulationFileController(QObject):
|
||||
raise
|
||||
self._log_compilation()
|
||||
self._update_window()
|
||||
self.runtime_changed.emit()
|
||||
logger.info("Opened simulation: %s", file_path)
|
||||
|
||||
def open_dialog(self) -> None:
|
||||
@@ -111,9 +115,12 @@ class SimulationFileController(QObject):
|
||||
self.root.component = updated.component
|
||||
self.root.component_path = next((path for component_id, _component, path in components if component_id == updated.component), self.root.component_path)
|
||||
self.root.settings_name = updated.name
|
||||
self.root.current_end_time = updated.start_time
|
||||
self.root.results.clear()
|
||||
if component_changed:
|
||||
self.compiled_model = None
|
||||
self._update_window()
|
||||
self.runtime_changed.emit()
|
||||
logger.info("Updated simulation settings: %s", updated.name)
|
||||
|
||||
def _source_components(self) -> list[tuple]:
|
||||
|
||||
155
src/bedit_gui/controllers/simulation_run_controller.py
Normal file
155
src/bedit_gui/controllers/simulation_run_controller.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from threading import Thread
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
from bedit_gui.controllers.simulation_file_controller import SimulationFileController
|
||||
from bedit_gui.services.application_logging import get_logger
|
||||
from bedit_gui.simulation_models import CompiledModel
|
||||
from bedit_gui.views.simulation_window import SimulationWindow
|
||||
from bedit_simulation import SimulationCancelledError, SimulationResult, SimulationRunSettings, SimulationSession
|
||||
|
||||
logger = get_logger(__name__)
|
||||
SessionFactory = Callable[[CompiledModel, SimulationRunSettings, float | None, list[SimulationResult]], SimulationSession]
|
||||
|
||||
|
||||
def _create_session(compiled: CompiledModel, settings: SimulationRunSettings, current_end_time: float | None, results: list[SimulationResult]) -> SimulationSession:
|
||||
return SimulationSession(compiled.model_name, compiled.executable, settings, current_end_time=current_end_time, results=results)
|
||||
|
||||
|
||||
class SimulationRunController(QObject):
|
||||
run_completed = Signal(object)
|
||||
run_cancelled = Signal()
|
||||
run_failed = Signal(object)
|
||||
|
||||
def __init__(self, window: SimulationWindow, files: SimulationFileController, session_factory: SessionFactory = _create_session) -> None:
|
||||
super().__init__(window)
|
||||
self.window = window
|
||||
self.files = files
|
||||
self.session_factory = session_factory
|
||||
self.session: SimulationSession | None = None
|
||||
self._running = False
|
||||
self._reset_pending = False
|
||||
|
||||
window.ui.actionRun_Simulation.triggered.connect(self.start)
|
||||
window.ui.actionStop_Simulation.triggered.connect(self.stop)
|
||||
window.ui.actionRestart_Simulation.triggered.connect(self.restart)
|
||||
files.runtime_changed.connect(self._load_session)
|
||||
self.run_completed.connect(self._completed)
|
||||
self.run_cancelled.connect(self._cancelled)
|
||||
self.run_failed.connect(self._failed)
|
||||
self._load_session()
|
||||
|
||||
def start(self) -> None:
|
||||
if self._running or self.session is None:
|
||||
return
|
||||
start_time = self.session.current_end_time
|
||||
stop_time = start_time + self.session.settings.duration
|
||||
self._running = True
|
||||
self._update_actions()
|
||||
logger.info("Starting simulation from %s to %s", start_time, stop_time)
|
||||
Thread(target=self._run_worker, args=(self.session,), name="besim-run", daemon=True).start()
|
||||
|
||||
def stop(self) -> None:
|
||||
if not self._running or self.session is None:
|
||||
return
|
||||
self.session.cancel()
|
||||
logger.info("Stopping simulation")
|
||||
|
||||
def restart(self) -> None:
|
||||
if self.session is None:
|
||||
return
|
||||
if self._running:
|
||||
self._reset_pending = True
|
||||
logger.info("Reset requested; stopping the active simulation")
|
||||
self.stop()
|
||||
return
|
||||
self._reset()
|
||||
|
||||
def _reset(self) -> None:
|
||||
if self.session is None:
|
||||
return
|
||||
self.session.reset()
|
||||
self._store_session_state()
|
||||
logger.info("Reset simulation to start time %s", self.session.current_end_time)
|
||||
self._update_actions()
|
||||
|
||||
def _run_worker(self, session: SimulationSession) -> None:
|
||||
try:
|
||||
result = asyncio.run(session.run_next())
|
||||
except SimulationCancelledError:
|
||||
self.run_cancelled.emit()
|
||||
except (OSError, RuntimeError, TypeError, ValueError) as exc:
|
||||
self.run_failed.emit(exc)
|
||||
else:
|
||||
self.run_completed.emit(result)
|
||||
|
||||
def _completed(self, result: SimulationResult) -> None:
|
||||
self._running = False
|
||||
self._store_session_state()
|
||||
logger.info("Simulation completed at time %s", self.session.current_end_time if self.session is not None else "unknown")
|
||||
if result.process_output.strip():
|
||||
logger.info("Simulation output:\n%s", result.process_output.strip())
|
||||
if result.process_errors.strip():
|
||||
logger.warning("Simulation errors:\n%s", result.process_errors.strip())
|
||||
self._finish_run()
|
||||
|
||||
def _cancelled(self) -> None:
|
||||
self._running = False
|
||||
logger.info("Simulation stopped")
|
||||
self._finish_run()
|
||||
|
||||
def _failed(self, error: Exception) -> None:
|
||||
self._running = False
|
||||
logger.error("Simulation failed: %s", error, exc_info=(type(error), error, error.__traceback__))
|
||||
self._finish_run()
|
||||
|
||||
def _finish_run(self) -> None:
|
||||
self._update_actions()
|
||||
if self._reset_pending:
|
||||
self._reset_pending = False
|
||||
self._reset()
|
||||
|
||||
def _load_session(self) -> None:
|
||||
root = self.files.root
|
||||
compiled = self.files.compiled_model
|
||||
self._reset_pending = False
|
||||
if root is None or compiled is None:
|
||||
self.session = None
|
||||
else:
|
||||
settings = root.settings
|
||||
try:
|
||||
run_settings = SimulationRunSettings(
|
||||
start_time=settings.start_time,
|
||||
duration=settings.duration,
|
||||
use_timed_steps=settings.use_timed_steps,
|
||||
number_of_steps=settings.number_of_steps,
|
||||
step_size=settings.step_size,
|
||||
tolerance=settings.dassl_tolerance,
|
||||
method=settings.method.value,
|
||||
)
|
||||
self.session = self.session_factory(compiled, run_settings, root.current_end_time, root.results)
|
||||
except (OSError, RuntimeError, ValueError):
|
||||
self.session = None
|
||||
logger.exception("Could not initialize the simulation runtime")
|
||||
self._update_actions()
|
||||
|
||||
def _update_actions(self) -> None:
|
||||
available = self.session is not None
|
||||
self.window.ui.actionRun_Simulation.setEnabled(available and not self._running)
|
||||
self.window.ui.actionStop_Simulation.setEnabled(available and self._running)
|
||||
self.window.ui.actionRestart_Simulation.setEnabled(available)
|
||||
for action in (self.window.ui.actionNew_Simulation_Run, self.window.ui.actionOpen_Simulation_Run, self.window.ui.actionSave_Simulation_Run, self.window.ui.actionSimulation_Options):
|
||||
action.setEnabled(not self._running and (available or action is not self.window.ui.actionSimulation_Options))
|
||||
if self.session is not None:
|
||||
state = "running" if self._running else "ready"
|
||||
self.window.statusBar().showMessage(f"Simulation {state} · current time {self.session.current_end_time}")
|
||||
|
||||
def _store_session_state(self) -> None:
|
||||
if self.session is None or self.files.root is None:
|
||||
return
|
||||
self.files.root.current_end_time = self.session.current_end_time
|
||||
self.files.root.results = list(self.session.results)
|
||||
Reference in New Issue
Block a user