We built a Jupyter Book plugin that embeds dynamical systems visualizations as pure Python code via PyScript. That is, we can visualize differential systems written in Python in the browser. Which is pretty cool.
1Leaky Integrator Example¶
A simple leaky integrator system. The state v decays exponentially towards zero with time constant τ, driven by input x.
The system is defined by:
Dynamics:
dv/dt = (-v + x) / τOutput:
x_new = v(output equals state)
2How It Works¶
The simulator:
Implements
step(x, state, p)that computes one timestepxis the input (controlled by the Input slider)stateandpare plain Python dicts for simplicityReturns
(x_new, state_new)with updated state
Try adjusting the sliders:
Input (x): Drive signal to the system - move this to see the system respond in real-time!
τ (tau): Time constant - controls how quickly the state converges to the input
3Usage¶
To create your own dynamical system, use the dynsim directive with Python code:
```{dynsim}
:params: [{"id": "param1", "label": "Label", "min": 0, "max": 1, "step": 0.1, "value": 0.5}]
:plotType: timeseries
:plotConfig: {"title": "My System"}
:initialState: {"your_var": 0}
:initialX: 0
import numpy as np
def step(x, state, p):
# x: input from slider
# state: your state variables (dict)
# p: parameters including 'dt' (dict)
# Your dynamics here (can use numpy functions)
new_var = state['your_var'] + p['dt'] * (x - state['your_var'])
x_new = new_var
return (x_new, {'your_var': new_var})
```4Why return a tuple?¶
The step function returns (x_new, state_new) — a tuple of two things:
x_new— the output value that gets plotted.state_new— a dict with the updated state, fed back into the next call asstate.
This separation exists because the plotted output and the internal state
are not always the same thing. A neuron model, for example, might track
a membrane voltage v and a recovery variable u internally, but only
plot the voltage. Returning a tuple lets you choose what to display
independently from what to remember.
Note that the naming in the Tuple doesn’t matter, so you can call
your variables whatever you want, for instance (y, s).
5Example: from equation to code¶
Say you want to simulate exponential decay:
To turn this into a step function:
Read the current state from the
statedict (state['v']).Read any parameters from
p(p['tau'],p['dt']).Compute the next value using the equation.
Return
(output, new_state).
def step(x, state, p):
v_new = state['v'] * (1 - p['dt'] / p['tau'])
return (v_new, {'v': v_new})Here x (the input slider) is unused — the system just decays on its
own. If you wanted the input to drive the system, you could add it:
def step(x, state, p):
v_new = state['v'] * (1 - p['dt'] / p['tau']) + x * p['dt']
return (v_new, {'v': v_new})6Directive Options¶
:params:- JSON array of parameter definitions with id, label, min, max, step, value:plotType:- Type of plot:timeseries(default),2d, or3d:plotConfig:- JSON object with Plotly configuration (title, axis labels, ranges):initialState:- JSON object with initial state values:initialX:- Initial input/output value (default: 0):height:- Plot height in pixels (default: 400):dt:- Integration timestep (default: 0.02)
7How to run it locally¶
We had to hack the Jupyter book code, so to get this running you need to inject our hack and then re-start the server. Here is a sequence of commands you can run to make it work:
rm -rf _build/site- clears the build cachejupyter book build- pulls the latest Jupyter book templates, including the code we’ll overwritecp _static/js/server.js _build/templates/site/myst/book-theme/server.js- copies our own hacky script into the Jupyter book systemjupyter book start- builds the book and starts a webserver you can access with your browser