Quickstart¶
Fit and check your first HSSM model in about 15 minutes: simulate data, build a model, sample the posterior, and run a posterior predictive check.
This page only runs the workflow. For the guided introduction that teaches it — model choice, priors, diagnostics, validation, comparison — continue with the HSSM tutorial.
Run this tutorial¶
On Colab, uncomment and run the installation cell below once, then restart the runtime. For local setup, GPU extras, and troubleshooting see the Installation guide.
# %pip install hssm
Import Modules¶
import arviz as az
import numpy as np
import hssm
%matplotlib inline
Simulating a dataset¶
The hssm.simulate_data() function generates data for most SSM types. Here we simulate some data from a Drift Diffusion Model (DDM) with known true parameter values.
v_true, a_true, z_true, t_true = [0.5, 1.5, 0.5, 0.5]
dataset = hssm.simulate_data(
model="ddm",
theta=[v_true, a_true, z_true, t_true],
size=1000,
)
dataset
| rt | response | |
|---|---|---|
| 0 | 3.062525 | 1.0 |
| 1 | 1.122580 | 1.0 |
| 2 | 3.332273 | 1.0 |
| 3 | 1.741256 | 1.0 |
| 4 | 5.868677 | 1.0 |
| ... | ... | ... |
| 995 | 2.810799 | 1.0 |
| 996 | 2.115806 | -1.0 |
| 997 | 1.443752 | 1.0 |
| 998 | 2.863981 | -1.0 |
| 999 | 2.619913 | 1.0 |
1000 rows × 2 columns
Model specification¶
A DDM using defaults¶
We begin with a simple example. The only information required to create a model in HSSM is a dataset.
A dataset in HSSM is typically a pandas DataFrame with at least rt and response columns, which indicates response time and choices respectively. Right now, response only accepts values of 1 and -1.
If none of the optional parameters is provided, HSSM will assume that we are modeling a classical DDM model with v, a, z, and t as its parameters. HSSM also provides a default analytical likelihood function and some uninformative priors. These can all be overriden by user inputs.
Note
From HSSM v0.1.2 on, lapse distributions will be enabled by default, with `p_outlier` fixed to 0.05. You can set `p_outlier` to 0 or `None` to disable lapse distributions.
simple_ddm_model = hssm.HSSM(data=dataset)
simple_ddm_model
Model initialized successfully.
Hierarchical Sequential Sampling Model
Model: ddm
Response variable: rt,response
Likelihood: analytical
Observations: 1000
Parameters:
v:
Prior: Normal(mu: 0.0, sigma: 2.0)
Explicit bounds: (-inf, inf)
a:
Prior: HalfNormal(sigma: 2.0)
Explicit bounds: (0.0, inf)
z:
Prior: Uniform(lower: 0.0, upper: 1.0)
Explicit bounds: (0.0, 1.0)
t:
Prior: HalfNormal(sigma: 2.0)
Explicit bounds: (0.0, inf)
Lapse probability: 0.05
Lapse distribution: Uniform(lower: 0.0, upper: 20.0)
Performing MCMC sampling¶
Similar to PyMC, HSSM provides the sample() method once the model is created to perform MCMC sampling. By default, it uses PyMC's NUTS sampler. We can use other samplers, which we will cover soon.
simple_ddm_model.sample()
Using default initvals.
Initializing NUTS using adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [z, t, a, v]
/Users/afengler/.local/share/uv/python/cpython-3.14.6-macos-aarch64-none/lib/python3.14/multiprocessing/popen_fork.py:76: RuntimeWarning: os.fork() was called. os.fork() is incompatible with multithreaded code, and JAX is multithreaded, so this will likely lead to a deadlock. self.pid = os.fork()
/Users/afengler/.local/share/uv/python/cpython-3.14.6-macos-aarch64-none/lib/python3.14/multiprocessing/popen_fork.py:76: RuntimeWarning: os.fork() was called. os.fork() is incompatible with multithreaded code, and JAX is multithreaded, so this will likely lead to a deadlock. self.pid = os.fork()
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 8 seconds.
<xarray.DataTree>
Group: /
├── Group: /posterior
│ Dimensions: (chain: 4, draw: 1000)
│ Coordinates:
│ * chain (chain) int64 32B 0 1 2 3
│ * draw (draw) int64 8kB 0 1 2 3 4 5 6 7 ... 993 994 995 996 997 998 999
│ Data variables:
│ z (chain, draw) float64 32kB 0.5001 0.502 0.5047 ... 0.5184 0.5044
│ v (chain, draw) float64 32kB 0.5507 0.5163 0.5116 ... 0.5131 0.5309
│ t (chain, draw) float64 32kB 0.5564 0.5479 0.5668 ... 0.5345 0.5358
│ a (chain, draw) float64 32kB 1.454 1.433 1.422 ... 1.458 1.48 1.47
│ Attributes:
│ created_at: 2026-08-12T04:53:30.053546+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.3.0
│ creation_library_language: Python
│ inference_library: pymc
│ inference_library_version: 6.2.0
│ sample_dims: ['chain', 'draw']
│ sampling_time: 8.270431995391846
│ tuning_steps: 1000
│ modeling_interface: bambi
│ modeling_interface_version: 0.20.0
├── Group: /sample_stats
│ Dimensions: (chain: 4, draw: 1000)
│ Coordinates:
│ * chain (chain) int64 32B 0 1 2 3
│ * draw (draw) int64 8kB 0 1 2 3 4 5 ... 995 996 997 998 999
│ Data variables: (12/18)
│ energy (chain, draw) float64 32kB 1.963e+03 ... 1.962e+03
│ index_in_trajectory (chain, draw) int64 32kB -2 3 -1 -6 -2 ... 5 1 -3 -3
│ perf_counter_start (chain, draw) float64 32kB 1.629e+05 ... 1.629e+05
│ largest_eigval (chain, draw) float64 32kB nan nan nan ... nan nan
│ divergences (chain, draw) int64 32kB 0 0 0 0 0 0 ... 0 0 0 0 0 0
│ n_steps (chain, draw) float64 32kB 7.0 7.0 7.0 ... 3.0 3.0
│ ... ...
│ max_energy_error (chain, draw) float64 32kB -0.4641 -0.2886 ... 0.1677
│ perf_counter_diff (chain, draw) float64 32kB 0.003328 ... 0.001517
│ smallest_eigval (chain, draw) float64 32kB nan nan nan ... nan nan
│ lp (chain, draw) float64 32kB -1.961e+03 ... -1.96e+03
│ diverging (chain, draw) bool 4kB False False ... False False
│ step_size_bar (chain, draw) float64 32kB 0.6304 0.6304 ... 0.5957
│ Attributes:
│ created_at: 2026-08-12T04:53:30.057522+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.3.0
│ creation_library_language: Python
│ inference_library: pymc
│ inference_library_version: 6.2.0
│ sample_dims: ['chain', 'draw']
│ sampling_time: 8.270431995391846
│ tuning_steps: 1000
│ modeling_interface: bambi
│ modeling_interface_version: 0.20.0
├── Group: /observed_data
│ Dimensions: (__obs__: 1000, rt,response_extra_dim_0: 2)
│ Coordinates:
│ * __obs__ (__obs__) int64 8kB 0 1 2 3 4 ... 996 997 998 999
│ * rt,response_extra_dim_0 (rt,response_extra_dim_0) int64 16B 0 1
│ Data variables:
│ rt,response (__obs__, rt,response_extra_dim_0) float64 16kB ...
│ Attributes:
│ created_at: 2026-08-12T04:53:30.058710+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.3.0
│ creation_library_language: Python
│ inference_library: pymc
│ inference_library_version: 6.2.0
│ sample_dims: []
│ modeling_interface: bambi
│ modeling_interface_version: 0.20.0
└── Group: /log_likelihood
Dimensions: (chain: 4, draw: 1000, __obs__: 1000)
Coordinates:
* chain (chain) int64 32B 0 1 2 3
* draw (draw) int64 8kB 0 1 2 3 4 5 6 ... 993 994 995 996 997 998 999
* __obs__ (__obs__) int64 8kB 0 1 2 3 4 5 6 ... 994 995 996 997 998 999
Data variables:
rt,response (chain, draw, __obs__) float64 32MB -2.064 -0.8878 ... -1.758
Attributes:
modeling_interface: bambi
modeling_interface_version: 0.20.0Visualizing the traces with ArviZ¶
Like that of pm.sample(), the result of model.sample() is also an az.InferenceData object, which can be used with the ArviZ package. The last sample the model has performed is stored in the model.traces property for eazy access. Here we use the az.summary() and az.plot_trace() functions to generate a summary table and diagnostic plots for the samples.
az.summary(simple_ddm_model.traces)
| mean | sd | eti89_lb | eti89_ub | ess_bulk | ess_tail | r_hat | mcse_mean | mcse_sd | |
|---|---|---|---|---|---|---|---|---|---|
| z | 0.5049 | 0.0144 | 0.48 | 0.53 | 2285 | 2419 | 1.00 | 0.0003 | 0.00022 |
| v | 0.535 | 0.035 | 0.48 | 0.59 | 2601 | 2725 | 1.00 | 0.00068 | 0.0005 |
| t | 0.5356 | 0.0209 | 0.5 | 0.57 | 2361 | 2121 | 1.00 | 0.00043 | 0.0003 |
| a | 1.458 | 0.0269 | 1.4 | 1.5 | 2447 | 2730 | 1.00 | 0.00054 | 0.00038 |
az.plot_trace_dist(simple_ddm_model.traces);
Posterior predictive check¶
Does the fitted model reproduce the data it was trained on? hssm.plotting.plot_predictive() overlays predictions simulated from the posterior on the observed reaction-time distribution — the fastest visual check that the model makes sense.
hssm.plotting.plot_predictive(simple_ddm_model);
No posterior_predictive samples found. Generating posterior_predictive samples using the provided DataTree object and the original data. This will modify the provided DataTree object, or if not provided, the traces object stored inside the model.
/Users/afengler/Projects/proj_hssmspine/HSSMSpine/repos/.worktrees/HSSM/1164-getting-started-consolidation/.venv/lib/python3.14/site-packages/pytensor/link/numba/dispatch/basic.py:234: UserWarning: Numba will use object mode to run ddm_RV_rv{"(),(),(),(),()->(2)"}'s perform method. Set `pytensor.config.compiler_verbose = True` to see more details.
warnings.warn(
/Users/afengler/Projects/proj_hssmspine/HSSMSpine/repos/.worktrees/HSSM/1164-getting-started-consolidation/.venv/lib/python3.14/site-packages/pytensor/link/numba/dispatch/basic.py:234: UserWarning: Numba will use object mode to run ddm_RV_rv{"(),(),(),(),()->(2)"}'s perform method. Set `pytensor.config.compiler_verbose = True` to see more details.
warnings.warn(
Congratulations — you have built, sampled, and checked your first HSSM model!
Next steps¶
Continue along the learning path:
- The HSSM tutorial — the full guided introduction: model choice, priors, regressions, validation, and comparison.
- How to specify priors and fix parameters — the reference walkthrough for prior syntax.
- Hierarchical modeling — multi-participant models.
- A complete scientific workflow — an advanced, end-to-end case study.