Skip to content

v0.26.0 — Nimble Narwhal

Latest

Choose a tag to compare

@lenarddome lenarddome released this 16 Oct 12:39
ecfa900

v0.26.0 makes cpm much faster and adds new models and data. The built-in applications are refactored to improve runtime. With the new optional numba backend (pip install "cpm-toolbox[numba]") they run 90–220 times faster, and 11–23 times faster without it, with the same results. EmpiricalBayes and VariationalBayes have new options (hessian="finite_differences", start="prior_mean", number_of_starts=1) that bring them to about cbm's speed. The release also adds the hybrid model-based/model-free model of the two-step task, a SARSA rule with an eligibility trace, cpm.brainexplorer, SessionWrapper for writing fast custom models, and new datasets and examples. The documentation has been rebuilt with Sphinx, and the release fixes a long list of bugs. Thanks to @lucakosina, @aslansd and @chotong for their contributions to this release.

Note

Good to know when upgrading. A few fixes mean some fits now give different, more correct numbers than in 0.25.x:

  • EmpiricalBayes and VariationalBayes now keep the estimated population priors throughout each fit.
  • Parallel fits with prior=True now use priors updated with update_prior, so they match serial fits.
  • With three or more starts, Fmin, FminBound, Minimize and Bads now always keep each participant's best fit.
  • Value(prior="uniform") now spans [lower, upper].

The new speed options are opt-in, so the defaults give the same results as before for the same seed. cpm now requires Python 3.11 or later.

Added

  • Added test units for cpm.hierarchical.EmpiricalBayes and cpm.hierarchical.VariationalBayes
  • Added number_of_starts and initial_guess_supplied attributes to all optimisers, recording how many starts a fit used and whether the initial guesses were supplied (#83)
  • Added test units for the new optimiser attributes, and a smoke test that runs a real cpm.optimisation.Bads fit
  • Added log_likelihood and log_prior to the output of all optimisers fitted with prior=True (#28)
  • Added a metrics argument to all optimisers, for goodness-of-fit metrics such as PenalisedLikelihoods.BIC evaluated at the optimum (#28)
  • Added cpm.applications.reinforcement_learning.HybridMBMF, the hybrid model-based / model-free model of the two-step task (Kool et al., 2016; Smid et al., 2022)
  • Added cpm.models.learning.SARSATrace, a SARSA learning rule with an eligibility trace
  • Added test units for HybridMBMF and SARSATrace
  • Added a two-step task example replicating Smid et al. (2022), with its data in cpm.datasets.load_two_step_data
  • Added cpm.brainexplorer, which computes descriptive statistics and applies the exclusion criteria for the BrainExplorer games Space Observer, Scavenger, Treasure Hunt, and Milky Way and Pirate Market, with a page in the API reference
  • Added test units for cpm.brainexplorer
  • Added cpm.datasets.load_model_based_model_free, two-step task data from the BrainExplorer game Goblin Heist
  • Added an example that fits causal ratings in a blocking experiment, recreating Figures 1 and 2 of Spicer et al. (2021), with its data in cpm.datasets.load_blocking_data
  • Added cpm.generators.SessionWrapper, a Wrapper for models that compute all trials of a participant at once
  • Added cpm.models.kernels, the formulas of the cpm.models classes as functions that numba can compile, used by the classes and the built-in applications
  • Added numba as an optional dependency (pip install "cpm-toolbox[numba]") that compiles the built-in applications; without it, they give the same results as plain Python
  • Added the numba install option to the installation guide, a how-to guide on numba, and troubleshooting entries
  • Added a how-to guide on speeding up your own model with SessionWrapper, with a test that runs its example
  • Added test units for SessionWrapper, cpm.models.kernels, the fast priors, and the built-in applications with and without numba
  • Added a benchmark suite (benchmarks/run.py for one evaluation, benchmarks/hierarchical.py for the hierarchical tutorials) and scripts/local_tests.py, which runs the tests with and without numba
  • Added a GitHub Actions workflow that runs the tests with and without numba, on Python 3.11 to 3.14
  • Added benchmarks/hierarchical_profile.py, which shows where the time of a VariationalBayes fit goes and counts the objective evaluations per participant and iteration; against cbm (RLRW, 100 participants, 120 trials), cpm makes about as many evaluations but each one costs more, at 17.3 against 5.0 ms per participant and iteration
  • Added a hessian argument to cpm.optimisation.FminBound, Fmin, Minimize and Bads; hessian="finite_differences" computes the Hessian at the optimum with bounded finite differences (cpm.core.optimisers.finite_difference_hessian), cutting Hessian evaluations from 22 to 7 per participant and iteration in the speed comparison. The default, "numdifftools", keeps the previous Hessian
  • Added a start argument to cpm.hierarchical.EmpiricalBayes and cpm.hierarchical.VariationalBayes; start="prior_mean" starts the first optimisation of every participant from the current prior's location, clipped into the bounds, as cbm does, so fits stop sooner. The default, "random", keeps the previous starts
  • Added a positive_definite column to the fit of cpm.hierarchical.EmpiricalBayes and cpm.hierarchical.VariationalBayes, recording whether each participant's Hessian was positive definite, and a not_positive_definite column to their hyperparameters with the count; an estimate on a bound can have a non-positive-definite Hessian and is kept, as the bounds require
  • Added the results of the speed comparison with cbm (100 participants, 120 trials) in benchmarks/results/hierarchical-comparison-*.csv: combining hessian="finite_differences", start="prior_mean" and number_of_starts=1 brings EmpiricalBayes/VariationalBayes from 20.8/20.1 to 6.8/7.0 ms per participant and iteration, against cbm's 4.4 ms, with RMSE against 0.26.0.dev1 staying within bootstrap 95% intervals throughout

Changed

  • Rebuilt the documentation with Sphinx and the PyData theme, with a separate API reference, tutorials, examples and how-to guides; pages of the old site redirect
  • Converted all docstrings to reStructuredText (NumPy style), and fixed incorrect examples and a SyntaxWarning from LaTeX in docstrings
  • Restricted package discovery to cpm, and added docs and notebooks optional dependencies
  • Sped up long and multi-chain runs of cpm.hierarchical.EmpiricalBayes and cpm.hierarchical.VariationalBayes
  • Made every model cheaper to evaluate: copies of a cpm.generators.Value share their prior, and the built-in priors are evaluated without scipy overhead, with identical results
  • cpm.generators.Value.update_prior() now replaces the prior instead of changing it in place; changing value.prior.kwds directly now affects every copy of the Value
  • cpm.generators.Wrapper.reset() now keeps the current priors and bounds of the parameters, instead of restoring the ones the model was created with
  • Made the built-in applications (RLRW, HybridMBMF, PTSM, PTSM1992, PTSM2025) compute all trials at once: 90-220 times faster with numba, 11-23 times without, with the same interface and results
  • Made per-trial cpm.generators.Wrapper models about twice as fast, by reading trials without DataFrame.iloc
  • Made the classes in cpm.models faster, up to 17 times for large inputs, with identical results
  • Made LogLikelihood.bernoulli and LogLikelihood.continuous 2.5-3 times faster, with identical results
  • Made cpm.applications.signal_detection.EstimatorMetaD about 10 times faster, with identical results
  • cpm.models.decision.Softmax no longer overflows: it returns the correct policy where it returned NaN and warned
  • cpm.models.activation.ProspectUtility.weights and .utilities are float arrays instead of object arrays, and the simulation records of HybridMBMF hold NumPy scalars instead of Python numbers
  • PTSM2025 always uses its power utility: replacing parameters.utility_curvature on a model no longer changes it
  • Required Python 3.11 or later (previously >3.11.0, which excluded 3.11.0), removed the PyPy classifier, and tagged the wheel for Python 3 only
  • cpm.optimisation.FminBound with approx_grad=True now computes the finite-difference gradient itself, together with the objective, exactly as SciPy does, with identical results; with LogLikelihood.bernoulli, this makes the hierarchical fits of the speed comparison 19% faster
  • The defaults of the new arguments, hessian="numdifftools" and start="random", give the same results as before for the same seed; the faster options have to be chosen

Fixed

  • Fixed the optimisers and cpm.applications.signal_detection.EstimatorMetaD raising UnboundLocalError for a pandas DataFrame without ppt_identifier; they now raise a ValueError that explains what to pass, or a KeyError that lists the columns if ppt_identifier is not one of them, and take ppt_identifier from the grouping of data grouped by a single column, such as data.groupby("ppt"), so that the fits record the participants
  • Fixed cpm.hierarchical.EmpiricalBayes and cpm.hierarchical.VariationalBayes discarding the estimated population priors after the first evaluation of each fit; hierarchical results change
  • Fixed the random starting priors of later chains of EmpiricalBayes and VariationalBayes being length-one arrays
  • Fixed numpy.random.seed not reproducing the later chains of EmpiricalBayes and VariationalBayes, whose starting priors now also respect the lower bound and work with infinite bounds
  • Fixed cpm.optimisation.Bads emitting a DeprecationWarning on every GP fit (#88)
  • Fixed cpm.optimisation.Bads failing under NumPy 2, by requiring gpyreg>=1.2.1
  • Fixed installs with pandas, matplotlib or pybads releases that do not work with NumPy 2, by requiring pandas>=2.2.2, matplotlib>=3.8.4 and pybads>=1.0.5
  • Fixed cpm.optimisation.FminBound raising TypeError on SciPy 1.18.0 and later
  • Fixed cpm.hierarchical.VariationalBayes.ttest raising NameError when null is a pandas.DataFrame
  • Fixed cpm.hierarchical.VariationalBayes.lmes repeating the final list of model evidences for every iteration
  • Removed a dead, always-zero mean_errorbar column from cpm.hierarchical.VariationalBayes.hyperparameters
  • Fixed cpm.generators.Value with prior="uniform" spanning [lower, lower + upper] instead of [lower, upper]
  • Fixed cpm.generators.Parameters.sample() raising AttributeError for a parameter that is None
  • Fixed callables passed to cpm.generators.Parameters leaking onto every other Parameters instance
  • Fixed cpm.models.learning.SeparableRule.error never being filled, which made noisy_learning_rule() a no-op, and the error of DeltaRule and SeparableRule having the wrong shape for 1D weights
  • Fixed cpm.generators.Wrapper.reset() misassigning an array of parameter values when a state is declared before a free parameter
  • Fixed numpy.asarray(value, dtype=...) raising TypeError for a cpm.generators.Value
  • Fixed cpm.models.activation.ProspectUtility with weighting="prelec" failing for options with several outcomes
  • Fixed cpm.applications.decision_making.PTSM2025 stopping a fit with ValueError when its exponentials overflowed
  • Fixed cpm.models.learning.HumbleTeacher raising IndexError for 1D weights
  • Fixed cpm.applications.reinforcement_learning.RLRW not returning the choices it simulates with generate=True; it now outputs response (#100)
  • Fixed cpm.generators.Simulator not raising its intended TypeError for an ungrouped pandas.DataFrame
  • Fixed cpm.generators.Simulator raising AttributeError for parameters in a pandas.DataFrame with grouped data
  • Fixed cpm.generators.Simulator raising ValueError for a single parameter set given as Parameters, dict or pandas.Series
  • Fixed diagnostics() of EmpiricalBayes and VariationalBayes failing for three or more free parameters; convergence_diagnostics_plots has a new bounds argument
  • Fixed a clean install raising ModuleNotFoundError in diagnostics(), by adding matplotlib to the dependencies
  • Fixed cpm.utils.data and cpm.utils.metad not being reachable after import cpm
  • Fixed cpm.core.diagnostics.gelman_rubin and cpm.core.diagnostics.psrf failing on every call, and gelman_rubin computing the statistic incorrectly
  • Fixed fits with parallel=True and prior=True going back to the priors the model was created with after the first evaluation in every worker, so that priors updated with update_prior, such as the population priors of cpm.hierarchical.EmpiricalBayes and cpm.hierarchical.VariationalBayes, were ignored; parallel fits now equal serial ones, and parallel results with updated priors change. cpm.generators no longer deletes its submodules, which made dill copy its classes into the workers instead of referencing them
  • Fixed Fmin, FminBound, Minimize and Bads comparing every later start with the first instead of the best so far, which with three or more starts could replace a participant's best fit with a worse one; results with three or more starts can change
  • Fixed the cpm.hierarchical.VariationalBayes docstring giving 1000 as the default of iteration, which is 50

Full changelog: 0.25.6...0.26.0