Construct Custom Models from simulators and JAX callables¶
import os
import pickle
import arviz as az
import jax.numpy as jnp
import lanfactory as lf
import numpy as np
import pymc as pm
import hssm
from hssm.config import ModelConfig
mlflow not available mlflow not available
Simulate Data¶
As a pre-amble, we will simulate a simple dataset from the DDM model to use through the example.
# simulate some data from the model
SEED = 123
obs_ddm = hssm.simulate_data(
theta=dict(v=0.40, a=1.25, t=0.2, z=0.5),
model="ddm",
size=500,
random_state=SEED,
)
Construct PyMC distribution from simulator and JAX callable¶
Create JAX Log-likelihood Callable¶
What we need is a callable that takes in a matrix that stacks model parameters and data trialwise, hence with input dimensions: $trials \times (dim-parameters + dim-data)$. The function should return a vector of length $trials$ that contains the log-likelihood for each trial.
If we have a JAX function with this signature, we will be able to proceed to create a valid PyMC distribution form helper functions provided by hssm.
In this particular example, we will reinstantiate a pretrained LAN via utilities from the lanfactory package. This is not necessary, but illustrates how to make use of the braoder ecosystem around HSSM.
Note:
lanfactoryis an optional dependency ofhssmand can be installed using the commandpip install hssm[notebook]. Alternatively, you can install it directly withpip install lanfactory.
# Loaded Net
jax_infer = lf.trainers.JaxMLPFactory(
network_config=pickle.load(
open(
os.path.join(
"data", "jax_models", "ddm", "ddm_jax_lan_network_config.pickle"
),
"rb",
)
),
train=False,
)
jax_mlp_forward, _ = jax_infer.make_forward_partial(
seed=42,
input_dim=4 + 2, # n-parameters (v,a,z,t) + n-data (rts and choices)
state=os.path.join("data", "jax_models", "ddm", "ddm_jax_lan_train_state.jax"),
add_jitted=True,
)
Checking the signature of the JAX callable¶
We can test the signature of the JAX callable by passing in a batch of inputs and checking the output shape.
# Testing the signature of the JAX function 2
n_dim_model_parameters = 4
n_dim_data = 2
n_trials = 1
in_ = jnp.zeros((n_trials, n_dim_model_parameters + n_dim_data))
out = jax_mlp_forward(in_)
print(out.shape)
(1, 1)
import jax.numpy as jnp
from hssm.distribution_utils.jax import make_jax_single_trial_logp_from_network_forward
jax_logp = make_jax_single_trial_logp_from_network_forward(
jax_forward_fn=jax_mlp_forward, params_only=False
)
# Test call
jax_logp(
jnp.zeros((2)),
jnp.array([1.0]),
jnp.array([1.5]),
jnp.array([0.5]),
jnp.array([0.3]),
)
Array(-20.01882519, dtype=float64)
Decorate and wrap a simulator¶
The simulator-signature expected by hssm is the following:
A simulator is a callable that takes in a matrix that stacks model parameters and data trialwise, hence with input dimensions: $trials \times (|paramters))$. The function should return a matrix of shape $trials \times |data|$.
We will use the decorate_atomic_simulator() utility to annotate the simulator with necessary metadata we use the hssm_sim_wrapper() function from the ssm-simulators package to make our simulator ready for usage inside a PyTensor RandomVariable later.
If you check the signature of the resulting rv_ready_simulator, you should find no problems shoe-horning your own custon simulator into the corresponding behavior.
decorate_atomic_simulator() is just a Python decorator that you can use around any function.
You can start from any simulator you like, we use the one from the ssm-simulators package for convenience.
from functools import partial
from ssms.basic_simulators.simulator import simulator
from ssms.hssm_support import decorate_atomic_simulator, hssm_sim_wrapper
rv_ready_simulator = partial(
hssm_sim_wrapper, simulator_fun=simulator, model="ddm", n_replicas=1
) # AF-TODO: n_replicas should default to 1 instead of being required
# We decorate the simulator to attach some metadata
# that HSSM can use
decorated_simulator = decorate_atomic_simulator(
model_name="ddm", choices=[-1, 1], obs_dim=2
)(rv_ready_simulator)
Checking the signature of the decorated simulator¶
We can check the signature of the decorated simulator by passing in a batch of parameters and checking the output shape.
decorated_simulator(
theta=np.tile(np.array([1.0, 1.5, 0.5, 0.2]), (10, 1)), random_state=42
)
array([[ 0.93413299, 1. ],
[ 2.03800225, 1. ],
[ 1.91076136, 1. ],
[ 0.94315928, 1. ],
[ 2.32025361, 1. ],
[ 1.7177335 , 1. ],
[ 1.74526691, 1. ],
[ 1.3696779 , 1. ],
[ 3.77424622, 1. ],
[ 0.74013716, -1. ]])
Create valid PyMC distribution¶
We have all ingredients to create a valid PyMC distribution, form a few helper functions provided by hssm at this point.
A valid Distribution will need two ingredients:
- A
RandomVariable(RV) that encodes the simulator and parameter names. - A likelihood function, which is a valid PyTensor
Opthat encodes the log-likelihood of the model.
We will use the make_hssm_rv() helper function to create the RandomVariable and the make_likelihood_callable() helper function to create the likelihood Op.
Finally, the make_distribution() helper function will package everything into a valid PyMC distribution.
NOTE:
There are a few helpful settings which we can use to customize our distribution. We will not cover all details here, however it is worth highlighting the params_is_reg argument in make_likelihood_callable(). This argument is used to tell PyMC whether the parameter is a regression parameter or not. Specifically, if a parameter is treated as a regression, the likelihood function is built to assume that the parameter is passed trial-wise, i.e. as a vector of length n_trials.
Note here, we set all parameters to be non-regression parameters, as we expect the parameters to be passed as single values in the simple PyMC model below.
from hssm.distribution_utils.dist import (
make_distribution,
make_hssm_rv,
make_likelihood_callable,
)
# Step 1: Define a pytensor RandomVariable
CustomRV = make_hssm_rv(
simulator_fun=decorated_simulator, list_params=["v", "a", "z", "t"]
)
# Step 2: Define a likelihood function
logp_jax_op = make_likelihood_callable(
loglik=jax_logp,
loglik_kind="approx_differentiable",
backend="jax",
params_is_reg=[True, False, False, False],
params_only=False,
)
# Step 3: Define a distribution
CustomDistribution = make_distribution(
rv=CustomRV,
loglik=logp_jax_op,
list_params=["v", "a", "z", "t"],
bounds=dict(v=(-3, 3), a=(0.5, 3.0), z=(0.1, 0.9), t=(0, 2.0)),
)
We can now test the distribution by passing it to a simple PyMC model.
from pytensor import tensor as pt
# Test via basic pymc model
with pm.Model() as model_pymc:
v = pm.Normal("v", mu=0, sigma=1)
a = pm.Weibull("a", alpha=2.0, beta=1.2)
z = pm.Beta("z", alpha=10, beta=10)
t = pm.Weibull("t", alpha=1.5, beta=0.5)
# We define `v` as a vector of length `n_trials`
# To conform to the expected signature of the likelihood function
v_det = pm.Deterministic("v_det", v * pt.ones(obs_ddm.shape[0]))
CustomDistribution("custom", observed=obs_ddm.values, v=v_det, a=a, z=z, t=t)
pm.model_to_graphviz(model_pymc)
with model_pymc:
idata = pm.sample(draws=500, tune=500, chains=2, nuts_sampler="numpyro")
/Users/yxu150/HSSM/.venv/lib/python3.13/site-packages/pytensor/gradient.py:1327: FutureWarning: LANLogpOp should implement `pullback` instead of `L_op`/`grad`. Direct `L_op`/`grad` implementations are deprecated and will stop being called in a future version. input_grads = node.op.pullback(inputs, node.outputs, new_output_grads) NUTS[numpyro]: [v, a, z, t]
/Users/yxu150/HSSM/.venv/lib/python3.13/site-packages/pymc/sampling/jax.py:463: UserWarning: There are not enough devices to run parallel chains: expected 2 but got 1. Chains will be drawn sequentially. If you are running MCMC in CPU, consider using `numpyro.set_host_device_count(2)` at the beginning of your program. You can double-check how many devices are available in your system using `jax.local_device_count()`. pmap_numpyro = MCMC(
0%| | 0/1000 [00:00<?, ?it/s]
warmup: 5%|▌ | 50/1000 [00:01<00:20, 45.89it/s, 3 steps of size 1.18e-02. acc. prob=0.74]
warmup: 10%|█ | 100/1000 [00:01<00:13, 68.27it/s, 3 steps of size 1.24e-02. acc. prob=0.76]
warmup: 15%|█▌ | 150/1000 [00:02<00:10, 80.38it/s, 7 steps of size 4.49e-01. acc. prob=0.78]
warmup: 20%|██ | 200/1000 [00:02<00:09, 87.55it/s, 7 steps of size 6.58e-01. acc. prob=0.78]
warmup: 25%|██▌ | 250/1000 [00:02<00:07, 97.72it/s, 15 steps of size 3.59e-01. acc. prob=0.78]
warmup: 30%|███ | 300/1000 [00:03<00:06, 103.17it/s, 7 steps of size 5.44e-01. acc. prob=0.78]
warmup: 35%|███▌ | 350/1000 [00:03<00:05, 109.08it/s, 7 steps of size 3.45e-01. acc. prob=0.78]
warmup: 40%|████ | 400/1000 [00:04<00:05, 116.54it/s, 7 steps of size 4.50e-01. acc. prob=0.79]
warmup: 45%|████▌ | 450/1000 [00:04<00:04, 126.10it/s, 7 steps of size 3.78e-01. acc. prob=0.79]
warmup: 50%|█████ | 500/1000 [00:04<00:03, 127.16it/s, 7 steps of size 4.28e-01. acc. prob=0.79]
sample: 55%|█████▌ | 550/1000 [00:05<00:03, 137.46it/s, 7 steps of size 4.28e-01. acc. prob=0.89]
sample: 60%|██████ | 600/1000 [00:05<00:02, 139.57it/s, 7 steps of size 4.28e-01. acc. prob=0.87]
sample: 65%|██████▌ | 650/1000 [00:05<00:02, 138.84it/s, 7 steps of size 4.28e-01. acc. prob=0.87]
sample: 70%|███████ | 700/1000 [00:06<00:02, 140.32it/s, 7 steps of size 4.28e-01. acc. prob=0.88]
sample: 75%|███████▌ | 750/1000 [00:06<00:01, 139.30it/s, 15 steps of size 4.28e-01. acc. prob=0.88]
sample: 80%|████████ | 800/1000 [00:06<00:01, 143.12it/s, 7 steps of size 4.28e-01. acc. prob=0.88]
sample: 85%|████████▌ | 850/1000 [00:07<00:01, 142.89it/s, 15 steps of size 4.28e-01. acc. prob=0.88]
sample: 90%|█████████ | 900/1000 [00:07<00:00, 142.14it/s, 7 steps of size 4.28e-01. acc. prob=0.88]
sample: 95%|█████████▌| 950/1000 [00:07<00:00, 144.53it/s, 7 steps of size 4.28e-01. acc. prob=0.88]
sample: 100%|██████████| 1000/1000 [00:08<00:00, 144.03it/s, 7 steps of size 4.28e-01. acc. prob=0.87]
sample: 100%|██████████| 1000/1000 [00:08<00:00, 120.31it/s, 7 steps of size 4.28e-01. acc. prob=0.87]
0%| | 0/1000 [00:00<?, ?it/s]
warmup: 5%|▌ | 50/1000 [00:00<00:05, 158.75it/s, 2 steps of size 8.97e-06. acc. prob=0.68]
warmup: 10%|█ | 100/1000 [00:00<00:06, 132.72it/s, 4 steps of size 1.60e-05. acc. prob=0.73]
warmup: 15%|█▌ | 150/1000 [00:01<00:11, 73.55it/s, 3 steps of size 4.52e-04. acc. prob=0.75]
warmup: 20%|██ | 200/1000 [00:02<00:12, 66.04it/s, 11 steps of size 3.77e-03. acc. prob=0.77]
warmup: 25%|██▌ | 250/1000 [00:03<00:09, 80.13it/s, 6 steps of size 8.18e-04. acc. prob=0.77]
warmup: 30%|███ | 300/1000 [00:04<00:10, 63.74it/s, 50 steps of size 2.22e-04. acc. prob=0.77]
warmup: 35%|███▌ | 350/1000 [00:04<00:08, 73.39it/s, 6 steps of size 4.14e-03. acc. prob=0.78]
warmup: 40%|████ | 400/1000 [00:04<00:06, 86.05it/s, 9 steps of size 2.16e-03. acc. prob=0.78]
warmup: 45%|████▌ | 450/1000 [00:05<00:05, 98.65it/s, 4 steps of size 1.14e-04. acc. prob=0.78]
warmup: 50%|█████ | 500/1000 [00:05<00:04, 103.84it/s, 3 steps of size 3.45e-03. acc. prob=0.78]
sample: 55%|█████▌ | 550/1000 [00:05<00:03, 124.52it/s, 8 steps of size 3.45e-03. acc. prob=0.57]
sample: 60%|██████ | 600/1000 [00:06<00:03, 125.29it/s, 1 steps of size 3.45e-03. acc. prob=0.63]
sample: 65%|██████▌ | 650/1000 [00:06<00:02, 131.42it/s, 7 steps of size 3.45e-03. acc. prob=0.66]
sample: 70%|███████ | 700/1000 [00:07<00:02, 122.78it/s, 13 steps of size 3.45e-03. acc. prob=0.71]
sample: 75%|███████▌ | 750/1000 [00:07<00:02, 118.85it/s, 9 steps of size 3.45e-03. acc. prob=0.74]
sample: 80%|████████ | 800/1000 [00:07<00:01, 139.72it/s, 16 steps of size 3.45e-03. acc. prob=0.71]
sample: 85%|████████▌ | 850/1000 [00:08<00:01, 135.49it/s, 6 steps of size 3.45e-03. acc. prob=0.72]
sample: 90%|█████████ | 900/1000 [00:08<00:00, 132.63it/s, 6 steps of size 3.45e-03. acc. prob=0.73]
sample: 95%|█████████▌| 950/1000 [00:09<00:00, 106.45it/s, 15 steps of size 3.45e-03. acc. prob=0.75]
sample: 100%|██████████| 1000/1000 [00:09<00:00, 95.16it/s, 15 steps of size 3.45e-03. acc. prob=0.76]
sample: 100%|██████████| 1000/1000 [00:09<00:00, 100.45it/s, 15 steps of size 3.45e-03. acc. prob=0.76]
There were 494 divergences after tuning. Increase `target_accept` or reparameterize.
We recommend running at least 4 chains for robust computation of convergence diagnostics
The rhat statistic is larger than 1.01 for some parameters. This indicates problems during sampling. See https://arxiv.org/abs/1903.08008 for details
The effective sample size per chain is smaller than 100 for some parameters. A higher number is needed for reliable rhat and ess computation. See https://arxiv.org/abs/1903.08008 for details
az.summary(idata)
| mean | sd | eti89_lb | eti89_ub | ess_bulk | ess_tail | r_hat | mcse_mean | mcse_sd | |
|---|---|---|---|---|---|---|---|---|---|
| v | 0.4 | 0.13 | 0.24 | 0.53 | 2 | 6 | 2.20 | 0.093 | 0.01 |
| a | 2 | 0.86 | 1.2 | 3 | 2 | 18 | 1.85 | 0.6 | 0.014 |
| z | 0.6 | 0.1 | 0.44 | 0.65 | 2 | 5 | 2.23 | 0.067 | 0.0054 |
| t | 0.21 | 0.02 | 0.19 | 0.25 | 4 | 21 | 1.93 | 0.012 | 0.0084 |
| v_det[0] | 0.4 | 0.13 | 0.24 | 0.53 | 2 | 6 | 2.20 | 0.093 | 0.01 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| v_det[495] | 0.4 | 0.13 | 0.24 | 0.53 | 2 | 6 | 2.20 | 0.093 | 0.01 |
| v_det[496] | 0.4 | 0.13 | 0.24 | 0.53 | 2 | 6 | 2.20 | 0.093 | 0.01 |
| v_det[497] | 0.4 | 0.13 | 0.24 | 0.53 | 2 | 6 | 2.20 | 0.093 | 0.01 |
| v_det[498] | 0.4 | 0.13 | 0.24 | 0.53 | 2 | 6 | 2.20 | 0.093 | 0.01 |
| v_det[499] | 0.4 | 0.13 | 0.24 | 0.53 | 2 | 6 | 2.20 | 0.093 | 0.01 |
504 rows × 9 columns
az.plot_trace_dist(idata);
Custom HSSM model from simulator and JAX callable¶
Next, we will create a custom HSSM model from the simulator and JAX callable. After the work we have done above, this is now very straightforward.
The only hssm specific extra step is to define a ModelConfig object,
which bundles all information about the model.
Then we pass our ModelConfig object to the HSSM class, along with the data and the log-likelihood function,
and hssm will take care of the rest.
Importantly, hssm will automatically understand how to construct the correct likelihood function
for the specified model configuration (parameter-wise regression settings, etc.). A step we have
to accomplish manually in the code above.
# Define model config
my_custom_model_config = ModelConfig(
response=["rt", "response"],
list_params=["v", "a", "z", "t"],
bounds={
"v": (-2.5, 2.5),
"a": (1.0, 3.0),
"z": (0.0, 0.9),
"t": (0.001, 2),
},
rv=decorated_simulator,
backend="jax",
choices=[-1, 1],
)
# Define the HSSM model
model = hssm.HSSM(
data=obs_ddm,
model="my_new_model", # some name for the model
model_config=my_custom_model_config,
loglik_kind="approx_differentiable", # use the blackbox loglik
loglik=jax_logp,
p_outlier=0,
)
model.graph()
Model initialized successfully.
# Test sampling
model.sample(
draws=500,
tune=200,
sampler="numpyro",
chains=2,
cores=2,
discard_tuned_samples=False,
)
Using default initvals.
/Users/yxu150/HSSM/.venv/lib/python3.13/site-packages/pytensor/gradient.py:1327: FutureWarning: LANLogpOp should implement `pullback` instead of `L_op`/`grad`. Direct `L_op`/`grad` implementations are deprecated and will stop being called in a future version. input_grads = node.op.pullback(inputs, node.outputs, new_output_grads) /Users/yxu150/HSSM/.venv/lib/python3.13/site-packages/bambi/backend/pymc.py:224: UserWarning: `init='adapt_diag'` is ignored by `nuts_sampler='numpyro'`; the external sampler uses its own initialization. idata = pm.sample( NUTS[numpyro]: [t, z, a, v]
/Users/yxu150/HSSM/.venv/lib/python3.13/site-packages/pymc/sampling/jax.py:463: UserWarning: There are not enough devices to run parallel chains: expected 2 but got 1. Chains will be drawn sequentially. If you are running MCMC in CPU, consider using `numpyro.set_host_device_count(2)` at the beginning of your program. You can double-check how many devices are available in your system using `jax.local_device_count()`. pmap_numpyro = MCMC(
0%| | 0/700 [00:00<?, ?it/s]
warmup: 5%|▌ | 35/700 [00:00<00:16, 39.88it/s, 35 steps of size 3.13e-02. acc. prob=0.74]
warmup: 10%|█ | 70/700 [00:01<00:12, 51.76it/s, 31 steps of size 1.92e-02. acc. prob=0.76]
warmup: 15%|█▌ | 105/700 [00:02<00:10, 54.70it/s, 15 steps of size 4.89e-01. acc. prob=0.77]
warmup: 20%|██ | 140/700 [00:02<00:08, 69.26it/s, 31 steps of size 1.03e-01. acc. prob=0.77]
warmup: 25%|██▌ | 175/700 [00:02<00:06, 79.03it/s, 3 steps of size 1.03e+00. acc. prob=0.78]
sample: 30%|███ | 210/700 [00:02<00:05, 91.43it/s, 7 steps of size 4.00e-01. acc. prob=0.77]
sample: 35%|███▌ | 245/700 [00:03<00:04, 103.67it/s, 15 steps of size 4.00e-01. acc. prob=0.85]
sample: 40%|████ | 280/700 [00:03<00:03, 117.42it/s, 7 steps of size 4.00e-01. acc. prob=0.84]
sample: 45%|████▌ | 315/700 [00:03<00:03, 124.88it/s, 7 steps of size 4.00e-01. acc. prob=0.86]
sample: 50%|█████ | 350/700 [00:03<00:02, 132.98it/s, 3 steps of size 4.00e-01. acc. prob=0.86]
sample: 55%|█████▌ | 385/700 [00:04<00:02, 138.87it/s, 7 steps of size 4.00e-01. acc. prob=0.85]
sample: 60%|██████ | 420/700 [00:04<00:02, 136.52it/s, 7 steps of size 4.00e-01. acc. prob=0.85]
sample: 65%|██████▌ | 455/700 [00:04<00:01, 142.09it/s, 7 steps of size 4.00e-01. acc. prob=0.85]
sample: 70%|███████ | 490/700 [00:04<00:01, 147.63it/s, 3 steps of size 4.00e-01. acc. prob=0.84]
sample: 75%|███████▌ | 525/700 [00:04<00:01, 154.22it/s, 7 steps of size 4.00e-01. acc. prob=0.84]
sample: 80%|████████ | 560/700 [00:05<00:00, 155.97it/s, 3 steps of size 4.00e-01. acc. prob=0.85]
sample: 85%|████████▌ | 595/700 [00:05<00:00, 159.13it/s, 7 steps of size 4.00e-01. acc. prob=0.84]
sample: 90%|█████████ | 630/700 [00:05<00:00, 159.49it/s, 7 steps of size 4.00e-01. acc. prob=0.84]
sample: 95%|█████████▌| 665/700 [00:05<00:00, 159.17it/s, 7 steps of size 4.00e-01. acc. prob=0.84]
sample: 100%|██████████| 700/700 [00:06<00:00, 160.32it/s, 7 steps of size 4.00e-01. acc. prob=0.84]
sample: 100%|██████████| 700/700 [00:06<00:00, 115.73it/s, 7 steps of size 4.00e-01. acc. prob=0.84]
0%| | 0/700 [00:00<?, ?it/s]
warmup: 5%|▌ | 35/700 [00:00<00:11, 60.32it/s, 15 steps of size 1.71e-02. acc. prob=0.73]
warmup: 10%|█ | 70/700 [00:01<00:09, 63.56it/s, 15 steps of size 3.02e-02. acc. prob=0.76]
warmup: 15%|█▌ | 105/700 [00:01<00:09, 59.63it/s, 19 steps of size 6.24e-01. acc. prob=0.77]
warmup: 20%|██ | 140/700 [00:02<00:07, 71.89it/s, 15 steps of size 9.06e-01. acc. prob=0.78]
warmup: 25%|██▌ | 175/700 [00:02<00:06, 85.45it/s, 7 steps of size 3.81e-01. acc. prob=0.78]
sample: 30%|███ | 210/700 [00:02<00:04, 99.85it/s, 3 steps of size 5.91e-01. acc. prob=0.86]
sample: 35%|███▌ | 245/700 [00:02<00:03, 114.14it/s, 7 steps of size 5.91e-01. acc. prob=0.82]
sample: 40%|████ | 280/700 [00:03<00:03, 122.02it/s, 7 steps of size 5.91e-01. acc. prob=0.84]
sample: 45%|████▌ | 315/700 [00:03<00:02, 129.39it/s, 3 steps of size 5.91e-01. acc. prob=0.83]
sample: 50%|█████ | 350/700 [00:03<00:02, 137.02it/s, 7 steps of size 5.91e-01. acc. prob=0.83]
sample: 55%|█████▌ | 385/700 [00:03<00:02, 146.32it/s, 7 steps of size 5.91e-01. acc. prob=0.84]
sample: 60%|██████ | 420/700 [00:03<00:01, 145.68it/s, 7 steps of size 5.91e-01. acc. prob=0.85]
sample: 65%|██████▌ | 455/700 [00:04<00:01, 151.31it/s, 7 steps of size 5.91e-01. acc. prob=0.85]
sample: 70%|███████ | 490/700 [00:04<00:01, 159.16it/s, 7 steps of size 5.91e-01. acc. prob=0.85]
sample: 75%|███████▌ | 525/700 [00:04<00:01, 155.97it/s, 3 steps of size 5.91e-01. acc. prob=0.85]
sample: 80%|████████ | 560/700 [00:04<00:00, 161.72it/s, 7 steps of size 5.91e-01. acc. prob=0.86]
sample: 85%|████████▌ | 595/700 [00:05<00:00, 159.12it/s, 7 steps of size 5.91e-01. acc. prob=0.85]
sample: 90%|█████████ | 630/700 [00:05<00:00, 160.39it/s, 15 steps of size 5.91e-01. acc. prob=0.85]
sample: 95%|█████████▌| 665/700 [00:05<00:00, 158.43it/s, 7 steps of size 5.91e-01. acc. prob=0.85]
sample: 100%|██████████| 700/700 [00:05<00:00, 157.27it/s, 7 steps of size 5.91e-01. acc. prob=0.85]
sample: 100%|██████████| 700/700 [00:05<00:00, 123.29it/s, 7 steps of size 5.91e-01. acc. prob=0.85]
We recommend running at least 4 chains for robust computation of convergence diagnostics
/Users/yxu150/HSSM/.venv/lib/python3.13/site-packages/pytensor/link/numba/dispatch/basic.py:214: UserWarning: Numba will use object mode to run LANLogpOp's perform method. Set `pytensor.config.compiler_verbose = True` to see more details. warnings.warn(
<xarray.DataTree>
Group: /
├── Group: /posterior
│ Dimensions: (chain: 2, draw: 500)
│ Coordinates:
│ * chain (chain) int64 16B 0 1
│ * draw (draw) int64 4kB 0 1 2 3 4 5 6 7 ... 493 494 495 496 497 498 499
│ Data variables:
│ t (chain, draw) float64 8kB ...
│ v (chain, draw) float64 8kB ...
│ z (chain, draw) float64 8kB ...
│ a (chain, draw) float64 8kB ...
│ Attributes:
│ created_at: 2026-06-29T19:06:05.771427+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.1.0
│ creation_library_language: Python
│ sample_dims: ['chain', 'draw']
│ inference_library: numpyro
│ inference_library_version: 0.21.0
│ sampling_time: 11.820039
│ tuning_steps: 200
│ modeling_interface: bambi
│ modeling_interface_version: 0.18.0
├── Group: /sample_stats
│ Dimensions: (chain: 2, draw: 500)
│ Coordinates:
│ * chain (chain) int64 16B 0 1
│ * draw (draw) int64 4kB 0 1 2 3 4 5 6 ... 494 495 496 497 498 499
│ Data variables:
│ acceptance_rate (chain, draw) float64 8kB ...
│ step_size (chain, draw) float64 8kB ...
│ diverging (chain, draw) bool 1kB ...
│ energy (chain, draw) float64 8kB ...
│ n_steps (chain, draw) int64 8kB ...
│ tree_depth (chain, draw) int64 8kB 2 3 3 3 3 3 3 2 ... 3 3 2 3 3 4 3 3
│ lp (chain, draw) float64 8kB ...
│ Attributes:
│ created_at: 2026-06-29T19:06:05.774351+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.1.0
│ creation_library_language: Python
│ sample_dims: ['chain', 'draw']
│ modeling_interface: bambi
│ modeling_interface_version: 0.18.0
├── Group: /observed_data
│ Dimensions: (__obs__: 500, rt,response_extra_dim_0: 2)
│ Coordinates:
│ * __obs__ (__obs__) int64 4kB 0 1 2 3 4 ... 496 497 498 499
│ * 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 8kB 0...
│ Attributes:
│ created_at: 2026-06-29T19:06:05.774885+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.1.0
│ creation_library_language: Python
│ sample_dims: []
│ modeling_interface: bambi
│ modeling_interface_version: 0.18.0
├── Group: /constant_data
│ Attributes:
│ created_at: 2026-06-29T19:06:05.774958+00:00
│ creation_library: ArviZ
│ creation_library_version: 1.1.0
│ creation_library_language: Python
│ sample_dims: []
│ modeling_interface: bambi
│ modeling_interface_version: 0.18.0
└── Group: /log_likelihood
Dimensions: (chain: 2, draw: 500, __obs__: 500)
Coordinates:
* chain (chain) int64 16B 0 1
* draw (draw) int64 4kB 0 1 2 3 4 5 6 ... 493 494 495 496 497 498 499
* __obs__ (__obs__) int64 4kB 0 1 2 3 4 5 6 ... 494 495 496 497 498 499
Data variables:
rt,response (chain, draw, __obs__) float64 4MB -0.7752 -1.273 ... -2.116
Attributes:
modeling_interface: bambi
modeling_interface_version: 0.18.0az.plot_trace_dist(model.traces);
az.plot_pair(model.traces);
We hope you find it easy to use the above example to leverage hssm to fit your own custom models.