Repository navigation
Migrate to the Bambi rewrite - #1387
Draft
digicosmos86 wants to merge 107 commits into
Draft
digicosmos86 wants to merge 107 commits into
digicosmos86 wants to merge 107 commits into
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR brings HSSM up to the Bambi rewrite (bambinos/bambi#1002). It is large because Bambi changed a lot underneath us, so the notes below explain the most important changes and why they were made.
Tracking issue: #1305.
Summary
Table of contents
inverse_linkmainBambi 0.20 separates its frontend, which describes the model, from its backend, which builds and evaluates the PyMC graph. Prediction, log-likelihood computation, and parameter transforms now run on the PyMC/PyTensor graph instead of being rebuilt with NumPy/xarray. Bambi also renamed its model vocabulary (
components→parameters) and reorganized its modules.HSSM relied on several internals that were removed or moved:
Model._compute_likelihood_params,Family._make_dist_kwargs_and_coords,response_evaluate_new_data,sample_new_groups, and the splitlinkinv/linkinv_backendlinks, among others. This PR replaces each of them with the graph-based paths Bambi now provides. Where Bambi's new behaviour would have changed what HSSM users see, the PR keeps HSSM's public results stable. Where Bambi's new behaviour is more correct, the PR adopts it and documents the change.The work began by marking every test failure under Bambi
devasxfailand grouping them by root cause (design/bambi-dev-upgrade-test-failures.md,design/bambi-migration-fix-plan.md). Each group was then fixed under its own sub-issue and itsxfailmarks removed. No migrationxfailmarks remain.Breaking changes
Custom links take a single
inverse_linkBambi no longer keeps one inverse link for NumPy computations and another for the PyTensor graph (#1311).
hssm.Linkfollows suit:inverse_linkmust work on PyTensor tensors. NumPy ufuncs such asnp.expalready do, because they dispatch to symbolic operations. Readinglink.linkinvorlink.linkinv_backendnow raisesAttributeError. The forwardlinkis optional. The built-in boundedgen_logitexposes the same transformation through the new attribute, so its callers are unaffected.Dimension names follow Bambi 0.20
{factor}_diminstead of{factor}__factor_dim.{var}_dim_reduced.(1|g:h)now gets two dims,g_dimandh_dim, instead of one.rt,response_dimin every group ([Migration] Regenerate the recorded trace fixtures that predate bambi 0.20 (*_Intercept_centered) #1336). Bambi names itrt_dim, andobserved_datapreviously usedrt,response_extra_dim_0.SSMFamilydeclaresRESPONSE_NDIM = 2so Bambi gives the response its correct dims.Code that selects coordinates by dim name must be updated.
Traces must match the model they predict from
sample_posterior_predictivenow raises aValueErrorwhen the posterior is missing a free random variable of the model (#1336). Before, an out-of-sample prediction silently drew the missing variable from its prior. The common case is a trace saved before Bambi 0.20, which has nov_Intercept_centered. Missing*_offsetvariables are allowed when their base term is present. Old traces need to be resampled.Changes to existing functionality
Centered intercepts and initial values
When a parameter has an intercept, at least one common predictor, and
center_predictors=True(the default), Bambi now samples{param}_Intercept_centeredand records the original-scale{param}_Interceptas a deterministic (#1375).az.summaryandplot_tracetherefore show two rows for such an intercept. Both are kept, because Bambi'spredictandcompute_log_likelihoodneed the centered one. The intercept prior means the same thing as before. To report only the usual intercept, use:Initial values follow the free variable:
INITVAL_SETTINGSdefaults and any priorinitvalkeyed on{param}_Interceptare applied to{param}_Intercept_centered([Migration] Let truncated-prior callables accept dims #1312).sample(initvals={"v_Intercept": ...})is moved tov_Intercept_centered, because PyMC silently ignores an initial value on a deterministic ([Migration] Document the *_Intercept_centered / *_Intercept pair and predictor centering #1375). An explicitv_Intercept_centeredkey takes precedence.The new explanation page Predictor centering and the intercept covers this in detail.
Likelihood parameters and out-of-sample log-likelihood
HSSM now evaluates the trial-wise likelihood parameters with
Model.predict(kind="response_params")instead of the removed Bambi internals (#1313). In sample, they are still added toposterior, andkeep_likelihood_params/add_likelihood_parameters_to_datatreebehave as before. Out of sample, Bambi writes them to apredictionsgroup.log_likelihood(data=...)uses that group for its computation and then removes it, so the only change to the trace is the newlog_likelihoodgroup. It no longer overwritesposteriorwith out-of-sample parameters.Rebuilding the response term on new data now goes through
formulae.design_matrices, which replaces Bambi's removedresponse_evaluate_new_data. For that reasonformulae>=0.7.0is now a direct dependency.Out-of-sample posterior predictive
Bambi now stores out-of-sample predictions in
predictionsandpredictions_constant_datainstead ofposterior_predictive(#1315). HSSM moves the response draws back intoposterior_predictive, so in-sample and out-of-sample results keep one layout. This also keeps the plotting helpers working, since they always predict on the data frame they are given. Stale prediction groups are cleared before a new response prediction.kind="response_params"still returns Bambi's result unchanged.Predictions for new or unidentified participants
Bambi retired
sample_new_groups(HSSM always passedFalse) and now decides how to handle each observation from its grouping value (#1316):None,np.nan,pd.NA) means the participant's identity is unknown. The observation borrows the coefficients of a randomly chosen fitted group, which matches the old behaviour.This affects
sample_posterior_predictive(data=...)andlog_likelihood(data=...). Because those coefficients are random,log_likelihoodnow takes arandom_seedto make the result reproducible (#1376). The new how-to guide Predict for new or unidentified participants explains the semantics and shows how to average draws into a trial-level or participant-level predictive score.Trace contents
constant_datagroup with the design-matrix data (for examplev_x_data) and the group index arrays (for exampleg__idx) Bambi used to build the model.p_outlier, or a fixedz) as constant deterministics. These are now dropped fromposterior([Migration] Drop the constant parameters and *_Intercept_centered RVs bambi 0.20 keeps in posterior #1330) and fromprior([Migration] Stop pymc's forward sampler rejecting the aDDM p_outlier TensorConstant #1329). Inpriorthey broke forward sampling, such as the prior-predictive mean inplot_model_cartoon.{parent}_meanalias (e.g.v_mean). Before, it dropped every variable whose name contained_mean, so a covariate such asrt_meanlost its coefficientv_rt_mean([Migration] Fix the _mean substring filter in posterior cleanup and drop dead helpers #1378).Errors for unknown formula variables
A formula that names a column missing from
datanow raises aValueErrorthat names the parameter and the formula (#1317). Before, users saw a bareKeyError/IndexErrorfrom formulae/pandas, and its type had changed across releases.Fixes
vi(backend="jax")works again on every model, including missing-data models ([Migration] Make VI on the JAX compile backend trace bambi 0.20's symbolic __obs__ alloc #1328). Bambi 0.20 keeps the response and the__obs__observation count as shared variables and derives shapes from them, which the JAX linker rejects as dynamic. HSSM now passespm.fitamore_replacementsthat pins these to constants, the same fix PyMC applies in its own JAX samplers. A user-suppliedmore_replacementsstill takes precedence entry by entry, and an explicitNoneis accepted.dimsandshape([Migration] Let truncated-prior callables accept dims #1312). Bambi now instantiates callable priors asdist(name, dims=...)when it builds the centered intercept.plot_model_cartooncasts traces to the model's dtypes before computing deterministics ([Migration] Regenerate the recorded trace fixtures that predate bambi 0.20 (*_Intercept_centered) #1336).Dependency, packaging, and CI changes
bambitracks thedevbranch through[tool.uv.sources]until 0.20 is released. The floor must be pinned to the publishedbambi>=0.20before this ships (see the release plan below).formulae>=0.7.0is now a direct dependency (see above).filterwarningssetting turns Bambi's"X is deprecated. Use Y instead."FutureWarnings into errors, so any deprecated accessor still in use fails CI ([Migration] Migrate deprecated bambi accessors and refresh test doubles #1314). All of HSSM's uses have been migrated:components→parameters,distributional_components→conditional_parameters,response_component.term→response_term.0.6.0.Significant internal changes
base.pyno longer rebuilds likelihood parameters itself. It callsModel.predictand reorganizes the output groups (predictions→posterior_predictive, removing temporary groups).utils.log_likelihoodrebuilds the response withformulae.design_matricesand evaluates the parameters returned by_compute_likelihood_params._vi_compat.pyholds the shared-variable freeze for JAX VI.link.pykeeps a singleinverse_linkpath._clean_posterior_groupdrops every parameter Bambi built as apm.Deterministicexcept*_Intercept_centered._remap_centered_intercept_initvalshandles user initvals passed tosample().Tests
cavanagh_idata.nc,idata_cavanagh_cartoon.nc,test_idata_race.nc) are regenerated for Bambi 0.20 by the newtests/trace_fixtures.py. The staleidata_race_cartoon.ncis removed.tests/test_new_group_predictions.pycovers the unknown-identity and new-group prediction paths, includingrandom_seed.sample(initvals=...), constant parameters dropped fromprior/posterior, the_meancleanup, the trace guard insample_posterior_predictive, JAX VI on missing-data models, and theValueErrorfor unknown formula variables.FutureWarning.xfailmark has been removed.Documentation
link_functionstutorial is migrated toinverse_link, and the initial-values tutorial now usesa_Intercept_centered.scientific_workflow_hssmtraces are regenerated with Bambi 0.20 dim names ([Migration] Refresh notebooks that show pre-0.20 dim and variable names #1377). Machine-local paths are stripped from outputs.random_slope_safe_priorsnow shows HSSM rejecting the legacy free-mean priors undernoncentered=True.tutorial_p_outlier_regressionmodelsvas population terms plus participant deviations, which satisfies the safe-prior guard on boundedv([Migration] Rework random_slope_safe_priors and tutorial_p_outlier_regression for the new prior validation #1382).0.6.0 (unreleased)section covering all of the above.Merge from
mainmainis merged into this branch. The conflicts were resolved as follows:base.pyinitvals: both sides are kept. The branch's_centered-aware lookup (settings_key) is combined with main's clamping of default initvals into declared bounds (Clamp default initial values into the model's declared bounds #1293). A default for*_Intercept_centeredis therefore looked up under*_Interceptand clamped into the parameter's bounds.utils.py: the branch is kept. Main's pyrefly fix (Lift the pyrefly <1.3 cap: 1.3.0 reports 5 new type errors #1334) applied to code the rewrite replaced.pyproject.toml: the branch's0.6.0andpyrefly>=1.3.0are kept. Main capped pyrefly at<1.3, but this branch already fixes those errors. The pyrefly and mypy hooks pass on the merged tree.ValueErrorrather than main'sKeyError(see [Migration] Reconcile two drifted test assertions (formula error type, noncentered message) #1317). Two of main's new tests were adapted to Bambi 0.20:test_identity_link_override_clamps_into_boundsandtest_explicit_log_link_keeps_default_startread the default from{param}_Intercept_centered, sincet ~ 1 + stimnow centers the intercept. The clamped value is unchanged (0.25 + 0.05 * 1.75).test_jax_callable_non_trialwise_params_logp[jax_linker]freezes the model withfreeze_dims_and_databefore compiling under the JAX linker, the same way PyMC's JAX samplers do and for the same reason as [Migration] Make VI on the JAX compile backend trace bambi 0.20's symbolic __obs__ alloc #1328. The test still guards Fixing a parameter with backend='jax' fails with a bare AssertionError in specifyshape #1092.random_slope_safe_priors,tutorial_p_outlier_regression): the branch is kept. Both sides fixed the same failures (Two tutorials fail at construction since the 0.5.0 safe-prior guards (p_outlier regression, random-slope safe priors) #1363 on main, [Migration] Rework random_slope_safe_priors and tutorial_p_outlier_regression for the new prior validation #1382 here), and only the branch's versions were executed against Bambi 0.20.0.6.0 (unreleased)is placed above main's0.5.1.Compatibility and release plan
devbranch via[tool.uv.sources], whichpip install hssmignores. Before 0.6.0 is tagged, remove the git source and pinbambi>=0.20.Linkobjects (linkinv→inverse_link), selects coordinates by dim name, or reads*_Interceptas a free variable. Traces saved before 0.6.0 must be resampled before they can be used for out-of-sample prediction.*_Intercept_centeredvariables, which 0.5.x will not interpret identically.Summary by CodeRabbit
inverse_linkinterface and added 0.6.0 migration notes.