Compare commits

...

4 Commits

Author SHA1 Message Date
a38d461a36 compilation output to logs 2026-07-31 12:52:04 +02:00
22e9a0386c BEsim logger 2026-07-31 12:48:42 +02:00
89ac2d8ff8 Launching of simulation application 2026-07-31 12:24:52 +02:00
66737e323b Start of simulation application 2026-07-31 11:46:06 +02:00
25 changed files with 1020 additions and 12 deletions

18
.vscode/launch.json vendored
View File

@@ -5,7 +5,7 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Python Debugger: Module", "name": "BEdit debug",
"type": "debugpy", "type": "debugpy",
"request": "launch", "request": "launch",
"module": "bedit_gui", "module": "bedit_gui",
@@ -18,6 +18,22 @@
"args": [ "args": [
"-f", "${workspaceFolder}/untitled.bedit.json" "-f", "${workspaceFolder}/untitled.bedit.json"
] ]
},
{
"name": "BEsim debug",
"type": "debugpy",
"request": "launch",
"module": "bedit_gui.simulation_application",
"preLaunchTask": "Qt: Generate files",
"cwd": "${workspaceFolder}",
"env": {
"QT_QPA_PLATFORMTHEME": "qt6ct",
"QT_QPA_PLATFORM": "xcb"
},
"args": [
"-s", "run_bondgraph",
"${workspaceFolder}/untitled.besim.json"
]
} }
] ]
} }

View File

@@ -161,6 +161,18 @@ Text widgets use their native `copy()`, `cut()`, and `paste()` methods. Componen
- Scope destructive shortcuts to the relevant widget where appropriate. - Scope destructive shortcuts to the relevant widget where appropriate.
- Always guard the operation itself even when an action is disabled for presentation. - Always guard the operation itself even when an action is disabled for presentation.
## Simulator application
- `bedit_gui/simulation_application.py` is the composition root for the separate `bedit-sim` Qt application.
- Keep compilation GUI-independent. Shared compile entry points belong in `bedit_simulation` and must not create or depend on a `QApplication` or window.
- The simulator opens BEdit `.beb`/`.json` documents or serialized simulation `.bes`/`.json` files.
- A component selector uses default simulation settings. A stored simulation-settings block already identifies its component; do not require both selectors.
- `SimulationDatabase.active_simulation` is the persisted active settings ID selected when the Simulation Settings dialog is accepted.
- Simulation files serialize `bedit_gui.simulation_models.SimulationRoot`. JSON uses the `root_type = "simulation_root"` discriminator; binary `.bes` files use their own BES header.
- `SimulationRoot` contains source-document identity, the selected component and copied settings, and eventually results. Compiled artifacts are transient application state and must not be serialized; reopen saved simulations by compiling them again. Never serialize live Python objects.
- BEdit launches the simulator as a separate process. Compile/Open Simulation integration may transfer a `.bes` file to that process.
- Keep simulator file workflows in their own services/controllers rather than adding them to `MainWindow` or the editor document controller.
## Verification ## Verification
The old test suite and its VS Code/packaging references were deliberately removed. Do not recreate a test suite unless asked. The old test suite and its VS Code/packaging references were deliberately removed. Do not recreate a test suite unless asked.

View File

@@ -18,6 +18,7 @@ dependencies = [
bedit-graphviz = "bedit_util.graphviz:main" bedit-graphviz = "bedit_util.graphviz:main"
bedit-simulate = "bedit_util.simulate:main" bedit-simulate = "bedit_util.simulate:main"
bedit = "bedit_gui.application:main" bedit = "bedit_gui.application:main"
besim = "bedit_gui.simulation_application:main"
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [

View File

@@ -10,6 +10,7 @@ from bedit_gui.controllers.document_controller import DocumentController
from bedit_gui.controllers.log_controller import LogController from bedit_gui.controllers.log_controller import LogController
from bedit_gui.controllers.settings_controller import SettingsController from bedit_gui.controllers.settings_controller import SettingsController
from bedit_gui.controllers.simulation_settings_controller import SimulationSettingsController from bedit_gui.controllers.simulation_settings_controller import SimulationSettingsController
from bedit_gui.controllers.simulation_controller import SimulationController
from bedit_gui.controllers.undo_controller import UndoController from bedit_gui.controllers.undo_controller import UndoController
from bedit_gui.controllers.view_menu_controller import ViewMenuController from bedit_gui.controllers.view_menu_controller import ViewMenuController
from bedit_gui.controllers.window_state_controller import WindowStateController from bedit_gui.controllers.window_state_controller import WindowStateController
@@ -48,6 +49,7 @@ def main() -> int:
DocumentController(document, window) DocumentController(document, window)
SettingsController(window, settings) SettingsController(window, settings)
SimulationSettingsController(document, window) SimulationSettingsController(document, window)
SimulationController(document, window)
UndoController(document, window) UndoController(document, window)
ViewMenuController(window) ViewMenuController(window)
document_tree_controller = DocumentTreeController(document, window) document_tree_controller = DocumentTreeController(document, window)

View File

@@ -6,6 +6,7 @@ from PySide6.QtCore import QObject, Signal
from bedit_gui.services.application_logging import configure_logging from bedit_gui.services.application_logging import configure_logging
from bedit_gui.views.main_window import MainWindow from bedit_gui.views.main_window import MainWindow
from bedit_gui.views.simulation_window import SimulationWindow
from bedit_gui.views.models import LogListModel from bedit_gui.views.models import LogListModel
@@ -30,7 +31,7 @@ class LogController(QObject):
def __init__( def __init__(
self, self,
window: MainWindow, window: MainWindow | SimulationWindow,
level: int | str = logging.INFO, level: int | str = logging.INFO,
) -> None: ) -> None:
super().__init__(window) super().__init__(window)

View File

@@ -0,0 +1,99 @@
from __future__ import annotations
import sys
import tempfile
from collections.abc import Callable
from pathlib import Path
from PySide6.QtCore import QObject, QProcess, Qt
from PySide6.QtWidgets import QApplication, QMessageBox
from bedit_core.models import Component
from bedit_gui.documents import Document
from bedit_gui.services import simulation_files
from bedit_gui.services.application_logging import get_logger
from bedit_gui.services.simulation_loader import component_choices
from bedit_gui.simulation_models import CompiledModel, SimulationRoot
from bedit_gui.views.main_window import MainWindow
from bedit_simulation import ModelBuildResult, compile_component_sync
logger = get_logger(__name__)
Compiler = Callable[[Component, str | Path], ModelBuildResult]
Launcher = Callable[[Path, CompiledModel], bool]
def _launch_simulator(path: Path, compiled_model: CompiledModel) -> bool:
arguments = ["-m", "bedit_gui.simulation_application", "--handoff", "--model-name", compiled_model.model_name, "--executable", compiled_model.executable, "--working-directory", compiled_model.working_directory, str(path)]
launched = QProcess.startDetached(sys.executable, arguments)
return launched[0] if isinstance(launched, tuple) else bool(launched)
class SimulationController(QObject):
"""Compile the active BEdit simulation and launch the simulator process."""
def __init__(self, document: Document, window: MainWindow, compiler: Compiler = compile_component_sync, launcher: Launcher = _launch_simulator) -> None:
super().__init__(window)
self.document = document
self.window = window
self.compiler = compiler
self.launcher = launcher
self.compiled_root: SimulationRoot | None = None
self.compiled_model: CompiledModel | None = None
window.ui.actionCompile_Model.triggered.connect(self.compile_model)
window.ui.actionOpen_Simulation_Window.triggered.connect(self.open_simulation)
def compile_model(self) -> SimulationRoot | None:
database = self.document.simulation_database()
settings = database.simulations.get(database.active_simulation) if database.active_simulation is not None else None
if settings is None:
QMessageBox.warning(self.window, "No active simulation", "Select and accept a simulation settings block first.")
return None
match = next((choice for choice in component_choices(self.document.model) if choice[0] == settings.component), None)
if match is None:
QMessageBox.critical(self.window, "Could not compile", "The active simulation references a missing component.")
return None
component_id, component, component_path = match
build_directory = Path(tempfile.mkdtemp(prefix="bedit-compiled-"))
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
self.window.statusBar().showMessage(f"Compiling {component_path}")
logger.info("Compiling model: %s", component_path)
try:
build = self.compiler(component, build_directory)
except (OSError, RuntimeError, ValueError) as exc:
logger.exception("Could not compile model %s", component_path)
QMessageBox.critical(self.window, "Could not compile", str(exc))
self.window.statusBar().showMessage("Compilation failed")
return None
finally:
QApplication.restoreOverrideCursor()
self.compiled_model = CompiledModel(build.model_name, str(build.executable.resolve()), str(build.executable.parent.resolve()), build.output, build.errors)
self.compiled_root = SimulationRoot(
format_version=1,
source_document=str(self.document.path.resolve()) if self.document.path is not None else None,
source_document_id=str(self.document.model.id),
component=component_id,
component_path=component_path,
settings_name=settings.name,
settings=settings,
)
self.window.statusBar().showMessage(f"Compiled {component_path}")
logger.info("Compiled model %s: %s", build.model_name, self.compiled_model.executable)
if build.output.strip():
logger.info("Compiler output:\n%s", build.output.strip())
if build.errors.strip():
logger.warning("Compiler errors:\n%s", build.errors.strip())
return self.compiled_root
def open_simulation(self) -> None:
root = self.compile_model()
if root is None or self.compiled_model is None:
return
transfer_directory = Path(tempfile.mkdtemp(prefix="bedit-simulator-launch-"))
transfer_path = transfer_directory / "simulation.bes"
try:
simulation_files.save(root, transfer_path)
if not self.launcher(transfer_path, self.compiled_model):
raise RuntimeError("the simulator process could not be started")
except (OSError, RuntimeError, ValueError) as exc:
QMessageBox.critical(self.window, "Could not open simulator", str(exc))

View File

@@ -0,0 +1,163 @@
from __future__ import annotations
from pathlib import Path
from PySide6.QtCore import QObject, Qt
from PySide6.QtWidgets import QApplication, QDialog, QFileDialog, QInputDialog, QMessageBox
from bedit_gui.services import document_files, simulation_files
from bedit_gui.services.simulation_loader import compile_simulation_root, component_choices, load_and_compile_bedit
from bedit_gui.models import SimulationDatabase, SimulationID
from bedit_gui.services.application_logging import get_logger
from bedit_gui.simulation_models import CompiledModel, SimulationRoot
from bedit_gui.views.dialogs.simulation_settings_dialog import SimulationSettingsDialog
from bedit_gui.views.simulation_window import SimulationWindow
logger = get_logger(__name__)
class SimulationFileController(QObject):
def __init__(self, window: SimulationWindow) -> None:
super().__init__(window)
self.window = window
self.root: SimulationRoot | None = None
self.compiled_model: CompiledModel | None = None
self.path: Path | None = None
window.ui.actionNew_Simulation_Run.triggered.connect(self.new)
window.ui.actionOpen_Simulation_Run.triggered.connect(self.open_dialog)
window.ui.actionSave_Simulation_Run.triggered.connect(self.save)
window.ui.actionSimulation_Options.triggered.connect(self.open_simulation_settings)
self._update_window()
def new(self) -> None:
self.root = None
self.compiled_model = None
self.path = None
self._update_window()
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:
file_path = Path(path)
try:
if file_path.suffix.lower() == ".bes" or simulation_files.is_simulation_json(file_path):
self.root = simulation_files.load(file_path)
self.compiled_model = compiled_model or self._recompile_with_wait_cursor(self.root)
self.path = file_path if backed_by_file else None
else:
self.root, self.compiled_model = self._compile_with_wait_cursor(file_path, component=component, simulation=simulation)
self.path = None
except (OSError, RuntimeError, TypeError, ValueError):
logger.exception("Could not open simulation %s", file_path)
raise
self._log_compilation()
self._update_window()
logger.info("Opened simulation: %s", file_path)
def open_dialog(self) -> None:
filename, _ = QFileDialog.getOpenFileName(self.window, "Open Simulation", "", "Simulation and BEdit files (*.bes *.beb *.json)")
if not filename:
return
try:
path = Path(filename)
if path.suffix.lower() == ".bes" or simulation_files.is_simulation_json(path):
self.open(path)
return
document = document_files.load(path)
database = document.metadata.get("simulation_database") if document.metadata is not None else None
entries = [(f"Simulation: {simulation.name}", None, simulation.name) for simulation in getattr(database, "simulations", {}).values()]
entries.extend((f"Component: {component_path}", component_path, None) for _component_id, _component, component_path in component_choices(document))
if not entries:
raise ValueError("the BEdit document contains no components or simulation settings")
label, accepted = QInputDialog.getItem(self.window, "Simulation Target", "Compile:", [entry[0] for entry in entries], 0, False)
if accepted:
_display, component, simulation = next(entry for entry in entries if entry[0] == label)
self.open(path, component=component, simulation=simulation)
except (OSError, RuntimeError, ValueError) as exc:
QMessageBox.critical(self.window, "Could not open simulation", str(exc))
def save(self) -> None:
if self.root is None:
return
path = self.path
if path is None:
filename, _ = QFileDialog.getSaveFileName(self.window, "Save Simulation", "simulation.bes", "BEdit simulation (*.bes);;Simulation JSON (*.json)")
if not filename:
return
path = Path(filename)
if path.suffix.lower() not in (".bes", ".json"):
path = path.with_suffix(".bes")
try:
simulation_files.save(self.root, path)
except (OSError, TypeError, ValueError) as exc:
logger.exception("Could not save simulation %s", path)
QMessageBox.critical(self.window, "Could not save simulation", str(exc))
return
self.path = path
self._update_window()
logger.info("Saved simulation: %s", path)
def open_simulation_settings(self) -> None:
if self.root is None:
return
simulation_id = SimulationID()
database = SimulationDatabase(simulations={simulation_id: self.root.settings}, active_simulation=simulation_id)
components = self._source_components()
dialog = SimulationSettingsDialog(database, [(component_id, path) for component_id, _component, path in components], self.window, show_simulation_list=False)
if dialog.exec() != QDialog.DialogCode.Accepted:
return
updated = dialog.database().simulations[simulation_id]
component_changed = updated.component != self.root.component
self.root.settings = updated
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
if component_changed:
self.compiled_model = None
self._update_window()
logger.info("Updated simulation settings: %s", updated.name)
def _source_components(self) -> list[tuple]:
if self.root is not None and self.root.source_document is not None:
try:
return component_choices(document_files.load(self.root.source_document))
except (OSError, TypeError, ValueError):
logger.warning("Could not load source components from %s", self.root.source_document, exc_info=True)
if self.root is None:
return []
return [(self.root.component, None, self.root.component_path)]
def _compile_with_wait_cursor(self, path: Path, *, component: str | None, simulation: str | None) -> tuple[SimulationRoot, CompiledModel]:
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
try:
return load_and_compile_bedit(path, component_selector=component, simulation_selector=simulation)
finally:
QApplication.restoreOverrideCursor()
def _recompile_with_wait_cursor(self, root: SimulationRoot) -> CompiledModel:
QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
try:
return compile_simulation_root(root)
finally:
QApplication.restoreOverrideCursor()
def _update_window(self) -> None:
if self.root is None:
self.window.setWindowTitle("BEdit Simulator")
self.window.statusBar().showMessage("No simulation loaded")
self.window.ui.actionSave_Simulation_Run.setEnabled(False)
self.window.ui.actionSimulation_Options.setEnabled(False)
return
self.window.setWindowTitle(f"{self.root.settings.name} — BEdit Simulator")
compiled = self.compiled_model.executable if self.compiled_model is not None else "not compiled"
self.window.statusBar().showMessage(f"{self.root.component_path} · {compiled}")
self.window.ui.actionSave_Simulation_Run.setEnabled(True)
self.window.ui.actionSimulation_Options.setEnabled(True)
def _log_compilation(self) -> None:
if self.compiled_model is None:
return
logger.info("Compiled model %s: %s", self.compiled_model.model_name, self.compiled_model.executable)
if self.compiled_model.output.strip():
logger.info("Compiler output:\n%s", self.compiled_model.output.strip())
if self.compiled_model.errors.strip():
logger.warning("Compiler errors:\n%s", self.compiled_model.errors.strip())

View File

@@ -16,7 +16,7 @@ from bedit_gui.commands.rename_component_command import RenameComponentCommand
from bedit_gui.commands.rename_document_command import RenameDocumentCommand from bedit_gui.commands.rename_document_command import RenameDocumentCommand
from bedit_gui.commands.simulation_database_command import ChangeSimulationDatabaseCommand from bedit_gui.commands.simulation_database_command import ChangeSimulationDatabaseCommand
from bedit_gui.commands.component_command import AddEmptyEquationComponent, AddEmptyGraphComponent, DeleteComponent, PasteComponents from bedit_gui.commands.component_command import AddEmptyEquationComponent, AddEmptyGraphComponent, DeleteComponent, PasteComponents
from bedit_gui.models import Icon, IconDatabase, SimulationDatabase from bedit_gui.models import Icon, IconDatabase, Simulation, SimulationDatabase
from bedit_gui.services import document_files from bedit_gui.services import document_files
@@ -131,6 +131,10 @@ class Document(QObject):
def simulation_database(self) -> SimulationDatabase: def simulation_database(self) -> SimulationDatabase:
return self.stored_simulation_database() or SimulationDatabase() return self.stored_simulation_database() or SimulationDatabase()
def active_simulation(self) -> Simulation | None:
database = self.simulation_database()
return database.simulations.get(database.active_simulation) if database.active_simulation is not None else None
def change_simulation_database(self, database: SimulationDatabase) -> None: def change_simulation_database(self, database: SimulationDatabase) -> None:
if database != self.stored_simulation_database(): if database != self.stored_simulation_database():
self.undo_stack.push(ChangeSimulationDatabaseCommand(self, database)) self.undo_stack.push(ChangeSimulationDatabaseCommand(self, database))

View File

@@ -176,11 +176,14 @@ class Simulation:
class SimulationDatabase: class SimulationDatabase:
format_version: int = 1 format_version: int = 1
simulations: dict[SimulationID, Simulation] = field(default_factory=dict) simulations: dict[SimulationID, Simulation] = field(default_factory=dict)
active_simulation: SimulationID | None = None
@classmethod @classmethod
def from_data(cls, data: Mapping[str, Any]) -> SimulationDatabase: def from_data(cls, data: Mapping[str, Any]) -> SimulationDatabase:
sims = {SimulationID(key): Simulation.from_data(value) for key, value in data.get("simulations", {}).items()} sims = {SimulationID(key): Simulation.from_data(value) for key, value in data.get("simulations", {}).items()}
return cls(format_version=int(data.get("format_version", 1)), simulations=sims) active = data.get("active_simulation")
active_id = SimulationID(str(active)) if active is not None else None
return cls(format_version=int(data.get("format_version", 1)), simulations=sims, active_simulation=active_id if active_id in sims else None)
def to_data(self) -> dict[str, Any]: def to_data(self) -> dict[str, Any]:
return {"format_version": self.format_version, "simulations": {str(key): sim.to_data() for key, sim in self.simulations.items()}} return {"format_version": self.format_version, "active_simulation": str(self.active_simulation) if self.active_simulation is not None else None, "simulations": {str(key): sim.to_data() for key, sim in self.simulations.items()}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,5 +1,7 @@
<RCC> <RCC>
<qresource prefix="icons"> <qresource prefix="icons">
<file>icons/media-skip-backward.png</file>
<file>icons/media-playback-stop.png</file>
<file>icons/edit-delete.png</file> <file>icons/edit-delete.png</file>
<file>icons/dialog-close.png</file> <file>icons/dialog-close.png</file>
<file>icons/list-remove.png</file> <file>icons/list-remove.png</file>

View File

@@ -25,3 +25,24 @@ class ApplicationSettings:
@log_level.setter @log_level.setter
def log_level(self, level: int) -> None: def log_level(self, level: int) -> None:
self._settings.setValue(self.LOG_LEVEL_KEY, level) self._settings.setValue(self.LOG_LEVEL_KEY, level)
class SimulationApplicationSettings:
"""Typed access to persistent BEsim application settings."""
LOG_LEVEL_KEY = "logging/level"
DEFAULT_LOG_LEVEL = logging.INFO
def __init__(self, settings: QSettings | None = None) -> None:
self._settings = settings if settings is not None else QSettings()
@property
def log_level(self) -> int:
return self._settings.value(
self.LOG_LEVEL_KEY,
self.DEFAULT_LOG_LEVEL,
type=int,
)
@log_level.setter
def log_level(self, level: int) -> None:
self._settings.setValue(self.LOG_LEVEL_KEY, level)

View File

@@ -0,0 +1,79 @@
from __future__ import annotations
import json
import zlib
from collections.abc import Mapping
from pathlib import Path
from typing import Any
import msgpack
from bedit_gui.simulation_models import SimulationRoot
BES_MAGIC = b"BES\x00"
FILE_FORMAT_VERSION = 1
_VERSION_SIZE = 4
def load(path: str | Path) -> SimulationRoot:
file_path = Path(path)
data = _load_json(file_path) if file_path.suffix.lower() == ".json" else _load_bes(file_path)
return SimulationRoot.from_data(data)
def save(root: SimulationRoot, path: str | Path) -> None:
file_path = Path(path)
if file_path.suffix.lower() == ".json":
_save_json(root.to_data(), file_path)
elif file_path.suffix.lower() == ".bes":
_save_bes(root.to_data(), file_path)
else:
raise ValueError(f"unsupported simulation file extension {file_path.suffix!r}; expected '.json' or '.bes'")
def is_simulation_json(path: str | Path) -> bool:
file_path = Path(path)
if file_path.suffix.lower() != ".json":
return False
try:
return _load_json(file_path).get("root_type") == "simulation_root"
except (TypeError, ValueError):
return False
def _load_json(path: Path) -> Mapping[str, Any]:
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
raise ValueError(f"could not read simulation JSON {path}: {exc}") from exc
if not isinstance(data, Mapping):
raise TypeError(f"simulation JSON {path} must contain an object")
return data
def _save_json(data: Mapping[str, Any], path: Path) -> None:
path.write_text(json.dumps({"file_format_version": FILE_FORMAT_VERSION, **data}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def _load_bes(path: Path) -> Mapping[str, Any]:
try:
payload = path.read_bytes()
header_end = len(BES_MAGIC) + _VERSION_SIZE
if not payload.startswith(BES_MAGIC):
raise ValueError("missing BES file header")
if len(payload) < header_end:
raise ValueError("truncated BES file header")
version = int.from_bytes(payload[len(BES_MAGIC):header_end], "big")
if version != FILE_FORMAT_VERSION:
raise ValueError(f"unsupported BES file version {version}")
data = msgpack.unpackb(zlib.decompress(payload[header_end:]), raw=False, strict_map_key=False)
except (OSError, ValueError, zlib.error, msgpack.exceptions.UnpackException) as exc:
raise ValueError(f"could not read BES simulation {path}: {exc}") from exc
if not isinstance(data, Mapping):
raise TypeError(f"BES simulation {path} must contain a map")
return data
def _save_bes(data: Mapping[str, Any], path: Path) -> None:
encoded = zlib.compress(msgpack.packb(dict(data), use_bin_type=True))
path.write_bytes(BES_MAGIC + FILE_FORMAT_VERSION.to_bytes(_VERSION_SIZE, "big") + encoded)

View File

@@ -0,0 +1,95 @@
from __future__ import annotations
import tempfile
from pathlib import Path
from bedit_core.models import Component, ComponentID, Document, GraphImplementation
from bedit_gui.models import Simulation, SimulationMethod
from bedit_gui.services import document_files
from bedit_gui.simulation_models import CompiledModel, SimulationRoot
from bedit_simulation import compile_component_sync
def component_choices(document: Document) -> list[tuple[ComponentID, Component, str]]:
choices: list[tuple[ComponentID, Component, str]] = []
def collect(items: dict[ComponentID, Component], path: tuple[str, ...] = ()) -> None:
for component_id, component in items.items():
component_path = (*path, component.name)
choices.append((component_id, component, ".".join(component_path)))
if isinstance(component.implementation, GraphImplementation):
collect(component.implementation.graph.components, component_path)
collect(document.root)
return choices
def load_and_compile_bedit(path: str | Path, *, component_selector: str | None = None, simulation_selector: str | None = None, working_directory: str | Path | None = None, omc_command: str = "omc") -> tuple[SimulationRoot, CompiledModel]:
"""Open a BEdit document, resolve one launch target, and compile it."""
if bool(component_selector) == bool(simulation_selector):
raise ValueError("specify exactly one component or simulation settings block")
source_path = Path(path).resolve()
document = document_files.load(source_path)
choices = component_choices(document)
settings_name: str | None = None
if component_selector is not None:
component_id, component, component_path = _find_component(choices, component_selector)
settings = _default_settings(component_id)
else:
database = document.metadata.get("simulation_database") if document.metadata is not None else None
if database is None or not hasattr(database, "simulations"):
raise ValueError("the BEdit document does not contain simulation settings")
matches = [(simulation_id, simulation) for simulation_id, simulation in database.simulations.items() if simulation.name == simulation_selector or str(simulation_id) == simulation_selector]
if len(matches) != 1:
raise ValueError(f"simulation settings {simulation_selector!r} were not found or are ambiguous")
_simulation_id, settings = matches[0]
component_id, component, component_path = _find_component(choices, str(settings.component))
settings_name = settings.name
build_directory = Path(working_directory) if working_directory is not None else Path(tempfile.mkdtemp(prefix="bedit-compiled-"))
root = SimulationRoot(
format_version=1,
source_document=str(source_path),
source_document_id=str(document.id),
component=component_id,
component_path=component_path,
settings_name=settings_name,
settings=settings,
)
return root, _compile(component, build_directory, omc_command)
def compile_simulation_root(root: SimulationRoot, *, working_directory: str | Path | None = None, omc_command: str = "omc") -> CompiledModel:
if root.source_document is None:
raise ValueError("the simulation does not reference a BEdit source document and cannot be recompiled")
document = document_files.load(root.source_document)
_component_id, component, _component_path = _find_component(component_choices(document), str(root.component))
build_directory = Path(working_directory) if working_directory is not None else Path(tempfile.mkdtemp(prefix="bedit-compiled-"))
return _compile(component, build_directory, omc_command)
def _find_component(choices: list[tuple[ComponentID, Component, str]], selector: str) -> tuple[ComponentID, Component, str]:
matches = [choice for choice in choices if str(choice[0]) == selector or choice[2] == selector]
if len(matches) != 1:
raise ValueError(f"component {selector!r} was not found or is ambiguous")
return matches[0]
def _default_settings(component_id: ComponentID) -> Simulation:
return Simulation(
component=component_id,
name="Default",
start_time=0.0,
duration=1.0,
use_timed_steps=False,
number_of_steps=500,
step_size=0.002,
method=SimulationMethod.DASSL,
dassl_tolerance=1e-6,
)
def _compile(component: Component, working_directory: Path, omc_command: str) -> CompiledModel:
build = compile_component_sync(component, working_directory, omc_command=omc_command)
return CompiledModel(build.model_name, str(build.executable.resolve()), str(build.executable.parent.resolve()), build.output, build.errors)

View File

@@ -0,0 +1,58 @@
from __future__ import annotations
import argparse
import sys
from PySide6.QtWidgets import QApplication, QMessageBox
from bedit_gui.controllers.simulation_file_controller import SimulationFileController
from bedit_gui.controllers.log_controller import LogController
from bedit_gui.simulation_models import CompiledModel
from bedit_gui.services.application_settings import SimulationApplicationSettings
from bedit_gui.views.simulation_window import SimulationWindow
def parse_arguments(arguments: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Open and run BEdit simulations")
parser.add_argument("file", nargs="?", help="BEdit (.beb/.json) or simulation (.bes/.json) file")
parser.add_argument("-f", "--file", dest="file_option", help=argparse.SUPPRESS)
parser.add_argument("--handoff", action="store_true", help=argparse.SUPPRESS)
parser.add_argument("--model-name", help=argparse.SUPPRESS)
parser.add_argument("--executable", help=argparse.SUPPRESS)
parser.add_argument("--working-directory", help=argparse.SUPPRESS)
target = parser.add_mutually_exclusive_group()
target.add_argument("-c", "--component", help="Component ID or dotted path in a BEdit file")
target.add_argument("-s", "--simulation", help="Simulation settings name or ID in a BEdit file")
args = parser.parse_args(arguments)
args.file = args.file_option or args.file
if args.handoff and not all((args.model_name, args.executable, args.working_directory)):
parser.error("a simulator handoff requires compiled-model arguments")
return args
def main(arguments: list[str] | None = None) -> int:
args = parse_arguments(arguments)
app = QApplication(sys.argv if arguments is None else [sys.argv[0], *arguments])
app.setOrganizationName("BEsim")
app.setApplicationName("BEsim")
window = SimulationWindow()
controller = SimulationFileController(window)
settings = SimulationApplicationSettings()
LogController(window, settings.log_level)
window.showMaximized()
if args.file:
try:
compiled_model = CompiledModel(args.model_name, args.executable, args.working_directory) if args.handoff else None
controller.open(args.file, component=args.component, simulation=args.simulation, backed_by_file=not args.handoff, compiled_model=compiled_model)
except (OSError, ValueError, RuntimeError) as exc:
QMessageBox.critical(window, "Could not open simulation", str(exc))
return app.exec()
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,61 @@
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any
from bedit_core.models import ComponentID
from bedit_gui.models import Simulation
@dataclass
class CompiledModel:
model_name: str
executable: str
working_directory: str
output: str = ""
errors: str = ""
@classmethod
def from_data(cls, data: Mapping[str, Any]) -> CompiledModel:
return cls(model_name=str(data["model_name"]), executable=str(data["executable"]), working_directory=str(data["working_directory"]), output=str(data.get("output", "")), errors=str(data.get("errors", "")))
def to_data(self) -> dict[str, Any]:
return {"model_name": self.model_name, "executable": self.executable, "working_directory": self.working_directory, "output": self.output, "errors": self.errors}
@dataclass
class SimulationRoot:
format_version: int
source_document: str | None
source_document_id: str | None
component: ComponentID
component_path: str
settings_name: str | None
settings: Simulation
@classmethod
def from_data(cls, data: Mapping[str, Any]) -> SimulationRoot:
if data.get("root_type") != "simulation_root":
raise ValueError("file does not contain a simulation root")
return cls(
format_version=int(data.get("format_version", 1)),
source_document=str(data["source_document"]) if data.get("source_document") is not None else None,
source_document_id=str(data["source_document_id"]) if data.get("source_document_id") is not None else None,
component=ComponentID(str(data["component"])),
component_path=str(data.get("component_path", "")),
settings_name=str(data["settings_name"]) if data.get("settings_name") is not None else None,
settings=Simulation.from_data(data["settings"]),
)
def to_data(self) -> dict[str, Any]:
return {
"root_type": "simulation_root",
"format_version": self.format_version,
"source_document": self.source_document,
"source_document_id": self.source_document_id,
"component": str(self.component),
"component_path": self.component_path,
"settings_name": self.settings_name,
"settings": self.settings.to_data(),
}

View File

@@ -72,6 +72,7 @@
<property name="title"> <property name="title">
<string>Help</string> <string>Help</string>
</property> </property>
<addaction name="actionAbout"/>
<addaction name="actionAbout_QT"/> <addaction name="actionAbout_QT"/>
</widget> </widget>
<widget class="QMenu" name="menuSimulation"> <widget class="QMenu" name="menuSimulation">
@@ -460,6 +461,11 @@
<enum>QAction::MenuRole::NoRole</enum> <enum>QAction::MenuRole::NoRole</enum>
</property> </property>
</action> </action>
<action name="actionAbout">
<property name="text">
<string>About</string>
</property>
</action>
</widget> </widget>
<resources> <resources>
<include location="../../resources/resources.qrc"/> <include location="../../resources/resources.qrc"/>

View File

@@ -0,0 +1,309 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SimulationWindow</class>
<widget class="QMainWindow" name="SimulationWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTabWidget" name="resultsTabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Tab 1</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionNew_Simulation_Run"/>
<addaction name="actionOpen_Simulation_Run"/>
<addaction name="actionSave_Simulation_Run"/>
</widget>
<widget class="QMenu" name="menuSimulation">
<property name="title">
<string>Simulation</string>
</property>
<addaction name="actionRestart_Simulation"/>
<addaction name="actionRun_Simulation"/>
<addaction name="actionStop_Simulation"/>
<addaction name="separator"/>
<addaction name="actionSimulation_Options"/>
</widget>
<widget class="QMenu" name="menuEdit">
<property name="title">
<string>Edit</string>
</property>
<addaction name="actionUndo"/>
<addaction name="actionRedo"/>
<addaction name="separator"/>
<addaction name="actionSettings"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>Tools</string>
</property>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
<addaction name="actionAbout_QT"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuSimulation"/>
<addaction name="menuTools"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="fileToolbar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionNew_Simulation_Run"/>
<addaction name="actionOpen_Simulation_Run"/>
<addaction name="actionSave_Simulation_Run"/>
</widget>
<widget class="QToolBar" name="actionToolbar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionUndo"/>
<addaction name="actionRedo"/>
</widget>
<widget class="QToolBar" name="simToolbar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionRestart_Simulation"/>
<addaction name="actionRun_Simulation"/>
<addaction name="actionStop_Simulation"/>
<addaction name="actionSimulation_Options"/>
</widget>
<widget class="QDockWidget" name="logDockWidget">
<property name="windowTitle">
<string>Log</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListView" name="listView"/>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="simulationTreeDock">
<property name="windowTitle">
<string>Signals</string>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTreeWidget" name="simulationTree">
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<action name="actionRun_Simulation">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/media-playback-start.png</normaloff>:/icons/icons/media-playback-start.png</iconset>
</property>
<property name="text">
<string>Run Simulation</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionStop_Simulation">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/media-playback-stop.png</normaloff>:/icons/icons/media-playback-stop.png</iconset>
</property>
<property name="text">
<string>Stop Simulation</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionRestart_Simulation">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/media-skip-backward.png</normaloff>:/icons/icons/media-skip-backward.png</iconset>
</property>
<property name="text">
<string>Restart Simulation</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionSimulation_Options">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/configure.png</normaloff>:/icons/icons/configure.png</iconset>
</property>
<property name="text">
<string>Simulation Options</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionNew_Simulation_Run">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/document-new.png</normaloff>:/icons/icons/document-new.png</iconset>
</property>
<property name="text">
<string>New Simulation Run</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionOpen_Simulation_Run">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/document-open.png</normaloff>:/icons/icons/document-open.png</iconset>
</property>
<property name="text">
<string>Open Simulation Run</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionSave_Simulation_Run">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/document-save.png</normaloff>:/icons/icons/document-save.png</iconset>
</property>
<property name="text">
<string>Save Simulation Run</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionUndo">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/edit-undo.png</normaloff>:/icons/icons/edit-undo.png</iconset>
</property>
<property name="text">
<string>Undo</string>
</property>
<property name="shortcut">
<string>Ctrl+Z</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionRedo">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/edit-redo.png</normaloff>:/icons/icons/edit-redo.png</iconset>
</property>
<property name="text">
<string>Redo</string>
</property>
<property name="shortcut">
<string>Ctrl+Y</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionSettings">
<property name="icon">
<iconset resource="../../resources/resources.qrc">
<normaloff>:/icons/icons/preferences-system.png</normaloff>:/icons/icons/preferences-system.png</iconset>
</property>
<property name="text">
<string>Settings</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About</string>
</property>
</action>
<action name="actionAbout_QT">
<property name="text">
<string>About QT</string>
</property>
</action>
</widget>
<resources>
<include location="../../resources/resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -4,7 +4,7 @@ from copy import deepcopy
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
from PySide6.QtGui import QDoubleValidator from PySide6.QtGui import QDoubleValidator
from PySide6.QtWidgets import QDialog, QListWidgetItem, QMessageBox, QWidget from PySide6.QtWidgets import QDialog, QLayout, QListWidgetItem, QMessageBox, QWidget
from bedit_core.models import ComponentID from bedit_core.models import ComponentID
from bedit_gui.models import Simulation, SimulationDatabase, SimulationID, SimulationMethod from bedit_gui.models import Simulation, SimulationDatabase, SimulationID, SimulationMethod
@@ -14,7 +14,7 @@ from bedit_gui.ui.generated.ui_simulation_settings import Ui_Dialog
class SimulationSettingsDialog(QDialog): class SimulationSettingsDialog(QDialog):
"""Editor for the detached simulation database of a document.""" """Editor for the detached simulation database of a document."""
def __init__(self, database: SimulationDatabase, components: list[tuple[ComponentID, str]], parent: QWidget | None = None) -> None: def __init__(self, database: SimulationDatabase, components: list[tuple[ComponentID, str]], parent: QWidget | None = None, *, show_simulation_list: bool = True) -> None:
super().__init__(parent) super().__init__(parent)
self.ui = Ui_Dialog() self.ui = Ui_Dialog()
@@ -25,6 +25,9 @@ class SimulationSettingsDialog(QDialog):
self._components = components self._components = components
self._loading = False self._loading = False
if not show_simulation_list:
self._set_layout_visible(self.ui.simulationListLayout, False)
self.ui.startTimeSpinBox.setRange(-1e12, 1e12) self.ui.startTimeSpinBox.setRange(-1e12, 1e12)
self.ui.simLengthSpinBox.setRange(0.0, 1e12) self.ui.simLengthSpinBox.setRange(0.0, 1e12)
self.ui.stepSizeSpinBox.setRange(1e-9, 1e12) self.ui.stepSizeSpinBox.setRange(1e-9, 1e12)
@@ -51,7 +54,7 @@ class SimulationSettingsDialog(QDialog):
self.ui.simulationMethodComboBox.currentIndexChanged.connect(self._form_changed) self.ui.simulationMethodComboBox.currentIndexChanged.connect(self._form_changed)
self.ui.toleranceEdit.textEdited.connect(self._form_changed) self.ui.toleranceEdit.textEdited.connect(self._form_changed)
self._rebuild_list() self._rebuild_list(self._database.active_simulation)
def database(self) -> SimulationDatabase: def database(self) -> SimulationDatabase:
return deepcopy(self._database) return deepcopy(self._database)
@@ -68,6 +71,7 @@ class SimulationSettingsDialog(QDialog):
QMessageBox.warning(self, "Invalid simulation", "The DASSL tolerance must be greater than zero.") QMessageBox.warning(self, "Invalid simulation", "The DASSL tolerance must be greater than zero.")
return return
simulation.name = simulation.name.strip() simulation.name = simulation.name.strip()
self._database.active_simulation = self._simulation_id()
super().accept() super().accept()
def _rebuild_list(self, selected_id: SimulationID | None = None) -> None: def _rebuild_list(self, selected_id: SimulationID | None = None) -> None:
@@ -172,6 +176,8 @@ class SimulationSettingsDialog(QDialog):
row = self._simulation_ids.index(simulation_id) row = self._simulation_ids.index(simulation_id)
del self._database.simulations[simulation_id] del self._database.simulations[simulation_id]
self._simulation_ids.remove(simulation_id) self._simulation_ids.remove(simulation_id)
if self._database.active_simulation == simulation_id:
self._database.active_simulation = None
selected = self._simulation_ids[min(row, len(self._simulation_ids) - 1)] if self._simulation_ids else None selected = self._simulation_ids[min(row, len(self._simulation_ids) - 1)] if self._simulation_ids else None
self._rebuild_list(selected) self._rebuild_list(selected)
@@ -193,3 +199,14 @@ class SimulationSettingsDialog(QDialog):
self.ui.frame.setEnabled(enabled) self.ui.frame.setEnabled(enabled)
self.ui.removeSimulationButton.setEnabled(enabled) self.ui.removeSimulationButton.setEnabled(enabled)
self.ui.addSimulationButton.setEnabled(bool(self._components)) self.ui.addSimulationButton.setEnabled(bool(self._components))
@classmethod
def _set_layout_visible(cls, layout: QLayout, visible: bool) -> None:
for index in range(layout.count()):
item = layout.itemAt(index)
widget = item.widget()
child_layout = item.layout()
if widget is not None:
widget.setVisible(visible)
elif child_layout is not None:
cls._set_layout_visible(child_layout, visible)

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from PySide6.QtWidgets import QMainWindow
from bedit_gui.ui.generated.ui_simulation_window import Ui_SimulationWindow
class SimulationWindow(QMainWindow):
def __init__(self) -> None:
super().__init__()
self.ui = Ui_SimulationWindow()
self.ui.setupUi(self)
self.setWindowTitle("BEsim")

View File

@@ -6,6 +6,7 @@ from .openmodelica import (
ProcessResult, ProcessResult,
) )
from .results import SimulationResult, load_openmodelica_csv from .results import SimulationResult, load_openmodelica_csv
from .compile import compile_component, compile_component_sync
from .simulation import ( from .simulation import (
ModelBuildResult, ModelBuildResult,
ModelCheckResult, ModelCheckResult,
@@ -18,17 +19,19 @@ from .simulation import (
) )
__all__ = [ __all__ = [
"OpenModelicaError",
"OpenModelicaRunner",
"ProcessResult",
"ModelBuildResult", "ModelBuildResult",
"ModelCheckResult", "ModelCheckResult",
"ModelInfo", "ModelInfo",
"OpenModelicaError",
"OpenModelicaRunner",
"ProcessResult",
"Simulation", "Simulation",
"SimulationOptions", "SimulationOptions",
"SimulationProgress", "SimulationProgress",
"SimulationStateError",
"SimulationResult", "SimulationResult",
"SimulationStateError",
"compile_component",
"compile_component_sync",
"load_openmodelica_csv", "load_openmodelica_csv",
"simulate", "simulate",
] ]

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
import asyncio
from pathlib import Path
from bedit_core.models import Component
from .openmodelica import OpenModelicaRunner
from .simulation import ModelBuildResult, Simulation
async def compile_component(component: Component, working_directory: str | Path, *, omc_command: str = "omc", timeout: float | None = None) -> ModelBuildResult:
"""Compose and compile a component without creating a GUI application."""
simulation = Simulation(OpenModelicaRunner(omc_command, timeout=timeout))
await simulation.load(component)
return await simulation.compile(working_directory)
def compile_component_sync(component: Component, working_directory: str | Path, *, omc_command: str = "omc", timeout: float | None = None) -> ModelBuildResult:
"""Synchronous entry point for scripts and non-async suite applications."""
return asyncio.run(compile_component(component, working_directory, omc_command=omc_command, timeout=timeout))

View File

@@ -591,6 +591,7 @@
}, },
"simulation_database": { "simulation_database": {
"format_version": 1, "format_version": 1,
"active_simulation": "7778ca68-7a36-401b-b4aa-236a44c5e771",
"simulations": { "simulations": {
"7778ca68-7a36-401b-b4aa-236a44c5e771": { "7778ca68-7a36-401b-b4aa-236a44c5e771": {
"component": "50e6ef97-f686-4400-bc01-e5a352e8cc22", "component": "50e6ef97-f686-4400-bc01-e5a352e8cc22",

21
untitled.besim.json Normal file
View File

@@ -0,0 +1,21 @@
{
"file_format_version": 1,
"root_type": "simulation_root",
"format_version": 1,
"source_document": "/home/joppe/Projects/BEdit/untitled.bedit.json",
"source_document_id": "3b6780c7-488b-471e-a784-392db7632090",
"component": "50e6ef97-f686-4400-bc01-e5a352e8cc22",
"component_path": "some_bondgraph",
"settings_name": "run bondgraph",
"settings": {
"component": "50e6ef97-f686-4400-bc01-e5a352e8cc22",
"name": "run bondgraph",
"start_time": 0.0,
"duration": 10.0,
"use_timed_steps": true,
"number_of_steps": 500,
"step_size": 0.001,
"method": "dassl",
"dassl_tolerance": 1e-06
}
}