Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions docs/ecosystem/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# The HSSM ecosystem

HSSM is the user-facing package of a four-part toolchain. Most users never
leave it: install HSSM, fit models, publish. You end up here when a question
crosses a package boundary — where does a likelihood network come from, which
version pairs with which, why does an ONNX file have to look a certain way.

This page is the map. It is maintained in
[HSSMSpine](https://github.com/lnccbrown/HSSMSpine), the ecosystem's
coordination repository, and published here so there is one description of the
whole rather than three partial ones.

## The packages you install

| Package | Owns | Start here if you want to... |
|---|---|---|
| [HSSM](https://lnccbrown.github.io/HSSM/) | Bayesian inference on sequential sampling models — model specification, priors, sampling, diagnostics, model comparison | ...fit a model to behavioral data. This is the default answer. |
| [ssm-simulators](https://lnccbrown.github.io/ssm-simulators/) (`ssms`) | The generative models: simulators for the SSM family, task environments and learning rules for RLSSMs, and the training-data generators | ...simulate from a model, add a new model to the family, or generate training data. |
| [LANfactory](https://lnccbrown.github.io/LANfactory/) | Training likelihood approximation networks on simulated data, and exporting them to ONNX | ...train your own likelihood network, or export one trained elsewhere. |
| [LAN_pipeline_minimal](https://github.com/lnccbrown/LAN_pipeline_minimal) | Orchestration on a cluster: data generation and network training as scheduled jobs | ...produce networks at scale rather than one at a time. |

## How the pieces connect

The chain runs in one direction, and the handoffs are files rather than
imports:

```text
ssm-simulators ──simulated training data──> LANfactory ──ONNX network──> HuggingFace
HSSM
(downloads networks at run time)
```

Two things follow from this shape.

**Networks are artifacts, not code.** Many SSMs have no analytical likelihood.
For those, a neural network is trained once — offline, on simulated data — to
approximate the likelihood, and HSSM calls that network during sampling. The
trained networks live on
[HuggingFace](https://huggingface.co/franklab/HSSM); HSSM downloads what a
model needs on first use. You do not need LANfactory installed to use a
network someone else trained.

**The boundary is a contract, not a convention.** Any ONNX file HSSM loads
must expose one per-trial forward pass with every input dimension concrete;
HSSM batches across trials itself. That rule is stated once, with runnable
checks, in [The ONNX likelihood
contract](https://lnccbrown.github.io/HSSM/how_to/custom_onnx_likelihoods/).
It is also what makes the ecosystem open at the edges: a network trained in
[sbi](https://github.com/sbi-dev/sbi) or
[BayesFlow](https://github.com/bayesflow-org/bayesflow) becomes usable in HSSM
by exporting it to ONNX with LANfactory's exporters, after which HSSM loads it
with the same `loglik="model.onnx"` gesture it uses for its own networks — no
library-specific glue on the HSSM side.

## Which package answers your question

| Your question | Where it is answered |
|---|---|
| How do I fit this model to my data? | [HSSM — Learn](https://lnccbrown.github.io/HSSM/) |
| What models exist, and what are their parameters? | [ssm-simulators — Reference](https://lnccbrown.github.io/ssm-simulators/) |
| How do I simulate data from a model? | [ssm-simulators — Learn](https://lnccbrown.github.io/ssm-simulators/) |
| How do I add a model that does not exist yet? | [ssm-simulators — Contributing](https://lnccbrown.github.io/ssm-simulators/) |
| How do I train a likelihood network for it? | [LANfactory — Learn](https://lnccbrown.github.io/LANfactory/) |
| I trained a network in sbi or BayesFlow — now what? | [HSSM — Bring your own likelihood](https://lnccbrown.github.io/HSSM/how_to/external_trainers/) |
| How do I track training and data-generation runs? | [Tracking runs with MLflow](#tracking-runs-with-mlflow), below |
| Which versions work together? | [Version compatibility](#version-compatibility), below |
| What is this package in my traceback or lockfile? | [Supporting components](#supporting-components), below |
| How do I contribute across several packages? | [Development and coordination](#development-and-coordination), below |

Each site is organised the same way — **Learn**, **How-to guides**,
**Explanations**, **Reference** — so the tab you want sits in the same place
on all three.

## Supporting components

These are not packages you choose — they arrive with HSSM, or hold artifacts
it fetches. They are listed here because their names show up in tracebacks,
lockfiles, and download logs.

| Component | What it is |
|---|---|
| [`hddm-wfpt`](https://github.com/lnccbrown/hddm-wfpt) | The Cython implementation of the Wiener first-passage-time likelihood, inherited from HDDM. Installed with HSSM, and used for the analytical DDM likelihoods. |
| [`franklab/HSSM`](https://huggingface.co/franklab/HSSM) | The HuggingFace repository holding trained likelihood networks. HSSM downloads from it on first use of a model without an analytical likelihood. |
| [`franklab/ssms_gui`](https://huggingface.co/spaces/franklab/ssms_gui) | A HuggingFace Space for exploring SSM behaviour interactively, built on `ssm-simulators`. Useful for building intuition about what a parameter does. |
| conda-forge feedstocks | `hssm` and `ssm-simulators` are also published on conda-forge; the feedstock repositories carry the recipes. |

Third-party libraries that do real work under the hood — PyMC, Bambi, ArviZ,
JAX, PyTensor, ONNX Runtime — are dependencies rather than ecosystem
components, and their own documentation is the right reference for them.

## Development and coordination

Two repositories exist for people working *on* the ecosystem rather than with
it. You never need them to fit models, and neither is a Python package you
install alongside HSSM.

| Repository | Role |
|---|---|
| [HSSMSpine](https://github.com/lnccbrown/HSSMSpine) | The coordination repository. It holds no library code — it carries cross-repo context, shared development workflows, the release playbook, the shared documentation brand, and this page. Contributors working across two or more packages start here. |
| [HSSMCortex](https://github.com/lnccbrown/HSSMCortex) | The capability layer: a knowledge base of papers, modeling taxonomies, and curated guides, plus tooling that makes that knowledge queryable during development. |

If you are contributing to a single package, its own contributing guide is the
place to start; the spine matters when a change spans packages, such as adding
a model that needs a simulator, a trained network, and an HSSM configuration.

## Tracking runs with MLflow

Data generation (`ssms`) and network training (LANfactory) both log to
[MLflow](https://mlflow.org/). Point them at the same tracking store and the
two halves of a network's history end up in one place.

Two environment variables are the interface:

```bash
export MLFLOW_TRACKING_URI="sqlite:////absolute/path/to/tracking.db"
export MLFLOW_ARTIFACT_LOCATION="/absolute/path/to/artifacts"
```

`ssms` records each generation run — the generator and model configuration,
the `data_output_folder`, the number of files produced and their total size,
and tags for the run phase and any SLURM job it ran under. LANfactory records
each training run's configuration and metrics.

The two are linked explicitly rather than by convention: pass the data
generation run's experiment id to the trainer with
`--data-generation-experiment-id`, and LANfactory records the lineage — it can
also discover the training-data folder from MLflow instead of being told where
it is.

Per-package details — CLI flags, what exactly is logged, how to query it —
live with the packages:
[ssm-simulators](https://lnccbrown.github.io/ssm-simulators/core_tutorials/using_mlflow/)
and [LANfactory](https://lnccbrown.github.io/LANfactory/using_mlflow/).

## Version compatibility

The packages are released independently, in dependency order:
`ssm-simulators` → `LANfactory` → HSSM. Install floors, rather than pins, are
what the ecosystem guarantees:

| Consumer | Requires |
|---|---|
| HSSM | `ssm-simulators>=0.13.1` |
| LANfactory | `ssm-simulators>=0.13.1` |
| LAN_pipeline_minimal | `ssm-simulators>=0.13.2`, `lanfactory>=0.8` |

All released packages require Python 3.12 or newer. `pip install hssm` pulls a
compatible `ssm-simulators` automatically; you only need to think about this
when you are pinning an environment or building networks yourself.

## Where to ask

- **Usage questions and modeling advice** —
[HSSM Discussions](https://github.com/lnccbrown/HSSM/discussions).
- **Bugs** — the issue tracker of the package the bug is in. If you are not
sure which, HSSM's tracker is the right default and we will move it.
- **New models** — [ssm-simulators](https://github.com/lnccbrown/ssm-simulators/issues),
which is where the model family lives.
129 changes: 129 additions & 0 deletions docs/explanations/coming_from_hddm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Coming from HDDM

HDDM is HSSM's predecessor, and the workflow will feel familiar: you hand a
data frame of reaction times and responses to a model object, sample it, and
inspect the posterior. This page covers what changes, so that an analysis you
already understand can be rebuilt rather than re-derived.

There are three kinds of differences: the data your model expects, the way a
model is specified, and one parameter convention that will silently change
your numbers if you miss it.

## The convention that matters: `a`

**HSSM's `a` is the distance from the starting point to one boundary; HDDM's
`a` is the distance between the two boundaries.** An HDDM fit reporting
`a = 2.0` corresponds to `a = 1.0` in HSSM.

This is not a cosmetic difference. If you carry HDDM parameter values across —
as priors, as simulation ground truth, or when comparing published estimates —
divide them by two.

Halving also matters for a second reason: HSSM's bounds on `a` depend on which
likelihood you use. The analytical DDM leaves `a` on `(0, inf)`, but the
neural approximation is only valid over the range it was trained on, which for
`ddm` is `(0.3, 2.5)`. An HDDM `a` of `2.0` becomes a comfortable `1.0`; an
HDDM `a` of `6.0` becomes `3.0`, which is outside the trained range and calls
for the analytical likelihood instead.

You can see the conversion in HSSM's own source: the black-box likelihoods
wrap HDDM's Cython WFPT implementation and pass `a * 2` when they call it. The
other parameters — `v`, `z`, `t`, `sv`, `sz`, `st` — carry over unchanged, and
`z` is a relative starting point in `(0, 1)` in both packages.

## Your data

| HDDM | HSSM | Note |
|---|---|---|
| `subj_idx` | `participant_id` | Rename the column. It is the default grouping variable in formulas. |
| `response` coded `0` / `1` | `response` coded `-1` / `1` | Recode. HSSM's two-choice models use `-1` and `1`. |
| `rt` | `rt` | Unchanged, in seconds. |

In practice this is two lines before you build the model:

```python
data = data.rename(columns={"subj_idx": "participant_id"})
data["response"] = np.where(data["response"] == 0, -1, 1)
```

Unlike HDDM, HSSM does not require a participant column at all — a model with
no group-specific terms is fit to the pooled data. The column matters when you
write a formula that groups by it.

## Specifying a model

HDDM selects a model by class and configures it with keyword arguments
(`HDDMRegressor`, `depends_on`, `include`). HSSM has one class, and the model
family is a string:

```python
model = hssm.HSSM(data=data, model="ddm")
```

`model=` accepts `ddm`, `ddm_sdv`, `full_ddm`, `angle`, `levy`, `ornstein`,
`weibull`, and others; `hssm.list_models()` prints the current set. Models
without an analytical likelihood are served by a neural approximation that
HSSM downloads on first use, which is why the model family is a string rather
than a separate class.

The larger change is how effects on parameters are expressed. HDDM's
`depends_on` splits a parameter by condition; HSSM uses `lmer`-style formulas,
one per parameter, which covers the same ground and more:

| What you want | HDDM | HSSM |
|---|---|---|
| Drift varies by condition | `depends_on={'v': 'stim'}` | `include=[{"name": "v", "formula": "v ~ 0 + C(stim)"}]` |
| Drift varies by a continuous covariate | `HDDMRegressor` with a patsy formula | `"formula": "v ~ 1 + x"` |
| Per-participant drift | hierarchical by default | `"formula": "v ~ 1 + (1\|participant_id)"` |
| Several parameters, same structure | one specification per parameter | `global_formula="y ~ 1 + (1\|participant_id)"` |

The [hierarchical modeling tutorial](../getting_started/hierarchical_modeling.ipynb)
covers the formula syntax; [hierarchical DDM
regressions](../tutorials/ddm_hierarchical_tutorial.ipynb) works through
within-subject, between-subject, and interaction designs.

## Hierarchy is opt-in, and parameterized differently

HDDM models are hierarchical by default. In HSSM, a model is hierarchical
exactly when a parameter has a group-specific term in its formula — there is
no `hierarchical` switch to set.

The parameterization also differs. HDDM's convention is what HSSM calls the
*centered* form: drop the common intercept (`v ~ 0 + ...`) and let the
participant-level coefficients come from one group distribution with a free
mean and standard deviation. HSSM defaults to the *non-centered* form, which
usually samples better. To reproduce the HDDM specification, set
`noncentered=False`; the group distributions then appear as
`v_1|participant_id_mu`-style nodes. [Centered vs. non-centered
parameterizations](../tutorials/centered_vs_noncentered_basic_logic.ipynb)
explains the tradeoff and when each is the better choice.

## Concepts with no direct translation

- **Priors.** For `ddm`, `ddm_sdv`, and `full_ddm`, HSSM's default priors on
regression terms are derived from HDDM's — the settings are carried in
`hssm.prior` under names like `HDDM_MU` and `HDDM_SIGMA` — so a regression
model on these families starts from familiar ground. The rule is the
likelihood: those defaults apply unless you are using the neural
(`approx_differentiable`) likelihood, which has its own priors derived from
the network's training bounds. Specifying your own is a different interface
— see [Specify priors and fix parameters](../how_to/specify_priors.ipynb).
- **Outliers.** HDDM's `p_outlier` exists in HSSM under the same name, and the
lapse distribution is configurable rather than fixed. See [Model outliers
with lapse probabilities](../tutorials/lapse_prob_and_dist.ipynb).
- **Sampling.** HSSM samples with PyMC and can dispatch to NumPyro and other
JAX-based samplers. `model.sample()` passes keyword arguments through to
PyMC, so `chains`, `draws`, `tune`, and `target_accept` behave as they do in
PyMC rather than as in HDDM's sampler.
- **Results.** Posterior samples come back as an ArviZ `InferenceData` object
rather than an HDDM-specific container, so diagnostics, plots, and model
comparison are standard ArviZ from there on.

## Where to start

If you are rebuilding an existing analysis, [the HSSM
tutorial](../tutorials/main_tutorial.ipynb) is the fastest route to the parts
you already know, in the new interface. The [Winterbrain 2025 workshop
snapshot](../archive/hssm_tutorial_workshop_2.ipynb) works through an analysis
first run in HDDM and then rebuilt in HSSM, including the data preparation
above.
Loading