class

ComposeTransform

extendsTransform
ComposeTransform(parts: list[Transform])
source

Function composition of a list of transforms — left-to-right.

Implements the composite map T=TnTn1T1T = T_n \circ T_{n-1} \circ \cdots \circ T_1 so that the first transform in parts is applied first. The composite Jacobian is the sum of the per-step Jacobians along the trajectory, by the chain rule. This is the workhorse for stacking normalising flows.

Parameters

partslist[Transform]
Ordered list of sub-transforms. Must be non-empty. Their event_dim values are reduced via max.

Raises

ValueError
If parts is empty.

Notes

Forward:

y=Tn(Tn1(T1(x)))\mathbf{y} = T_n(T_{n-1}(\cdots T_1(\mathbf{x}) \cdots ))

Inverse (transforms run in reverse with each one inverted):

x=T11(T21(Tn1(y)))\mathbf{x} = T_1^{-1}(T_2^{-1}(\cdots T_n^{-1}(\mathbf{y}) \cdots ))

Log Jacobian determinant (chain rule):

logdetJT(x)=k=1nlogdetJTk(x(k1))\log|\det J_T(\mathbf{x})| = \sum_{k=1}^{n} \log|\det J_{T_k}(\mathbf{x}^{(k-1)})|

where x(0)=x\mathbf{x}^{(0)} = \mathbf{x} and x(k)=Tk(x(k1))\mathbf{x}^{(k)} = T_k(\mathbf{x}^{(k-1)}). The implementation re-evaluates the forward chain to collect the intermediate states needed by each sub-Jacobian.

Examples

>>> import lucid
>>> from lucid.distributions.transforms import (
...     ExpTransform, AffineTransform, ComposeTransform)
>>> # Log-normal-like: y = exp(loc + scale * x)
>>> T = ComposeTransform([AffineTransform(loc=0.0, scale=1.0), ExpTransform()])
>>> T(lucid.tensor(0.0))
Tensor(1.0)

Methods (2)

dunder

__init__

None
__init__(parts: list[Transform])
source

Store the ordered list of sub-transforms.

Raises

ValueError
If parts is empty.
fn

log_abs_det_jacobian

Tensor
log_abs_det_jacobian(x: Tensor, y: Tensor)
source

Cumulative log-Jacobian across the composition.

Re-evaluates the chain of forward maps to obtain the intermediate states needed by each sub-Jacobian, then sums the contributions.