SimUI
Python-declared web interface for local simulations.
Overview
SimUI lets you create interactive web interfaces for your simulations without writing any JavaScript:
- No npm required - Bundled React frontend
- Declarative API - Define controls in Python
- Real-time updates - SSE-based event streaming
- Visualization - Automatic rendering of module visuals
Quick Start
from bsim import BioWorld, FixedStepSolver
from bsim.simui import Interface, Number, Button, EventLog, VisualsPanel
# Create your simulation world
world = BioWorld(solver=FixedStepSolver())
# ... add modules and wiring ...
# Create interface
ui = Interface(
world,
controls=[
Number("steps", 1000, minimum=100, maximum=10000, step=100),
Number("dt", 0.01, minimum=0.001, maximum=0.1, step=0.001),
Button("Run")
],
outputs=[
EventLog(),
VisualsPanel()
]
)
# Launch in browser
ui.launch(port=8080)Access at http://localhost:8080/ui/.
Controls
Number
Numeric input with validation:
Number(
name="steps", # Parameter name
default=1000, # Default value
minimum=100, # Minimum allowed
maximum=10000, # Maximum allowed
step=100, # Increment step
label="Simulation Steps" # Display label
)Button
Trigger actions:
Button(
label="Run Simulation"
)Outputs
EventLog
Display simulation events:
EventLog(
limit=200 # Max events to display
)VisualsPanel
Render module visualizations:
VisualsPanel(
refresh="auto", # "auto" or "manual"
interval_ms=500 # ms between updates
)Interface Configuration
ui = Interface(
world,
controls=[...],
outputs=[...],
title="My Simulation",
description="Interactive neuroscience simulation",
mount_path="/ui", # URL prefix
config_path="ui.json"
)Launching
Local Server
ui.launch(
host="127.0.0.1",
port=7860,
open_browser=True
)Custom Server
Integrate with existing FastAPI app:
from fastapi import FastAPI
app = FastAPI()
ui.mount(app, "/simulation")API Endpoints
SimUI exposes these endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/ui/api/spec | GET | Interface specification |
/ui/api/run | POST | Execute simulation |
/ui/api/status | GET | Current status |
/ui/api/state | GET | World state snapshot |
/ui/api/pause | POST | Pause a run |
/ui/api/resume | POST | Resume a run |
/ui/api/events | GET | Event log (paginated) |
/ui/api/visuals | GET | Module visualizations |
/ui/api/snapshot | GET | Status + events + visuals |
/ui/api/stream | GET | SSE event stream |
/ui/api/reset | POST | Reset status |
Run Simulation
curl -X POST http://localhost:8080/ui/api/run \
-H "Content-Type: application/json" \
-d '{"steps": 1000, "dt": 0.01}'Get Status
curl http://localhost:8080/ui/api/statusResponse:
{
"running": true,
"paused": false,
"started_at": "2024-01-01T00:00:00Z",
"finished_at": null,
"step_count": 450,
"error": null
}Event Stream
curl http://localhost:8080/ui/api/streamSSE format:
data: {"type":"snapshot","data":{"status":{...},"visuals":[...],"events":[...]}}
data: {"type":"tick","data":{"status":{...},"visuals":[...],"event":{...}}}Complete Example
import bsim
from bsim.packs.ecology import (
Environment, OrganismPopulation, PredatorPreyInteraction, PopulationMonitor
)
from bsim.simui import Interface, Number, Button, EventLog, VisualsPanel
world = bsim.BioWorld(solver=bsim.FixedStepSolver())
wb = bsim.WiringBuilder(world)
env = Environment(temperature=20.0)
rabbits = OrganismPopulation(name="Rabbits", initial_count=100, preset="rabbit")
foxes = OrganismPopulation(name="Foxes", initial_count=10, preset="fox")
predation = PredatorPreyInteraction(predation_rate=0.01)
monitor = PopulationMonitor()
wb.add("env", env)
wb.add("rabbits", rabbits)
wb.add("foxes", foxes)
wb.add("predation", predation)
wb.add("monitor", monitor)
wb.connect("env.out.conditions", ["rabbits.in.conditions", "foxes.in.conditions"])
wb.connect("rabbits.out.population_state", ["predation.in.prey_state", "monitor.in.population_state"])
wb.connect("foxes.out.population_state", ["predation.in.predator_state", "monitor.in.population_state"])
wb.connect("predation.out.predation", ["rabbits.in.predation"])
wb.connect("predation.out.food_gained", ["foxes.in.food_gained"])
wb.apply()
ui = Interface(
world,
title="Predator-Prey Simulation",
description="Interactive Lotka-Volterra dynamics",
controls=[
Number("steps", 5000, minimum=1000, maximum=50000, step=1000, label="Steps"),
Number("dt", 0.01, minimum=0.001, maximum=0.1, step=0.001, label="Time Step"),
Button("Run")
],
outputs=[
EventLog(limit=50),
VisualsPanel()
]
)
if __name__ == "__main__":
ui.launch(port=8080, open_browser=True)Best Practices
- Keep Controls Minimal - Only expose essential parameters
- Use Sensible Defaults - Users should be able to click Run immediately
- Validate Ranges - Set min/max to prevent crashes
- Update Frequently - Use SSE for responsive feedback
- Handle Reset - Always implement clean reset behavior