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:

EndpointMethodPurpose
/ui/api/specGETInterface specification
/ui/api/runPOSTExecute simulation
/ui/api/statusGETCurrent status
/ui/api/stateGETWorld state snapshot
/ui/api/pausePOSTPause a run
/ui/api/resumePOSTResume a run
/ui/api/eventsGETEvent log (paginated)
/ui/api/visualsGETModule visualizations
/ui/api/snapshotGETStatus + events + visuals
/ui/api/streamGETSSE event stream
/ui/api/resetPOSTReset 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/status

Response:

{
  "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/stream

SSE 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

  1. Keep Controls Minimal - Only expose essential parameters
  2. Use Sensible Defaults - Users should be able to click Run immediately
  3. Validate Ranges - Set min/max to prevent crashes
  4. Update Frequently - Use SSE for responsive feedback
  5. Handle Reset - Always implement clean reset behavior