from __future__ import annotations from collections.abc import Mapping from dataclasses import dataclass, field from typing import Any from bedit_core.models import ComponentID from bedit_gui.models import Simulation from bedit_simulation import SimulationResult @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 SimulationTraceSettings: visible: bool = True label: str = "" color: str = "" line_style: str = "-" line_width: float = 1.5 marker: str = "" marker_size: float = 6.0 @classmethod def from_data(cls, data: Mapping[str, Any]) -> SimulationTraceSettings: return cls(visible=bool(data.get("visible", True)), label=str(data.get("label", "")), color=str(data.get("color", "")), line_style=str(data.get("line_style", "-")), line_width=float(data.get("line_width", 1.5)), marker=str(data.get("marker", "")), marker_size=float(data.get("marker_size", 6.0))) def to_data(self) -> dict[str, Any]: return {"visible": self.visible, "label": self.label, "color": self.color, "line_style": self.line_style, "line_width": self.line_width, "marker": self.marker, "marker_size": self.marker_size} @dataclass class SimulationPlotSettings: title: str = "" x_label: str = "" y_label: str = "" x_scale: str = "linear" y_scale: str = "linear" x_auto: bool = True y_auto: bool = True x_min: float = 0.0 x_max: float = 1.0 y_min: float = 0.0 y_max: float = 1.0 grid_visible: bool = True grid_axis: str = "both" grid_style: str = "-" grid_alpha: float = 0.5 legend_visible: bool = True legend_location: str = "best" traces: dict[str, SimulationTraceSettings] = field(default_factory=dict) @classmethod def from_data(cls, data: Mapping[str, Any]) -> SimulationPlotSettings: raw_traces = data.get("traces", {}) if not isinstance(raw_traces, Mapping): raise TypeError("plot trace settings must be a mapping") return cls( title=str(data.get("title", "")), x_label=str(data.get("x_label", "")), y_label=str(data.get("y_label", "")), x_scale=str(data.get("x_scale", "linear")), y_scale=str(data.get("y_scale", "linear")), x_auto=bool(data.get("x_auto", True)), y_auto=bool(data.get("y_auto", True)), x_min=float(data.get("x_min", 0.0)), x_max=float(data.get("x_max", 1.0)), y_min=float(data.get("y_min", 0.0)), y_max=float(data.get("y_max", 1.0)), grid_visible=bool(data.get("grid_visible", True)), grid_axis=str(data.get("grid_axis", "both")), grid_style=str(data.get("grid_style", "-")), grid_alpha=float(data.get("grid_alpha", 0.5)), legend_visible=bool(data.get("legend_visible", True)), legend_location=str(data.get("legend_location", "best")), traces={str(signal): SimulationTraceSettings.from_data(trace) for signal, trace in raw_traces.items() if isinstance(trace, Mapping)}, ) def to_data(self) -> dict[str, Any]: return { "title": self.title, "x_label": self.x_label, "y_label": self.y_label, "x_scale": self.x_scale, "y_scale": self.y_scale, "x_auto": self.x_auto, "y_auto": self.y_auto, "x_min": self.x_min, "x_max": self.x_max, "y_min": self.y_min, "y_max": self.y_max, "grid_visible": self.grid_visible, "grid_axis": self.grid_axis, "grid_style": self.grid_style, "grid_alpha": self.grid_alpha, "legend_visible": self.legend_visible, "legend_location": self.legend_location, "traces": {signal: trace.to_data() for signal, trace in self.traces.items()}, } @dataclass class SimulationPlotTab: name: str signals: list[str] = field(default_factory=list) x_axis: str | None = None settings: SimulationPlotSettings = field(default_factory=SimulationPlotSettings) @classmethod def from_data(cls, data: Mapping[str, Any]) -> SimulationPlotTab: raw_signals = data.get("signals", []) if not isinstance(raw_signals, list): raise TypeError("plot tab signals must be a list") raw_settings = data.get("settings", {}) if not isinstance(raw_settings, Mapping): raise TypeError("plot tab settings must be a mapping") return cls(name=str(data.get("name", "Plot")), signals=[str(signal) for signal in raw_signals], x_axis=str(data["x_axis"]) if data.get("x_axis") is not None else None, settings=SimulationPlotSettings.from_data(raw_settings)) def to_data(self) -> dict[str, Any]: return {"name": self.name, "signals": self.signals, "x_axis": self.x_axis, "settings": self.settings.to_data()} @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 current_end_time: float | None = None results: list[SimulationResult] = field(default_factory=list) plot_tabs: list[SimulationPlotTab] = field(default_factory=lambda: [SimulationPlotTab("Plot 1")]) @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"]), current_end_time=float(data["current_end_time"]) if data.get("current_end_time") is not None else None, results=[_result_from_data(result) for result in data.get("results", [])], plot_tabs=[SimulationPlotTab.from_data(tab) for tab in data["plot_tabs"]] if "plot_tabs" in data else [SimulationPlotTab("Plot 1")], ) 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(), "current_end_time": self.current_end_time, "results": [_result_to_data(result) for result in self.results], "plot_tabs": [tab.to_data() for tab in self.plot_tabs], } def _result_from_data(data: Mapping[str, Any]) -> SimulationResult: raw_columns = data.get("data", {}) if not isinstance(raw_columns, Mapping): raise TypeError("simulation result data must be a mapping") columns = {str(name): [float(value) for value in values] for name, values in raw_columns.items()} return SimulationResult(model_name=str(data.get("model_name", "")), data=columns, process_output=str(data.get("process_output", "")), process_errors=str(data.get("process_errors", ""))) def _result_to_data(result: SimulationResult) -> dict[str, Any]: return {"model_name": result.model_name, "data": result.data, "process_output": result.process_output, "process_errors": result.process_errors}