Fix dashboard simulation speed with time multiplier

- Add time multiplier control (1× to 100× speed)
- Calculate steps based on real elapsed time
- Add 50ms delay to prevent UI thrashing
- Display current speed in Sim Time metric
This commit is contained in:
2025-04-05 17:58:41 +00:00
parent 1c0d2ead54
commit 75e0a1cc25

View File

@@ -4,6 +4,7 @@ This module provides an interactive dashboard for visualising the physics
engine directly, demonstrating thermal-electrical coupling in real-time. engine directly, demonstrating thermal-electrical coupling in real-time.
""" """
import time
from collections import deque from collections import deque
from dataclasses import dataclass, field from dataclasses import dataclass, field
@@ -15,6 +16,11 @@ from py_dvt_ate.simulation.physics.engine import PhysicsEngine
# History buffer size for charts # History buffer size for charts
HISTORY_SIZE = 500 HISTORY_SIZE = 500
# Simulation speed settings
DEFAULT_TIME_MULTIPLIER = 10.0 # 10x faster than real-time
STEPS_PER_UPDATE = 100 # Steps per UI refresh
UPDATE_INTERVAL_MS = 100 # Target UI refresh rate
@dataclass @dataclass
class SimulationHistory: class SimulationHistory:
@@ -44,12 +50,29 @@ def init_session_state() -> None:
st.session_state.history = SimulationHistory() st.session_state.history = SimulationHistory()
if "running" not in st.session_state: if "running" not in st.session_state:
st.session_state.running = False st.session_state.running = False
if "time_multiplier" not in st.session_state:
st.session_state.time_multiplier = DEFAULT_TIME_MULTIPLIER
if "last_update" not in st.session_state:
st.session_state.last_update = time.time()
def step_simulation(steps: int = 10) -> None: def step_simulation() -> None:
"""Advance the simulation by the given number of steps.""" """Advance the simulation based on elapsed real time and multiplier."""
engine: PhysicsEngine = st.session_state.engine engine: PhysicsEngine = st.session_state.engine
history: SimulationHistory = st.session_state.history history: SimulationHistory = st.session_state.history
multiplier: float = st.session_state.time_multiplier
# Calculate how much simulation time to advance
current_time = time.time()
elapsed_real = current_time - st.session_state.last_update
st.session_state.last_update = current_time
# Simulation time to advance (capped to prevent huge jumps)
sim_time_to_advance = min(elapsed_real * multiplier, 2.0)
# Calculate number of steps needed
steps = int(sim_time_to_advance / engine.dt)
steps = max(1, min(steps, 1000)) # Clamp between 1 and 1000 steps
for _ in range(steps): for _ in range(steps):
engine.step() engine.step()
@@ -177,7 +200,11 @@ def display_current_state() -> None:
with col7: with col7:
st.metric("Power Diss.", f"{electrical.power_dissipation * 1000:.2f} mW") st.metric("Power Diss.", f"{electrical.power_dissipation * 1000:.2f} mW")
with col8: with col8:
st.metric("Sim Time", f"{engine.simulation_time:.2f} s") st.metric(
"Sim Time",
f"{engine.simulation_time:.1f} s",
delta=f"{st.session_state.time_multiplier:.0f}× speed",
)
def display_controls() -> None: def display_controls() -> None:
@@ -203,10 +230,25 @@ def display_controls() -> None:
st.session_state.engine = PhysicsEngine(update_rate_hz=100.0) st.session_state.engine = PhysicsEngine(update_rate_hz=100.0)
st.session_state.history = SimulationHistory() st.session_state.history = SimulationHistory()
st.session_state.running = False st.session_state.running = False
st.session_state.last_update = time.time()
st.rerun() st.rerun()
st.sidebar.divider() st.sidebar.divider()
# Time multiplier
st.sidebar.subheader("Simulation Speed")
time_mult = st.sidebar.select_slider(
"Time Multiplier",
options=[1, 2, 5, 10, 20, 50, 100],
value=int(st.session_state.time_multiplier),
format_func=lambda x: f"{x}×",
key="time_mult_slider",
)
st.session_state.time_multiplier = float(time_mult)
st.sidebar.caption(f"1 real second = {time_mult} simulation seconds")
st.sidebar.divider()
# Temperature setpoint # Temperature setpoint
st.sidebar.subheader("Thermal Chamber") st.sidebar.subheader("Thermal Chamber")
temp_setpoint = st.sidebar.slider( temp_setpoint = st.sidebar.slider(
@@ -290,7 +332,8 @@ def main() -> None:
# Auto-refresh when running # Auto-refresh when running
if st.session_state.running: if st.session_state.running:
step_simulation(steps=10) step_simulation()
time.sleep(0.05) # Small delay to prevent UI thrashing
st.rerun() st.rerun()