Continuous Morphisms¶
Operational kernels over discrete or continuous carriers. A
ContinuousMorphism evaluates log densities and draws samples rather
than materializing a finite morphism tensor.
morphisms
¶
Markov kernels on discrete, continuous, and mixed spaces.
ContinuousMorphism defines log_prob and rsample. The >>
operator composes kernels and @ forms their independent product.
Discrete intermediates are marginalized by finite summation. Continuous
intermediates are scored along the deterministic reference path described
by SampledComposition.log_prob.
AnySpace
¶
AnySpace = SetObject | ContinuousSpace
A space a morphism maps between: a finite set or a continuous space.
ContinuousMorphism
¶
Bases: Module, ABC
Abstract base for morphisms involving continuous spaces.
Subclasses must implement log_prob and rsample. The
composition operator >> and product operator @ are
provided and dispatch to SampledComposition and
ProductContinuousMorphism respectively.
Unlike discrete Morphism (which materializes a full tensor), ContinuousMorphism is defined operationally: it can evaluate log-densities and generate reparameterized samples.
| PARAMETER | DESCRIPTION |
|---|---|
domain
|
Source space.
TYPE:
|
codomain
|
Target space.
TYPE:
|
Source code in src/quivers/continuous/morphisms.py
support
property
¶
support: Constraint
The support constraint of the distribution this morphism samples
from, in the form of a torch.distributions.constraints.Constraint.
Used by variational guides (quivers.inference.AutoNormalGuide,
quivers.inference.AutoDeltaGuide) to determine the
correct bijector that maps an unconstrained variational
approximation back to the constrained support of the prior, so
that samples used to evaluate the prior's log_prob lie inside
its support (avoiding Expected value to be within the support
of the distribution errors).
Subclasses representing a constrained distribution family
(HalfNormal, Beta, Uniform, Dirichlet,
LogitNormal, Wishart, …) should override this property
to return the appropriate constraint. The default is
torch.distributions.constraints.real, which is correct
for unconstrained families like Normal and discrete
codomains (where the guide skips the site anyway).
log_prob
abstractmethod
¶
Log-probability (density) of y given x.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Inputs. Shape (batch,) for discrete domain or (batch, domain_dim) for continuous domain.
TYPE:
|
y
|
Outputs. Shape (batch,) for discrete codomain or (batch, codomain_dim) for continuous codomain.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Log-probabilities/densities. Shape (batch,). |
Source code in src/quivers/continuous/morphisms.py
rsample
abstractmethod
¶
Reparameterized samples from p(. | x).
Gradients flow through the returned samples back to the parameters of this morphism (and to x if the domain is continuous).
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Inputs. Shape (batch,) or (batch, domain_dim).
TYPE:
|
sample_shape
|
Additional leading sample dimensions. |
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Samples. Shape (sample_shape, batch, codomain_dim) for continuous codomain, or (sample_shape, batch) for discrete. |
Source code in src/quivers/continuous/morphisms.py
has_conditional_density
¶
Whether
log_prob
evaluates a density rather than raising.
Every kernel whose conditional law is a named family answers
yes. A kernel that denotes a program answers no: its density
at a value marginalizes the program's internal draws, which no
closed form covers, and its log_prob says so by raising.
A caller deciding between two constructions needs that answer
before it calls, not as an exception afterwards, so the
capability is declared rather than discovered. The parallel
with
point_mass_value
and
base_dimension
is exact: each reports a structural property of the kernel that
determines which exact treatment is open to a caller, and none
of them is a probe by trial.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True for a kernel with an evaluable conditional density. |
Source code in src/quivers/continuous/morphisms.py
point_mass_value
¶
The single value this kernel puts all of its mass on, or None.
A morphism whose conditional law is a Dirac delta
:math:\delta_{T(x)} returns :math:T(x); every other
morphism returns None. The distinction is what lets
SampledComposition
collapse an integral over a degenerate intermediate to a
single evaluation, which is exact rather than approximate.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Inputs. Shape
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor or None
|
The deterministic image of |
Source code in src/quivers/continuous/morphisms.py
base_dimension
¶
base_dimension(x: Tensor) -> int | None
Standard-normal coordinates this kernel's reparameterization reads.
A morphism that can be written :math:y = T_x(\varepsilon)
with :math:\varepsilon standard normal reports how many
coordinates :math:T_x consumes at this input; every other
morphism reports None. A deterministic map consumes none
and reports 0.
The count may depend on x: an embedding kernel reading a
(batch, seq) index matrix places one Gaussian per position,
so it consumes seq * dim coordinates where the same kernel
on a (batch,) index vector consumes dim.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Conditioning inputs. Shape
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
int or None
|
The coordinate count, or |
Source code in src/quivers/continuous/morphisms.py
push_base
¶
Push standard-normal coordinates through the reparameterization.
Evaluates :math:T_x(\varepsilon) for the map
base_dimension
describes. The map is a pure function of (x, base): it
reads no random state, which is what lets a caller build a
quadrature out of it and get the same nodes on every call.
The default covers the degenerate case, where the map ignores its (empty) coordinates and returns the point mass.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Conditioning inputs. Shape
TYPE:
|
base
|
Standard-normal coordinates. Shape
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Shape |
Source code in src/quivers/continuous/morphisms.py
marginal_quadrature
¶
A deterministic rule for integrating against p(. | x).
Returns (nodes, log_weights) approximating
.. math::
\int p(y \mid x) \, \varphi(y) \, dy
\;\approx\;
\sum_i \exp(\log w_i) \, \varphi(y_i)
Point masses return one unit-weight node. Reparameterized
kernels return equally weighted Sobol nodes produced by
push_base. Kernels without either representation return
None. This method does not consume random state.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Conditioning inputs. Shape
TYPE:
|
count
|
Requested number of nodes. An implementation may return fewer (an exact rule needs one) or round up to the count its construction is balanced at.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple[Tensor, Tensor] or None
|
Nodes and log-weights, or |
Source code in src/quivers/continuous/morphisms.py
sample
¶
Non-reparameterized samples (no gradient through samples).
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Inputs.
TYPE:
|
sample_shape
|
Additional leading sample dimensions. |
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Samples (detached from computation graph). |
Source code in src/quivers/continuous/morphisms.py
__rshift__
¶
__rshift__(other: object) -> ContinuousMorphism
Composition via ancestral sampling: self >> other.
Source code in src/quivers/continuous/morphisms.py
__rrshift__
¶
__rrshift__(other: object) -> ContinuousMorphism
Handle discrete_morphism >> continuous_morphism.
Source code in src/quivers/continuous/morphisms.py
__matmul__
¶
__matmul__(other: object) -> ProductContinuousMorphism
Independent product: self @ other.
Source code in src/quivers/continuous/morphisms.py
MarginalizedFactor
¶
MarginalizedFactor(base: ContinuousMorphism)
Bases: ContinuousMorphism
Score-suppressed wrapper for a marginalized block's live sites.
An ungrouped marginalize block keeps its latent draw and its
terminal observe as live sites so a forward trace still produces
the sampled coordinate and response (ancestral sampling and
synthetic-data generation both read those sites). Their densities,
however, are carried once by the block's integrated score step, so
adding them to the joint again would double-count the very factors
the marginal already integrates. This wrapper delegates sampling to
the base morphism yet reports a zero log-density, keeping the joint
free of the raw per-draw factor while preserving forward behaviour.
| PARAMETER | DESCRIPTION |
|---|---|
base
|
The underlying family whose sampling behaviour is preserved.
TYPE:
|
Source code in src/quivers/continuous/morphisms.py
rsample
¶
Sample from the base family (forward behaviour is preserved).
log_prob
¶
Report a zero log-density.
The factor's density is carried by the block's integrated score step; contributing it here would double-count it in the joint.
Source code in src/quivers/continuous/morphisms.py
SampledComposition
¶
SampledComposition(left: ContinuousMorphism, right: ContinuousMorphism, n_intermediate: int = 100)
Bases: ContinuousMorphism
Composition of morphisms via ancestral sampling.
rsample draws from left and then right. log_prob
sums over a discrete intermediate. For a stochastic continuous
intermediate it scores the deterministic reference path, not the
endpoint marginal.
| PARAMETER | DESCRIPTION |
|---|---|
left
|
First morphism (applied first).
TYPE:
|
right
|
Second morphism (applied second).
TYPE:
|
n_intermediate
|
Node count for the deterministic rule this composition offers
through
TYPE:
|
Source code in src/quivers/continuous/morphisms.py
factors
property
¶
factors: tuple[ContinuousMorphism, ...]
The composition flattened into its non-composite factors.
(a >> b) >> c and a >> (b >> c) both report
(a, b, c): association is invisible to the kernel the
composition denotes, and every intermediate between adjacent
factors is an object the chain integrates over. A caller that
wants those intermediates as named sites walks this tuple.
base_dimension
¶
base_dimension(x: Tensor) -> int | None
Total coordinates the whole chain's reparameterization reads.
A chain is reparameterized by reparameterizing each factor and threading the result forward, so its coordinate budget is the sum of its factors'. One factor without a reparameterization leaves the chain without one.
Source code in src/quivers/continuous/morphisms.py
push_base
¶
Thread the coordinates through the chain, factor by factor.
Each factor consumes its own contiguous block of base, so
no two factors share a coordinate.
Source code in src/quivers/continuous/morphisms.py
rsample
¶
Ancestral sampling: y ~ f(x, .), then z ~ g(y, .).
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Inputs to the composition.
TYPE:
|
sample_shape
|
Additional sample dimensions. |
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Samples from the composed morphism. |
Source code in src/quivers/continuous/morphisms.py
log_prob
¶
Log-probability of y given x through the composition.
A discrete intermediate is marginalized exactly, by finite
summation over its elements. A continuous one is not
marginalized at all: the chain is scored along the canonical
path
_log_prob_reference_path
describes, which is exact where an integral would have been
approximate, and is a pure function of (x, y) where a rule
would have made it a function of the node count as well.
Both branches return the same number when every intermediate is degenerate, which is the case the two readings share.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Inputs. Shape (batch,) or (batch, dom_dim).
TYPE:
|
y
|
Outputs. Shape (batch,) or (batch, cod_dim).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Log-probabilities. Shape (batch,). |
Source code in src/quivers/continuous/morphisms.py
ProductContinuousMorphism
¶
ProductContinuousMorphism(left: ContinuousMorphism, right: ContinuousMorphism)
Bases: ContinuousMorphism
Independent product of two continuous morphisms.
Given f: A -> B and g: C -> D, produces f @ g: (A, C) -> (B, D) where p_{f@g}((y,z) | (x,w)) = f(y | x) * g(z | w).
Domain inputs are concatenated: (x, w) as a single vector. Codomain outputs are concatenated: (y, z) as a single vector. For discrete components, indices are embedded as 1-d floats.
| PARAMETER | DESCRIPTION |
|---|---|
left
|
Left factor morphism.
TYPE:
|
right
|
Right factor morphism.
TYPE:
|
Source code in src/quivers/continuous/morphisms.py
base_dimension
¶
base_dimension(x: Tensor) -> int | None
Sum of the two factors' coordinate budgets at their own inputs.
Source code in src/quivers/continuous/morphisms.py
push_base
¶
Push each factor's own coordinate block through that factor.
The factors are independent given the input, so the product's
reparameterization is the pair of theirs on disjoint
coordinate blocks, concatenated along the feature axis exactly
as rsample concatenates its draws.
Source code in src/quivers/continuous/morphisms.py
FanOutMorphism
¶
Bases: ContinuousMorphism
Fan-out morphism: copy input to N morphisms, concatenate outputs.
Given f_1: A -> B_1, f_2: A -> B_2, ..., f_N: A -> B_N, produces fan(f_1, ..., f_N): A -> B_1 * B_2 * ... * B_N where the input A is copied to all N morphisms.
Unlike the tensor product (f @ g), which takes a product domain (A * C), fan-out feeds the same input to all morphisms. This implements the diagonal morphism Delta: A -> A^N followed by the product f_1 @ f_2 @ ... @ f_N.
| PARAMETER | DESCRIPTION |
|---|---|
components
|
The morphisms to fan out to. All must share the same domain.
TYPE:
|
Source code in src/quivers/continuous/morphisms.py
rsample
¶
Sample from all components and concatenate outputs.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Input tensor (broadcast to all components).
TYPE:
|
sample_shape
|
Additional leading sample dimensions. |
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Concatenated outputs from all components. |
Source code in src/quivers/continuous/morphisms.py
base_dimension
¶
base_dimension(x: Tensor) -> int | None
Sum of the components' coordinate budgets.
Fan-out copies its input to independent components, so their reparameterizations share the input and nothing else.
Source code in src/quivers/continuous/morphisms.py
push_base
¶
Push each component's own coordinate block through it.
The blocks are disjoint and the outputs concatenate along the
feature axis, matching the layout rsample and log_prob
already use for the fan's codomain.
Source code in src/quivers/continuous/morphisms.py
log_prob
¶
Log-probability: sum of component log-probs.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Input (same for all components).
TYPE:
|
y
|
Concatenated output values.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Sum of log-probabilities. Shape |
Source code in src/quivers/continuous/morphisms.py
DiscreteAsContinuous
¶
Bases: ContinuousMorphism
Wrap a discrete Morphism as a ContinuousMorphism.
Enables composition between discrete and continuous morphisms via the >> operator. The wrapped morphism's tensor is used for both log_prob evaluation and sampling.
Note
Sampling from a discrete distribution is NOT reparameterizable. Gradients do not flow through the discrete samples back to the left morphism's parameters. Use score function estimators (REINFORCE) if gradients through discrete choices are needed.
| PARAMETER | DESCRIPTION |
|---|---|
inner
|
The discrete morphism to wrap.
TYPE:
|
Source code in src/quivers/continuous/morphisms.py
log_prob
¶
Log-probability from the discrete tensor.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Domain indices. Shape (batch,).
TYPE:
|
y
|
Codomain indices. Shape (batch,).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Log-probabilities. Shape (batch,). |
Source code in src/quivers/continuous/morphisms.py
rsample
¶
Sample from the categorical distribution defined by the tensor.
Note: not reparameterizable. Gradients do not flow through the returned samples.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Domain indices. Shape (batch,).
TYPE:
|
sample_shape
|
Additional sample dimensions. |
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Sampled codomain indices. Shape (*sample_shape, batch). |
Source code in src/quivers/continuous/morphisms.py
dimension_probe
¶
A one-row slice of x, enough to settle coordinate counts.
A morphism's
base_dimension
depends on trailing event extents, not the number of rows.
Source code in src/quivers/continuous/morphisms.py
sobol_normal_points
¶
A deterministic standard-normal point set of shape (n, dimension).
Push an unscrambled Sobol point set through the standard-normal
quantile function. The result is deterministic. The implementation
skips the Sobol origin, clamps quantile inputs away from 0 and 1,
and rounds count up to a power of two.
| PARAMETER | DESCRIPTION |
|---|---|
dimension
|
Number of coordinates per point. Zero yields an empty
TYPE:
|
count
|
Requested point count; rounded up to a power of two.
TYPE:
|
device
|
Device to place the result on.
TYPE:
|
dtype
|
Floating dtype of the result.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Shape |
Source code in src/quivers/continuous/morphisms.py
chain_dimensions
¶
chain_dimensions(factors: 'collections.abc.Sequence[ContinuousMorphism]', x: Tensor) -> list[int] | None
Per-factor base-coordinate counts along a chain, or None.
A factor's count can depend on the shape of what reaches it, so the chain is walked once with the coordinates held at zero. That pushes each kernel's median forward, which costs a forward pass and settles the shapes without consuming a point set the caller has not built yet.
Source code in src/quivers/continuous/morphisms.py
chain_push_base
¶
chain_push_base(factors: 'collections.abc.Sequence[ContinuousMorphism]', x: Tensor, base: Tensor, dimensions: list[int]) -> Tensor
Thread base coordinates through a chain, one block per factor.
Each factor consumes its own contiguous block, so no two factors read the same coordinate and the composite map is the pushforward of a single point set through the whole chain rather than per-factor rules glued together by index. Sharing coordinates across factors would resolve some directions of the joint twice and leave others unexplored.
Source code in src/quivers/continuous/morphisms.py
chain_marginal_quadrature
¶
chain_marginal_quadrature(factors: 'collections.abc.Sequence[ContinuousMorphism]', x: Tensor, count: int) -> tuple[Tensor, Tensor] | None
A deterministic rule for the law a whole chain induces on its end.
Push one point set of dimension sum(chain_dimensions(...))
through every factor. The rule returns n terminal nodes without
multiplying the node count at each link.
| PARAMETER | DESCRIPTION |
|---|---|
factors
|
The chain, in application order. A single-element sequence defers to that morphism's own rule.
TYPE:
|
x
|
Conditioning inputs. Shape
TYPE:
|
count
|
Requested node count; rounded up to a power of two.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple[Tensor, Tensor] or None
|
Nodes of shape |