Skip to content

Add from_blackjax converter#174

Open
aman-coder03 wants to merge 9 commits into
arviz-devs:mainfrom
aman-coder03:add-from-blackjax
Open

Add from_blackjax converter#174
aman-coder03 wants to merge 9 commits into
arviz-devs:mainfrom
aman-coder03:add-from-blackjax

Conversation

@aman-coder03

Copy link
Copy Markdown

Closes #165

what this PR does

adds a from_blackjax() converter that turns BlackJAX sampling output into an ArviZ DataTree (InferenceData) object, similar to the existing from_numpyro converter

groups supported

  • posterior from states.position (dict, NamedTuple, or bare array)
  • sample_stats from the BlackJAX info object (NUTS fields renamed to ArviZ standard names)
  • prior / prior_predictive split by whether the variable appears in the posterior
  • posterior_predictive, observed_data, constant_data

design decisions

  • single-chain arrays (n_draws, *event_shape) get a chain dim of 1 inserted automatically
  • multi-chain arrays (n_chains, n_draws, *event_shape) are detected via num_chains
  • JAX arrays are converted to numpy via jax.device_get
  • no adapter class needed...BlackJAX has no unified inference object, users always pass states.position and infos directly
  • log_likelihood is not included...BlackJAX has no model trace to compute it from; open to discussion on whether to accept it as a plain dict in a follow up

notes

  • studied from_numpyro and from_dict before writing this
  • passes mypy, ruff, and all pre-commit hooks
  • 15 tests added in tests/test_io_blackjax.py

@read-the-docs-community

read-the-docs-community Bot commented Mar 16, 2026

Copy link
Copy Markdown

Documentation build overview

📚 arviz-base | 🛠️ Build #32165088 | 📁 Comparing 56fc750 against latest (0dc5042)

  🔍 Preview build  

Show files changed (26 files in total): 📝 25 modified | ➕ 1 added | ➖ 0 deleted
File Status
api/index.html 📝 modified
how_to/ConversionGuideBlackJAX.html ➕ added
how_to/ConversionGuideEmcee.html 📝 modified
how_to/ConversionGuideNumPyro.html 📝 modified
tutorial/WorkingWithDataTree.html 📝 modified
_modules/arviz_base/base.html 📝 modified
_modules/arviz_base/converters.html 📝 modified
_modules/arviz_base/rcparams.html 📝 modified
_modules/arviz_base/reorg.html 📝 modified
api/generated/arviz_base.convert_to_dataset.html 📝 modified
api/generated/arviz_base.convert_to_datatree.html 📝 modified
api/generated/arviz_base.dataset_to_dataarray.html 📝 modified
api/generated/arviz_base.dataset_to_dataframe.html 📝 modified
api/generated/arviz_base.dict_to_dataset.html 📝 modified
api/generated/arviz_base.explode_dataset_dims.html 📝 modified
api/generated/arviz_base.extract.html 📝 modified
api/generated/arviz_base.from_cmdstanpy.html 📝 modified
api/generated/arviz_base.from_dict.html 📝 modified
api/generated/arviz_base.from_emcee.html 📝 modified
api/generated/arviz_base.from_numpyro.html 📝 modified
api/generated/arviz_base.load_arviz_data.html 📝 modified
api/generated/arviz_base.make_attrs.html 📝 modified
api/generated/arviz_base.ndarray_to_dataarray.html 📝 modified
api/generated/arviz_base.references_to_dataset.html 📝 modified
api/generated/arviz_base.xarray_sel_iter.html 📝 modified
api/generated/arviz_base.xarray_var_iter.html 📝 modified

@OriolAbril OriolAbril left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started reviewing but have to leave. Only started skimming the very top of the file and the files being changed. The main thing to point out is tests for converters are in external_tests folder, not tests folder.

Comment thread src/arviz_base/io_blackjax.py Outdated
@requires("_samples")
def posterior_to_xarray(self):
"""Convert posterior samples to an xarray Dataset."""
assert self._samples is not None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert self._samples is not None

The requires decorator will have the method return None without ever callling the method body if the given attribute is None. We already have tests for the decorator and all other converters use them without problem. There is no point in adding these asserts

Comment thread src/arviz_base/io_blackjax.py
@aman-coder03

Copy link
Copy Markdown
Author

removed the assert statements (the @requires decorator handles that), added reached_max_tree_depth via a new max_tree_depth parameter, and moved tests to external_tests @OriolAbril


from .helpers import importorskip

blackjax = importorskip("blackjax")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are not using blackjax at all. Instead of manually creating fixtures with the outputs that are expected from blackjax, those should be generated with blackjax otherwise if something changes in blackjax the tests will continue to pass even though the converter might end up broken

Comment thread external_tests/test_blackjax.py Outdated
def test_from_blackjax_single_chain(single_chain_position, draws):
# single-chain position should get a chain dim of 1 inserted automatically
idata = from_blackjax(posterior=single_chain_position)
fails = check_multiple_attrs({"posterior": ["mu", "tau", "theta"]}, idata)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fails = check_multiple_attrs({"posterior": ["mu", "tau", "theta"]}, idata)
fails = check_multiple_attrs({"posterior": ["mu", "tau", "theta"], "~sample_stats": []}, idata)

check_multiple_attrs can be used to both check something is present or something is not. Here we want to make sure sample_stats is not present because there is no info argument. There is no point in having test_from_blackjax_no_info as a different test, it is doing exactly the same as this one. This extends to several other redundant tests that stem from suboptimal use of check_multiple_attrs

Comment thread src/arviz_base/io_blackjax.py Outdated
Comment on lines +59 to +64
if arr.ndim == 0:
result[name] = arr.reshape(1, 1)
elif num_chains is not None and arr.ndim >= 2 and arr.shape[0] == num_chains:
result[name] = arr
else:
result[name] = np.expand_dims(arr, axis=0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will end up in expand_dims whenever num_chains is not provided. I don't know much about blackjax and know it is not a PPL but a lower level library focused on sampling. However, sampling a single chain is not recommended so I would switch the API (both here and in from_blackjax) to take sample_dims as an input instead of using rc_context to ensure it is always chain and draw like the other samplers. That means by default chain and draw are assumed to be present unless the rcParam is modified or the argument passed explicitly to for example use sample_dims="sample" in which case we wouldn't need to expand the dimensions at all.

IIRC, only the loo related functionality has chain, draw dimensions as a hardcoded requirement in the input so maybe we could add an example around that in the conversion notebook. Something like 1) use sample_dims="draw", 2) use map_over_datasets to call expand_dims(chain=1) on all relevant groups.

Comment thread src/arviz_base/io_blackjax.py Outdated
-------
DataTree

Examples

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you move this to a notebook with the multiple conversion examples like the other converter functions have?

There are several things I am not completely sure about API wise and having proper examples that run as is would be helpful.

@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@aman-coder03

Copy link
Copy Markdown
Author

hey @OriolAbril i have addressed the review comments...

  • real BlackJAX sampling in tests....fixtures now actually run blackjax.nuts with window_adaptation and jax.lax.scan on the eight-schools model, so if BlackJAX changes its output format the tests will catch it. Multi chain tests use jax.vmap
  • check_multiple_attrs cleanup....consolidated presence and absence checks into single calls using the ~ prefix. Removed test_from_blackjax_no_info since it was redundant with the main single-chain test
  • sample_dims parameter....from_blackjax now accepts sample_dims just like from_numpyro does, defaulting to rcParams["data.sample_dims"]. The chain dimension expansion only happens when 'chain' is in sample_dims, so passing sample_dims=["sample"] gives a flat representation with no axis insertion
  • conversion notebook...added docs/source/how_to/ConversionGuideBlackJAX.ipynb following the same structure as the NumPyro guide. It covers single-chain, multi-chain, prior/prior_predictive and includes the LOO compatibility example you mentioned (sample_dims=["sample"] then map_over_datasets + expand_dims to add chain=1)

let me know if anything needs further changes

@aman-coder03

Copy link
Copy Markdown
Author

5 remaining failures are all pre-existing in test_numpyro.py::test_mcmc_with_thinning and are unrelated to this PR. They fail because the CI machine only has 1 JAX device, but the test uses nchains=2 with chain_method="parallel", which triggers a UserWarning that gets treated as an error by pytest. This was already failing on main before my changes. All 18 blackjax tests pass.

@OriolAbril OriolAbril left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only one high level comment regarding the API, I will review in depth after that.

Also do note this is not true:

5 remaining failures are all pre-existing in test_numpyro.py::test_mcmc_with_thinning and are unrelated to this PR. They fail because the CI machine only has 1 JAX device, but the test uses nchains=2 with chain_method="parallel", which triggers a UserWarning that gets treated as an error by pytest. This was already failing on main before my changes. All 18 blackjax tests pass.

CI on main is all green. This probably has to do with the introduction of blackjax to the env modifying the jax version and which trigers the warning. It might have happened without this PR later on but this is very different from what the message says. If you can please look into this better to see if it can be fixed robustly otherwise you'll have to wait until we can look into it. While not directly related we can't merge this if it is breaking the numpyro tests.

Comment thread src/arviz_base/io_blackjax.py Outdated
Comment on lines +307 to +324
num_chains : int, optional
Number of chains. Required when ``sample_dims`` includes ``"chain"``
and the position arrays already carry a leading chain axis (i.e.
multi-chain runs via ``jax.vmap`` / ``jax.pmap``). Ignored when
``sample_dims=["sample"]``.
max_tree_depth : int, optional
Maximum tree depth used during NUTS sampling. When provided,
``reached_max_tree_depth`` is added to ``sample_stats``.
sample_dims : list of str, optional
Names of the sample dimensions. Defaults to
``rcParams["data.sample_dims"]`` (typically ``["chain", "draw"]``).

Use ``["chain", "draw"]`` for standard multi/single-chain NUTS runs
where you want ArviZ to track the chain dimension explicitly.

Use ``["sample"]`` when you want a flat sample representation without
a chain dimension (e.g. after thinning or when compatibility with
LOO tools is not required).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has nothing to do with #174 (comment). Take for example this piece of the comment:

That means by default chain and draw are assumed to be present unless the rcParam is modified or the argument passed explicitly

The code is not assuming chain and draw are present. It still does exactly the same as before. The default behaviour continues to be that if n_chains is not passed then the dimension is added. IIUC, taking the variables from the notebook, from_blackjax(states.position) will work but from_blackjax(mc_states.position) will not. As I said, running a single chain is not recommended, so I don't think the from_blackjax API should "reward" that by making it simpler while making the recommended behaviour harder. Given the default is sample_dims being chain and draw, the second example should work out of the box, and the first example should require being explicit about the lack of chain dimension, e.g. from_blackjax(states.position, sample_dims="draw").

Not everything we'll say will be clear but reviews allow for further comments so you can ask for clarification.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the explanation
just to confirm: when sample_dims=["chain", "draw"] (the default) and num_chains is provided, the leading axis is treated as the chain axis. But when sample_dims=["chain", "draw"] and num_chains is NOT provided, should we raise an error asking the user to either pass num_chains or use sample_dims=["draw"]? Or should we still try to handle it somehow?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be no num_chains argument, I don't see what value it would add if we already have sample_dims. If sample_dims has chain as the first dimension then the first dimension in the data should be assumed to be the chain dimension. If there is no chain dimension then the converter should error out. I think the behaviour on this should basically be like from_dict

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, i will remove num_chains entirely and update _ensure_chain_dim to simply assume the first axis is the chain dimension when sample_dims starts with "chain", erroring out if the data doesn't have enough dimensions. I'll also look into the numpyro test failures caused by the jax version change from adding blackjax to the CI environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add from_blackjax converter

2 participants