Skip to content

Commit 2e01f21

Browse files
committed
feat: accept model instance + docs + readme pypi install
1 parent a2408ce commit 2e01f21

4 files changed

Lines changed: 121 additions & 6 deletions

File tree

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ Interactive phase plane widget for neural mass models, usable in Jupyter noteboo
2020

2121
## Installation
2222

23+
```bash
24+
pip install tvb-phaseplane
25+
```
26+
27+
For development with Jupyter:
28+
```bash
29+
pip install tvb-phaseplane[dev]
30+
```
31+
32+
Or install from source:
2333
```bash
2434
# Using uv
2535
uv venv
@@ -29,11 +39,6 @@ uv pip install -e .
2939
pip install -e .
3040
```
3141

32-
For development with Jupyter:
33-
```bash
34-
uv pip install -e ".[dev]"
35-
```
36-
3742
## Quick Start
3843

3944
### Jupyter / VS Code — Built-in Models

docs/deployment.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,42 @@ pp
3636

3737
The SymPy expressions are transpiled to JavaScript via an inlined Nerdamer CAS (~100 KB) that compiles and runs entirely in the browser.
3838

39+
### Passing a Model Instance {#model-instance}
40+
41+
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.
42+
43+
```python
44+
from tvb_phaseplane import PhasePlaneWidget, MPRModel
45+
46+
# 1. Create a model instance
47+
model = MPRModel()
48+
49+
# 2. Optionally override default parameter values
50+
model.default_params.update({"J": 15.0, "eta_bar": -5.0})
51+
52+
# 3. Pass the instance to the widget
53+
widget = PhasePlaneWidget(model=model)
54+
widget
55+
```
56+
57+
After the user adjusts sliders in the widget, read back the tuned parameter values:
58+
59+
```python
60+
print("Current parameter values:")
61+
for name, value in widget.params.items():
62+
print(f" {name:12s} = {value:.4f}")
63+
```
64+
65+
You can also read back computed data:
66+
67+
```python
68+
print(f"Fixed points: {len(widget.fixed_points)}")
69+
for fp in widget.fixed_points:
70+
print(f" x={fp[0]:.4f}, y={fp[1]:.4f}, type={fp[2]}")
71+
```
72+
73+
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.
74+
3975
### Exporting Notebooks
4076

4177
Use [jupytext](https://jupytext.readthedocs.io/) to keep notebooks in plain `.py` percent-format:

examples/model_instance_demo.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Demo: pass a Python model instance to the widget and read back tuned parameters.
2+
3+
This demonstrates the ``model=`` argument to ``PhasePlaneWidget``,
4+
which accepts any ``BaseModel`` subclass instance. After the user
5+
interacts with the widget (dragging sliders, clicking the phase plane),
6+
the current parameter values can be read back from the ``.params``
7+
traitlet.
8+
9+
If the model name is registered in ``MODEL_REGISTRY`` the JavaScript
10+
front-end will recognise it and carry out all computation client-side.
11+
For arbitrary custom models that are *not* in the built-in registry use
12+
:func:`phase_plane` instead (see ``custom_model_demo.py``).
13+
"""
14+
15+
from tvb_phaseplane import PhasePlaneWidget, MPRModel
16+
17+
# ------------------------------------------------------------------
18+
# 1. Instantiate a model class and optionally override defaults
19+
# ------------------------------------------------------------------
20+
model = MPRModel()
21+
22+
# You can pre-configure parameters before creating the widget
23+
# (the widget will pick these up as its initial slider positions)
24+
initial_params = {
25+
"delta": 1.0,
26+
"eta_bar": -5.0,
27+
"J": 15.0,
28+
"I": 0.0,
29+
}
30+
31+
# Override the model's default parameter values with your own
32+
for k, v in initial_params.items():
33+
model.default_params[k] = v
34+
35+
# ------------------------------------------------------------------
36+
# 2. Pass the instance to PhasePlaneWidget
37+
# ------------------------------------------------------------------
38+
widget = PhasePlaneWidget(model=model)
39+
40+
# The widget is now live — display it in Jupyter / VS Code
41+
widget
42+
43+
# ------------------------------------------------------------------
44+
# 3. After user interaction, read back tuned parameters
45+
# ------------------------------------------------------------------
46+
# Run this cell *after* adjusting sliders in the widget:
47+
print("Current parameter values after tuning:")
48+
for name, value in widget.params.items():
49+
print(f" {name:12s} = {value:.4f}")
50+
51+
# You can also programmatically set parameters and trigger updates:
52+
# widget.params["J"] = 20.0
53+
54+
# ------------------------------------------------------------------
55+
# 4. Read back computed data (nullclines, fixed points, trajectory)
56+
# ------------------------------------------------------------------
57+
print(f"\nFixed points detected: {len(widget.fixed_points)}")
58+
for fp in widget.fixed_points[:5]:
59+
print(f" x={fp[0]:.4f}, y={fp[1]:.4f}, type={fp[2]}")
60+
61+
print(f"\nTrajectory points: {len(widget.trajectory)}")
62+
63+
# ------------------------------------------------------------------
64+
# 5. Export the tuned configuration to standalone HTML
65+
# ------------------------------------------------------------------
66+
widget.to_standalone_html("mpr_tuned.html", title="MPR Model – Tuned Parameters")
67+
print("\nExported to 'mpr_tuned.html' with current parameter values.")

src/tvb_phaseplane/widget.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,20 @@ def _validate_display_mode(self, proposal):
9090
)
9191
return v
9292

93-
def __init__(self, **kwargs):
93+
_model_instance = None
94+
95+
def __init__(self, model=None, **kwargs):
96+
if model is not None:
97+
self._model_instance = model
98+
kwargs.setdefault("model_name", model.name)
9499
super().__init__(**kwargs)
95100
self._update_model()
96101

97102
def _get_model(self):
98103
from .models import MODEL_REGISTRY
99104

105+
if self._model_instance is not None:
106+
return self._model_instance
100107
cls = MODEL_REGISTRY.get(self.model_name, MODEL_REGISTRY["wilson_cowan"])
101108
return cls()
102109

0 commit comments

Comments
 (0)