diff --git a/src/py_dvt_ate/app/dashboard/app.py b/src/py_dvt_ate/app/dashboard/app.py index 1630d9d..86f72c4 100644 --- a/src/py_dvt_ate/app/dashboard/app.py +++ b/src/py_dvt_ate/app/dashboard/app.py @@ -4,6 +4,7 @@ This module provides an interactive dashboard for visualising the physics engine directly, demonstrating thermal-electrical coupling in real-time. """ +import time from collections import deque from dataclasses import dataclass, field @@ -15,6 +16,11 @@ from py_dvt_ate.simulation.physics.engine import PhysicsEngine # History buffer size for charts 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 class SimulationHistory: @@ -44,12 +50,29 @@ def init_session_state() -> None: st.session_state.history = SimulationHistory() if "running" not in st.session_state: 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: - """Advance the simulation by the given number of steps.""" +def step_simulation() -> None: + """Advance the simulation based on elapsed real time and multiplier.""" engine: PhysicsEngine = st.session_state.engine 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): engine.step() @@ -177,7 +200,11 @@ def display_current_state() -> None: with col7: st.metric("Power Diss.", f"{electrical.power_dissipation * 1000:.2f} mW") 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: @@ -203,10 +230,25 @@ def display_controls() -> None: st.session_state.engine = PhysicsEngine(update_rate_hz=100.0) st.session_state.history = SimulationHistory() st.session_state.running = False + st.session_state.last_update = time.time() st.rerun() 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 st.sidebar.subheader("Thermal Chamber") temp_setpoint = st.sidebar.slider( @@ -290,7 +332,8 @@ def main() -> None: # Auto-refresh when running if st.session_state.running: - step_simulation(steps=10) + step_simulation() + time.sleep(0.05) # Small delay to prevent UI thrashing st.rerun()