Getting started#

Start with PyIBS Example 1: Basic usage and calibration for a worked introduction. The summary below shows how to create an estimate and use it for model fitting.

Estimate a log-likelihood#

To estimate a model’s log-likelihood:

  1. Write a simulator that draws one response for each requested trial.

  2. Supply the observed responses and each trial’s design, such as its stimulus.

  3. Create an IBS object and call it with a parameter vector. For fitting, wrap this call as the target of an optimizer or inference method.

  4. Check the estimates and the fit.

This complete example generates 600 trials from the included orientation-discrimination model and compares an IBS estimate with its exact negative log-likelihood:

import numpy as np

from pyibs import IBS
from pyibs.examples.psycho_model import psycho_generator, psycho_neg_logl

# The parameters are (log(sigma), bias, lapse).
rng = np.random.default_rng(20261009)
S = 3 * rng.standard_normal(600)  # the orientation of each trial's stimulus
theta = np.array([0.0, 0.2, 0.03])
R = psycho_generator(theta, S, rng)  # the responses, 1 or -1

ibs = IBS(psycho_generator, R, S, vectorized=True, random_seed=1)
neg_logl, sd = ibs(theta, num_reps=100, additional_output="std")
exact = psycho_neg_logl(theta, S, R)
print(f"IBS: {neg_logl:.1f} +/- {sd:.1f}; exact: {exact:.1f}")

To use your own model, pass these inputs to IBS:

  • sample_from_model: a simulator called as sample_from_model(params, design_rows). If it has an rng parameter that accepts a keyword, PyIBS also passes its random number generator as rng=rng. Return an independent simulated response for each requested row; a trial may be requested several times in one call.

  • response_matrix: the observed responses, one row per trial.

  • design_matrix (optional): the design of each trial, one row per trial. The simulator receives the rows requested by IBS. When no design is supplied, it receives the trials’ 0-based indices instead.

ibs(params) returns the negative log-likelihood estimate, averaging 10 independent IBS repeats by default. Set num_reps to change this number. With additional_output="std", the call also returns an estimated standard deviation, which measures variation between IBS calls at the same parameters; it is not uncertainty in the parameters. With "full", it returns an EstimateResult containing per-trial estimates and the cost of the call. The IBS reference describes all settings, including the likelihood threshold neg_logl_threshold.

Choose a sampling schedule#

The code above sets vectorized=True, which requests batches of responses, because psycho_generator returns many responses cheaply in one call. By default (vectorized=None), IBS times your simulator once and, if simulating is slow, requests one response per trial and call, which wastes no samples; see Should I set vectorized?.

With PyBADS and PyVBMC#

The fitting snippets below are templates. Supply a starting parameter vector x0, hard bounds lb and ub, and plausible bounds plb and pub; PyVBMC also needs a prior. Example 2 and Example 3 define these inputs for the orientation-discrimination model.

For PyBADS, which minimizes its target, return the negative log-likelihood and its estimated standard deviation:

from pybads import BADS

def target(theta):
    return ibs(theta, num_reps=100, additional_output="std")

bads = BADS(
    target, x0, lb, ub, plb, pub,
    options={"specify_target_noise": True, "random_seed": 2},
)
result = bads.optimize()

For PyVBMC, return the log-likelihood (return_positive=True) and its estimated standard deviation. When you supply prior, PyVBMC adds the log prior itself:

from pyvbmc import VBMC

def log_likelihood(theta):
    return ibs(
        theta, num_reps=100, additional_output="std", return_positive=True
    )

vbmc = VBMC(
    log_likelihood, x0, lb, ub, plb, pub,
    prior=prior, options={"specify_target_noise": True}, seed=2,
)
vp, results = vbmc.optimize()

Choose num_reps for a standard deviation of about 1 near the optimum. PyBADS works best with noise of 1 or less there; PyVBMC works best with about 1, and not much more than 3, in the region containing most posterior mass. For this model, 100 repeats give an SD of about 1.1 on 600 trials.

For reproducible runs, seed both PyIBS and the fitting method, and have the simulator draw from the rng it receives. Set vectorized explicitly and leave the timing-dependent settings max_time and acceleration_threshold at their defaults. The examples above use separate seeds for IBS and the fit; the FAQ explains when a seed reproduces a run.

Examples and FAQ#

The example notebooks form a short tutorial: basic use and checks of the uncertainty estimates, maximum-likelihood fitting with PyBADS, and posterior and model-evidence estimation with PyVBMC.

The FAQ explains how to write a simulator, choose num_reps and the likelihood threshold, and troubleshoot a failed call.