Skip to content

Add Leafwise bijector#83

Open
gvcallen wants to merge 3 commits into
lockwo:mainfrom
gvcallen:treemap
Open

Add Leafwise bijector#83
gvcallen wants to merge 3 commits into
lockwo:mainfrom
gvcallen:treemap

Conversation

@gvcallen

@gvcallen gvcallen commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Adds a Leafwise bijector, analogous to tensorflow's JointMap and opposite to Chain.

This applies a tree of bijectors to a tree of inputs.

@gvcallen gvcallen changed the title Add TreeMap Add Leafwise May 8, 2026
@gvcallen gvcallen changed the title Add Leafwise Add Leafwise bijector May 8, 2026
Leafwise applies a tree of bijectors to a matching tree of inputs,
analogous to TensorFlow's JointMap and the pytree-level opposite of
Chain (which composes bijectors sequentially on a single input).
return isinstance(node, AbstractBijector)


class Leafwise(AbstractFwdLogDetJacBijector, AbstractInvLogDetJacBijector):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can you clarify the specific use case for this style? I'm just thinking in general, pytrees can be handled by the user and ideally we can make things work for pytrees naturally, without requiring additional classes. I guess this is just doing that as a utility like jax.tree.map(lambda bi, x: bi(x), bijectors, inputs?

@gvcallen gvcallen Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

By this style do you mean these types of classes? Essentially it is to prevent a ton of boilerplate and if conditions for pytree "power users".

Leafwise specifically is very similar to a Joint distribution. A user may have a tree of distributions that encapsulate a bijective transformation over another tree. Without this class:

  • Maintaining semantic clarity becomes difficult. Instead of the user writing bijector.forward(tree) they have to manually map over the leaves, even though the equivalent operation is semantically represented by a single bijector
  • Abstraction layers become muddled. For example, lets say a user actually has some bijector that caters for the trees shape. Now they have to use an if condition to check "if they have a pytree of bijectors" or a "single bijector" which complicates code very quickly

It would be similar to having to manually sum log probs for a Joint distribution: technically the user could do it themselves (maybe even implementing the class manually), but it probably should just be in distreqx itself.

This is why I think TensorFlow provides classes like JointMap - it is not that the user cannot accomplish the same task themselves (as you've demonstrated): it is to keep the abstraction layer clean. Otherwise classes like Chain should technically also not exist (as the user could just write a for loop while carrying through log dets).

If you would like a concrete example, have a look at the second codeblock in Parax's documentation.

One more such utility class (not yet submitted for PR but one I've found I will need) is a Combined distribution, which is essentially eqx.combine over a tree of distributions with some none-leaves. Again, technically doable by an end-user, but provides much better pytree enablement if part of the base library.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I see, makes sense, lets make sure this is clear in class docstring

return isinstance(node, AbstractBijector)


class Leafwise(AbstractFwdLogDetJacBijector, AbstractInvLogDetJacBijector):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I see, makes sense, lets make sure this is clear in class docstring


def __init__(self, bijectors: PyTree[AbstractBijector]):
"""Initializes a TreeMap bijector."""
leaves = jax.tree_util.tree_leaves(bijectors, is_leaf=_is_bijector)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

should we check to make sure log det shapes match? e.g. Leafwise({"a": Shift(jnp.ones(4)), "b": Shift(jnp.ones(2))}) raises TypeError: add got incompatible shapes for broadcasting: (4,), (2,) on forward_log_det_jacobian.

Wrapping them in Block would work tho.


def __init__(self, bijectors: PyTree[AbstractBijector]):
"""Initializes a TreeMap bijector."""
leaves = jax.tree_util.tree_leaves(bijectors, is_leaf=_is_bijector)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

jax.tree.

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.

2 participants