Skip to content

Commit b843b9a

Browse files
authored
docs(reference): complete API coverage — every __all__ symbol documented (goal-13j) (#57)
Author observation.rst as the canonical home for the HRF-kernel family, HRFBold and TemporalAverage (moved off forward.rst to avoid duplicate-object warnings). Add the previously-undocumented symbols to their canonical pages: - models.rst: XY_Oscillator, JansenRitTR, the 10 WilsonCowan variants - noise.rst: the Noise base class - forward.rst: LeadfieldReadout (+ cross-refs to observation.rst) - utilities.rst: delay_index, and the Initializer/Array/Parameter type aliases index.rst: name the Simulator/Network/Fitter entry points in API conventions. Coverage audit: all 84 brainmass.__all__ symbols documented on exactly one page (0 missing, 0 duplicated). make doctest green (257 tests, 0 failures); make html build succeeded with 0 new broken-ref/orphan/duplicate warnings. Co-authored-by: Chaoming Wang <wcm.brainsim@gmail.com>
1 parent c2c4a89 commit b843b9a

7 files changed

Lines changed: 317 additions & 86 deletions

File tree

CONTEXT.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,3 +936,51 @@ Gotchas inherited: (a) execute notebooks with PYTHONPATH=<worktree-root> jupyter
936936
- **⚠ Build-helper re-run trap (cost a clobber-and-recover cycle):** the gitignored `dev/build_case_studies.py` regenerates **all 5** notebooks (stripping outputs). After executing 3, I edited the spec + re-ran the builder to fix HORN → it **silently overwrote the 3 already-executed notebooks back to un-executed**. They showed `errors=0` but ALSO `outputs=0` (the tell). **Lesson: after ANY builder re-run, re-execute EVERY notebook, not just the changed one** — or check file size / output-count, not just exit code. (Also: `jq '.cells[].outputs[]?'` returned empty/`null` and masked this; a small Python `json.load` loop counting `output_type`/`image/png` per cell is the reliable check.)
937937
- **Per-model API confirmations (executed):** `WongWangStep` = reduced **decision** model, `update(coherence=)``(r1,r2)` Hz, states `S1`/`S2`, noise via `noise_s1`/`noise_s2` (`GaussianNoise(N, sigma=…*u.nA)`); distinct from `WongWangExcInhStep`. `JansenRitStep.update` takes a 3-tuple `(M_inp*u.mV, E_inp*u.Hz, I_inp*u.mV)` via `Simulator(inputs=lambda i,t: (...))`; `eeg()` = `E−I` mV. **MEG forward from an mV source: use base `LeadFieldModel(sensor_unit=u.tesla, scale=u.nA*u.meter/u.mV)`**`MEGLeadFieldModel`'s hardcoded `scale=nA·m/tesla` is dimensionally wrong for an mV source and yields mV not T (the standing goal-13e MEG-scale note, confirmed again).
938938
- **make html:** baseline (detached `git worktree add --detach /tmp/bm_base_13i origin/main`, `make clean` first) and worktree both **build succeeded, 108 warnings / 93 unique normalized** (strip worktree path + `:LINE:`), **byte-identical: 0 new, 0 removed** — same pre-existing autosummary-duplicate + source-docstring set as goals 13a–13h. `nb_execution_mode="off"` renders committed outputs; all 5 case studies appear in `_build/html/gallery/case_studies/*.html` with their real results (e.g. "fitted C", "final test accuracy" grep-confirmed in the HTML). Only the 5 `docs/gallery/case_studies/*.ipynb` are tracked changes; `dev/` + `_build/` + `*/generated` stay gitignored.
939+
940+
### 2026-06-19 · goal-13j reference-polish
941+
- **Headline — the API reference is now COMPLETE: every one of the 84 `brainmass.__all__`
942+
symbols is documented on exactly ONE reference page** (audit script over `__init__.py`
943+
`__all__` × the autosummary/`.. data::` entries of every `reference/*.rst`: 0 missing, 0
944+
duplicated; only `__version__`/`__version_info__` are intentionally un-autosummary'd version
945+
metadata). Docs-only: `git diff --stat origin/main -- brainmass/` **empty**; 6 RST files changed
946+
(`forward`, `index`, `models`, `noise`, `observation`, `utilities`). `make doctest` **257 tests,
947+
0 failures**; `make html` **build succeeded**.
948+
- **Coverage gaps found + where fixed:** `XY_Oscillator` + `JansenRitTR` + the **10 WilsonCowan
949+
variants** (`WilsonCowanNoSaturationStep`/`Symmetric`/`Simplified`/`Linear`/`Divisive`/
950+
`DivisiveInput`/`Delayed`/`Adaptive`/`ThreePopBase`/`ThreePopulationStep`) → `models.rst` (new
951+
"Wilson-Cowan Variants" section + an Oscillator-base note + a JansenRitTR note); **`Noise`** base
952+
`noise.rst` (new "Base Class" section); **`LeadfieldReadout`**`forward.rst` lead-field
953+
autosummary (+ a unit-aware-`LeadFieldModel`-vs-trainable-`LeadfieldReadout` "use which" note);
954+
**`delay_index`** + type aliases **`Initializer`/`Array`/`Parameter`**`utilities.rst` (aliases
955+
via `.. data::`, NOT autosummary — they are `typing.Union`/`ArrayLike`, not classes/functions, so
956+
autosummary/autoclass would warn). Renamed the utilities page title to "Utilities & Types".
957+
- **⚠ THE canonical-page trap (the central design decision):** the 7 observation symbols
958+
(`HRFKernel`, the 4 HRF kernels, `HRFBold`, `TemporalAverage`) were **already autosummary'd on
959+
`forward.rst`** (goal-12). Authoring `observation.rst` with its own autosummary would have created
960+
**7 NEW `duplicate object description` warnings** (the exact failure mode the goal warned about).
961+
Fix: made **`observation.rst` the single canonical home** (full autosummary + intro + math +
962+
runnable `>>>` doctests) and **removed those autosummary entries from `forward.rst`**, replacing
963+
them with `:class:~brainmass.X` cross-refs + a `:doc:observation` pointer. Result: duplicate-object
964+
warnings **53 → 53 (zero delta)**. Lesson for any future "add a page" docs goal: grep the existing
965+
`reference/*.rst` for the symbol FIRST; if it's already autosummary'd elsewhere, MOVE it (don't
966+
add a second `:toctree:` entry) — cross-reference with `:class:`/`:func:` instead.
967+
- **⚠ make html warning delta is +21 raw (108→129) / +21 unique normalized (93→114), and it is
968+
EXPECTED + benign:** all 21 are pre-existing **source-docstring** formatting warnings
969+
(`[docutils]` "Field list ends without a blank line" ×11 + `[ref.footnote]` "Footnote [1]/[2] is
970+
not referenced" ×10) emitted from the `brainmass/` docstrings of the **WilsonCowan variants +
971+
LeadfieldReadout** — symbols that had **never been rendered into any page before** (no autosummary
972+
entry → autodoc never ran on them). Documenting them (the goal's whole point) makes autosummary
973+
generate their stub pages, which surfaces the latent docstring warnings. **0 new broken-ref, 0 new
974+
orphan, 0 new/changed duplicate-object, 0 removed** — the only gated categories are all clean; all
975+
my `:class:`/`:doc:` cross-refs resolve. These warnings live in source I'm forbidden to touch
976+
(`git diff --stat origin/main -- brainmass/` empty); a fast follow-up could fix the WilsonCowan/
977+
LeadfieldReadout docstrings (blank line before the field-list end; add `[1]`/`[2]` in-text
978+
back-references) to retire all 21 at the source.
979+
- **Baseline method (unchanged from 13e–13i):** detached `git worktree add --detach /tmp/bm_base_13j
980+
origin/main`, `make clean` first on BOTH sides, normalize by stripping the worktree path +
981+
`:LINE:`, then `comm` the sorted-unique sets — the incremental build re-emits only changed-page
982+
warnings and lies, so the clean normalized diff is the gate.
983+
- **index.rst** already listed all pages (table + quick-nav cards + toctree, from 13a/13b); added a
984+
"High-level entry points" lead-in to the API-conventions section naming `Simulator`/`Network`/
985+
`Fitter` and pointed the utilities row at "type aliases". `datasets.rst`/`viz.rst` (13b) verified
986+
complete + style-consistent (autosummary-only house style) — no changes needed.

docs/reference/forward.rst

Lines changed: 22 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -136,29 +136,15 @@ BOLD signals are typically acquired at TR ~ 1-2 seconds. Downsample high-frequen
136136
Convolution-based BOLD (HRF kernels)
137137
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
138138

139-
.. autosummary::
140-
:toctree: generated/
141-
:nosignatures:
142-
:template: classtemplate.rst
143-
144-
HRFBold
145-
HRFKernel
146-
FirstOrderVolterraHRFKernel
147-
GammaHRFKernel
148-
DoubleExponentialHRFKernel
149-
MixtureOfGammasHRFKernel
150-
151-
:class:`HRFBold` is a second, complementary BOLD path: instead of integrating the
152-
four-state Balloon-Windkessel ODE, it **convolves** downsampled neural activity
153-
with a closed-form hemodynamic response function (HRF) kernel, then decimates to
154-
the repetition time (TR):
155-
156-
.. math::
157-
158-
\text{BOLD} = k_1 V_0 \,(\,h * y_{\text{ds}} - 1\,),
159-
160-
where :math:`y_{\text{ds}}` is the temporally-averaged neural activity, :math:`h`
161-
the HRF kernel and :math:`*` 1-D convolution along time.
139+
:class:`BOLDSignal` is the biophysical (Balloon-Windkessel ODE) BOLD path. A
140+
second, complementary path -- :class:`~brainmass.HRFBold`, which **convolves**
141+
downsampled neural activity with a closed-form hemodynamic response function (HRF)
142+
kernel -- together with the HRF-kernel family
143+
(:class:`~brainmass.HRFKernel`, :class:`~brainmass.FirstOrderVolterraHRFKernel`,
144+
:class:`~brainmass.GammaHRFKernel`, :class:`~brainmass.DoubleExponentialHRFKernel`,
145+
:class:`~brainmass.MixtureOfGammasHRFKernel`) and the
146+
:class:`~brainmass.TemporalAverage` downsampler, are documented on the dedicated
147+
:doc:`observation` page.
162148

163149
**When to use which BOLD path:**
164150

@@ -167,7 +153,7 @@ the HRF kernel and :math:`*` 1-D convolution along time.
167153
:header-rows: 1
168154

169155
* - Aspect
170-
- :class:`HRFBold` (convolution)
156+
- :class:`~brainmass.HRFBold` (convolution)
171157
- :class:`BOLDSignal` (Balloon-Windkessel)
172158
* - Form
173159
- Single linear convolution with an HRF kernel
@@ -179,57 +165,8 @@ the HRF kernel and :math:`*` 1-D convolution along time.
179165
- **Fitting** / quick BOLD from a long neural trajectory
180166
- Biophysical realism
181167

182-
Both are kept; neither replaces the other.
183-
184-
**HRF kernel family.** Four closed-form kernels (each a callable ``kernel(t)``
185-
returning a dimensionless :math:`h(t)`; ``t`` is a time :class:`~brainunit.Quantity`,
186-
or a plain array interpreted as milliseconds):
187-
188-
- :class:`FirstOrderVolterraHRFKernel` -- TVB canonical underdamped damped-oscillator
189-
(Friston et al. 2000).
190-
- :class:`GammaHRFKernel` -- peak-normalised gamma pdf (Boynton et al. 1996).
191-
- :class:`DoubleExponentialHRFKernel` -- damped-oscillation difference (Polonsky et al. 2000).
192-
- :class:`MixtureOfGammasHRFKernel` -- difference of gammas / SPM canonical (Glover 1999).
193-
194-
**Example:**
195-
196-
>>> import brainmass
197-
>>> import brainunit as u
198-
>>> import jax.numpy as jnp
199-
>>> t = jnp.arange(2000.)
200-
>>> z = 1.0 + 0.5 * jnp.sin(2 * jnp.pi * t[:, None] / 800.0) # slow neural drive
201-
>>> bold = brainmass.HRFBold(
202-
... period=200. * u.ms, downsample_period=4. * u.ms,
203-
... kernel=brainmass.FirstOrderVolterraHRFKernel(duration=400. * u.ms),
204-
... )
205-
>>> y = bold(z, dt=1. * u.ms)
206-
>>> y.shape[1]
207-
1
208-
209-
210-
Temporal Averaging
211-
^^^^^^^^^^^^^^^^^^
212-
213-
.. autosummary::
214-
:toctree: generated/
215-
:nosignatures:
216-
:template: classtemplate.rst
217-
218-
TemporalAverage
219-
220-
:class:`TemporalAverage` downsamples a trajectory by averaging over non-overlapping
221-
windows of ``w = round(period / dt)`` samples (``y[k] = mean(signal[k*w : (k+1)*w])``;
222-
trailing partial windows are dropped). It is the **averaging** complement to the
223-
point-decimation ``Simulator(sample_every=k)`` subsampling -- smoother and
224-
anti-aliased -- and is the downsampler :class:`HRFBold` uses internally. Apply it as
225-
a post-transform on a run trajectory:
226-
227-
>>> import brainmass
228-
>>> import brainunit as u
229-
>>> import jax.numpy as jnp
230-
>>> signal = jnp.arange(20.).reshape(20, 1)
231-
>>> brainmass.TemporalAverage(period=5. * u.ms)(signal, dt=1. * u.ms).shape
232-
(4, 1)
168+
Both are kept; neither replaces the other. See :doc:`observation` for the kernel
169+
forms, equations and runnable examples.
233170

234171

235172
EEG/MEG Lead-Field Models
@@ -243,7 +180,16 @@ EEG/MEG Lead-Field Models
243180
LeadFieldModel
244181
EEGLeadFieldModel
245182
MEGLeadFieldModel
246-
183+
LeadfieldReadout
184+
185+
:class:`LeadFieldModel` (and its :class:`EEGLeadFieldModel` / :class:`MEGLeadFieldModel`
186+
specializations) is the **unit-aware physical** forward operator -- it carries
187+
brainunit units, aggregates vertices to regions, and can sample a measurement-noise
188+
covariance. :class:`LeadfieldReadout` is a complementary, lightweight **trainable**
189+
EEG/MEG head on unitless arrays: a single learnable lead-field matrix (optionally
190+
L1/L2 normalised) suitable for end-to-end gradient fitting. Use
191+
:class:`LeadFieldModel` when you have a physically-calibrated lead field with units;
192+
use :class:`LeadfieldReadout` when the projection is a parameter to be learned.
247193

248194
Lead-field models perform linear mapping from source dipoles to sensor measurements:
249195

docs/reference/index.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ The ``brainmass`` package provides the following components:
4848
* - :doc:`viz`
4949
- Thin plotting helpers for time series, connectivity, and spectra
5050
* - :doc:`utilities`
51-
- Utility functions for common operations
51+
- Utility functions, the model catalogue, and public type aliases
5252

5353

5454
Quick Navigation
@@ -121,7 +121,21 @@ Quick Navigation
121121
General API Conventions
122122
-----------------------
123123

124-
All neural mass models and dynamics classes in ``brainmass`` follow these conventions:
124+
**High-level entry points:**
125+
126+
Most workflows go through three orchestration classes (see :doc:`orchestration`)
127+
rather than driving the step models by hand:
128+
129+
- :class:`Simulator` -- wraps any model (a single node or a whole-brain
130+
:class:`Network`) in the compiled run loop and collects monitored trajectories
131+
into a unit-aware result dict.
132+
- :class:`Network` -- wires a node model into a delay-coupled whole-brain network
133+
from a connectome.
134+
- :class:`Fitter` -- fits a model's trainable parameters to data behind one
135+
``.fit`` call, swapping gradient / Nevergrad / SciPy backends.
136+
137+
The per-step conventions below describe the underlying model contract that these
138+
entry points build on.
125139

126140
**Initialization:**
127141

docs/reference/models.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,16 @@ Oscillator Models
179179
:nosignatures:
180180
:template: classtemplate.rst
181181

182+
XY_Oscillator
182183
HopfStep
183184
VanDerPolStep
184185
StuartLandauStep
185186
KuramotoNetwork
186187

188+
:class:`XY_Oscillator` is the shared two-variable ``(x, y)`` planar-oscillator base
189+
that :class:`HopfStep`, :class:`StuartLandauStep` and :class:`VanDerPolStep` specialise;
190+
instantiate the concrete subclasses for simulations.
191+
187192

188193
**Example: Hopf Oscillator**
189194

@@ -253,8 +258,13 @@ Rate-Based Models
253258

254259
WilsonCowanStep
255260
JansenRitStep
261+
JansenRitTR
256262
WongWangStep
257263

264+
:class:`JansenRitTR` wraps :class:`JansenRitStep` with an inner dt-substep loop that
265+
emits one output sample per acquisition repetition time (TR) -- the in-package
266+
precedent for TR-rate observation.
267+
258268

259269
**Example: Wilson-Cowan Model**
260270

@@ -300,6 +310,35 @@ The Jansen-Rit model simulates EEG-like signals from a cortical column with thre
300310
# The output represents pyramidal membrane potential (EEG proxy)
301311
302312
313+
Wilson-Cowan Variants
314+
^^^^^^^^^^^^^^^^^^^^^^
315+
316+
Beyond the canonical :class:`WilsonCowanStep`, ``brainmass`` ships a family of
317+
Wilson-Cowan variants that swap the activation nonlinearity, normalisation, or add
318+
adaptation / explicit delays / a third population. They share the same
319+
``(rE, rI)``-style state and ``update`` contract.
320+
321+
.. autosummary::
322+
:toctree: generated/
323+
:nosignatures:
324+
:template: classtemplate.rst
325+
326+
WilsonCowanNoSaturationStep
327+
WilsonCowanSymmetricStep
328+
WilsonCowanSimplifiedStep
329+
WilsonCowanLinearStep
330+
WilsonCowanDivisiveStep
331+
WilsonCowanDivisiveInputStep
332+
WilsonCowanDelayedStep
333+
WilsonCowanAdaptiveStep
334+
WilsonCowanThreePopBase
335+
WilsonCowanThreePopulationStep
336+
337+
:class:`WilsonCowanThreePopBase` is the shared base for the three-population
338+
variant; instantiate :class:`WilsonCowanThreePopulationStep` (or another concrete
339+
variant) for simulations.
340+
341+
303342
Spiking Network Reductions
304343
^^^^^^^^^^^^^^^^^^^^^^^^^^^
305344

docs/reference/noise.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,21 @@ For custom use cases, generate noise samples directly:
115115
API Reference
116116
-------------
117117

118+
Base Class
119+
^^^^^^^^^^
120+
121+
.. autosummary::
122+
:toctree: generated/
123+
:nosignatures:
124+
:template: classtemplate.rst
125+
126+
Noise
127+
128+
:class:`Noise` is the abstract base every noise process subclasses; it defines the
129+
common ``in_size`` / ``update`` contract. Instantiate one of the concrete processes
130+
below rather than :class:`Noise` directly.
131+
132+
118133
Stateless Noise
119134
^^^^^^^^^^^^^^^
120135

0 commit comments

Comments
 (0)