Deployment Scenarios¶
The phase plane widget supports three deployment modes, each optimized for a different use case.
1. Jupyter Notebook / JupyterLab¶
The native environment. All interactivity is client-side after the initial cell execution.
from tvb_phaseplane import PhasePlaneWidget
widget = PhasePlaneWidget(model_name="mpr")
widget.params["J"] = 20.0
widget
How It Works¶
- Python creates the widget and syncs initial state via
traitlets anywidgetloads the JS front-end in the notebook- All subsequent interaction (sliders, clicks, sweeps, noise toggles) is handled by JavaScript
- Python can still read back computed data (nullclines, fixed points, trajectories) via synced traitlets
Custom Models in Jupyter¶
from tvb_phaseplane import phase_plane
pp = phase_plane(
equations=["a*x - x**3 - y", "x - b*y"],
state_vars={"x": (-3, 3), "y": (-3, 3)},
params={"a": (0.7, 0, 2), "b": (0.8, 0, 2)},
)
pp
The SymPy expressions are transpiled to JavaScript via an inlined Nerdamer CAS (~100 KB) that compiles and runs entirely in the browser.
Passing a Model Instance¶
You can also instantiate a model class first, configure its defaults, and then pass it to the widget. This is useful when you want to programatically set initial parameter values or read back tuned values after the user interacts with the sliders.
from tvb_phaseplane import PhasePlaneWidget, MPRModel
# 1. Create a model instance
model = MPRModel()
# 2. Optionally override default parameter values
model.default_params.update({"J": 15.0, "eta_bar": -5.0})
# 3. Pass the instance to the widget
widget = PhasePlaneWidget(model=model)
widget
After the user adjusts sliders in the widget, read back the tuned parameter values:
print("Current parameter values:")
for name, value in widget.params.items():
print(f" {name:12s} = {value:.4f}")
You can also read back computed data:
print(f"Fixed points: {len(widget.fixed_points)}")
for fp in widget.fixed_points:
print(f" x={fp[0]:.4f}, y={fp[1]:.4f}, type={fp[2]}")
The model= argument accepts any BaseModel subclass whose name is registered in MODEL_REGISTRY (so the JavaScript front-end knows how to evaluate it). For arbitrary ODE systems that are not built in, use :func:phase_plane instead.
Exporting Notebooks¶
Use jupytext to keep notebooks in plain .py percent-format:
jupytext --to notebook demo.py -o demo.ipynb
jupytext --sync demo.ipynb # keep .py and .ipynb in sync
2. VS Code (Jupyter Extension)¶
VS Code's built-in Jupyter extension supports anywidget out of the box.
from tvb_phaseplane import PhasePlaneWidget
widget = PhasePlaneWidget()
widget # Renders in the VS Code output panel
No extra configuration needed.
3. Standalone HTML (No Kernel)¶
For blogs, documentation, course materials, or any static site where you cannot run a Python kernel:
from tvb_phaseplane import PhasePlaneWidget
widget = PhasePlaneWidget(model_name="mpr")
widget.to_standalone_html("mpr_demo.html", title="MPR Phase Plane")
Custom Models in Standalone HTML¶
from tvb_phaseplane import phase_plane
pp = phase_plane(
equations=["a*x - y", "x - b*y"],
state_vars={"x": (-3, 3), "y": (-3, 3)},
params={"a": (1.0, 0, 2), "b": (1.0, 0, 2)},
)
pp.to_standalone_html("custom_model.html", title="Custom Model")
What You Get¶
A single self-contained .html file (~700 KB) containing:
- All built-in model definitions
- Inlined Nerdamer CAS for custom model compilation
- RK4 and stochastic Heun integrators
- Newton-Raphson fixed-point finder with budget guards
- Nullcline and vector field computation
- HTML5 Canvas rendering engine
- Parameter sweep engine
No external dependencies. No CDN. No Python runtime. Works offline.
Embedding in MkDocs / GitHub Pages¶
The generated HTML can be embedded in an <iframe>:
<iframe src="demos/mpr_bistable.html"
width="100%" height="750"
frameborder="0"
style="border:1px solid #ddd; border-radius:8px;">
</iframe>
This entire documentation site uses this technique — every demo is a live, interactive widget running in your browser.
4. ipywidgets embed_minimal_html (Not Recommended)¶
from ipywidgets.embed import embed_minimal_html
from tvb_phaseplane import PhasePlaneWidget
widget = PhasePlaneWidget()
embed_minimal_html("export.html", views=[widget], title="Phase Plane",
drop_defaults=False)
Requires widget JS runtime
embed_minimal_html produces a file that references the anywidget CDN bundle via RequireJS. This does not work when opened as a local file (file://). Use to_standalone_html() instead for true offline/self-contained deployment.
Comparison¶
| Scenario | Python Kernel | Internet | File Size | Best For |
|---|---|---|---|---|
| Jupyter Notebook | Required at runtime | Optional | ~700 KB JS | Research, exploration |
| VS Code | Required at runtime | Optional | ~700 KB JS | IDE-based workflow |
| Standalone HTML | None | None | ~700 KB | Docs, blogs, courses |
embed_minimal_html |
Required to generate | Required to view | ~2 KB + CDN | Sharing with Jupyter users |
Fixed-Point Detection & Classification¶
The widget automatically locates fixed points (equilibria) by intersecting nullclines and refining with a Newton–Raphson solver. Each detected point is validated with a short trajectory starting from a perturbed initial condition near the equilibrium. The trajectory is not displayed; it serves only as a cross-check of the eigenvalue-based classification.
Classification Method¶
- Nullcline intersection → candidate location
- Newton–Raphson refinement → precise
(x*, y*) - Jacobian eigenvalues at the fixed point → preliminary type
- Dynamic validation (for non-saddles): a 3-second RK4 trajectory is launched from
(x*+0.02, y*+0.02). - Stable / unstable points: the trajectory must converge / diverge respectively. If it doesn't, the eigenvalue classification is recomputed at the refined point.
- Saddles are skipped: a generic perturbation near a saddle always contains a component along the unstable manifold, so forward integration can only diverge — the test is structurally incapable of confirming (or refuting) a saddle. The eigenvalue-based "saddle" label (opposite real-part signs on a 2×2 Jacobian) is already unambiguous.
Visual Legend¶
| Symbol | Type | Meaning |
|---|---|---|
| ● filled circle | Stable node | Both eigenvalues real & negative; trajectories approach monotonically |
| ◉ target ring | Stable focus | Complex-conjugate eigenvalues with negative real part; spirals inward |
| ○ open circle | Unstable node | Both eigenvalues real & positive; trajectories diverge monotonically |
| ⊕ circled cross | Unstable focus | Complex-conjugate eigenvalues with positive real part; spirals outward |
| ◆ diamond | Saddle | Eigenvalues of opposite sign; stable manifold & unstable manifold |
The color key (green for stable, red/orange for unstable, purple for saddle) is preserved alongside the new shapes so that colour-blind users can still distinguish categories.
TikZ / PGFPlots Export¶
For publication-quality figures, the widget can export the current phase plane to a self-contained .tex file:
from tvb_phaseplane import PhasePlaneWidget
widget = PhasePlaneWidget(model_name="wilson_cowan")
# ... interact with the widget ...
widget.export_tikz("phase_plane.tex")
The generated .tex file embeds all data (vector field arrows, nullclines, trajectory, fixed points) as inline coordinates and \draw commands. It requires the pgfplots package and compiles with pdflatex or lualatex:
What Is Exported¶
- Vector field: a grid of
\draw[-stealth, gray]arrows computed and normalized in Python. - Nullclines:
\addplot[blue, ...]and\addplot[red, ...]with embedded coordinate lists. - Trajectory:
\addplot[green!60!black, ...]with embedded coordinate list. - Fixed points: TikZ
\nodemarkers whose shapes encode stability (circle for nodes/foci, diamond for saddles; green for stable, red for unstable, purple for saddles). - Parameter annotation: a small
\nodein the top-left corner listing the current model name and parameter values.
The widget UI also contains an Export TikZ button (beside the Export SVG button) that sends a message to the Python kernel for potential future integration.
References¶
- Scholarpedia, Equilibrium: http://www.scholarpedia.org/article/Equilibrium — in particular the two-dimensional analysis and Figure 3 showing the eigenvalue-based classification diagram.
This site is built automatically by a GitHub Actions workflow that:
- Installs the package
- Generates standalone HTML demos for each model
- Builds mkdocs with mkdocstrings API docs
- Deploys to GitHub Pages
The demos are live widgets — you can interact with them directly in the documentation.