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.
ContinuousMorphism
¶
ContinuousMorphism(domain: AnySpace, codomain: AnySpace)
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
252 253 254 255 | |
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_prob(x: Tensor, y: Tensor) -> Tensor
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
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | |
rsample
abstractmethod
¶
rsample(x: Tensor, sample_shape: Size = Size()) -> Tensor
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.
TYPE:
|
| 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
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 | |
has_conditional_density
¶
has_conditional_density() -> bool
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
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |
point_mass_value
¶
point_mass_value(x: Tensor) -> Tensor | None
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
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 | |
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
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 | |
push_base
¶
push_base(x: Tensor, base: Tensor) -> Tensor
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
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 | |
marginal_quadrature
¶
marginal_quadrature(x: Tensor, count: int) -> tuple[Tensor, Tensor] | None
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
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 | |
sample
¶
sample(x: Tensor, sample_shape: Size = Size()) -> Tensor
Non-reparameterized samples (no gradient through samples).
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Inputs.
TYPE:
|
sample_shape
|
Additional leading sample dimensions.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Samples (detached from computation graph). |
Source code in src/quivers/continuous/morphisms.py
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 | |
__rshift__
¶
__rshift__(other: object) -> ContinuousMorphism
Composition via ancestral sampling: self >> other.
Source code in src/quivers/continuous/morphisms.py
533 534 535 536 537 538 539 540 541 | |
__rrshift__
¶
__rrshift__(other: object) -> ContinuousMorphism
Handle discrete_morphism >> continuous_morphism.
Source code in src/quivers/continuous/morphisms.py
543 544 545 546 547 548 549 | |
__matmul__
¶
__matmul__(other: object) -> ProductContinuousMorphism
Independent product: self @ other.
Source code in src/quivers/continuous/morphisms.py
551 552 553 554 555 556 557 558 559 | |
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
585 586 587 | |
rsample
¶
rsample(x: Tensor, sample_shape: Size = Size()) -> Tensor
Sample from the base family (forward behaviour is preserved).
Source code in src/quivers/continuous/morphisms.py
594 595 596 597 598 | |
log_prob
¶
log_prob(x: Tensor, y: Tensor) -> Tensor
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
600 601 602 603 604 605 606 607 | |
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
633 634 635 636 637 638 639 640 641 642 | |
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
662 663 664 665 666 667 668 669 670 671 672 673 | |
push_base
¶
push_base(x: Tensor, base: Tensor) -> Tensor
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
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 | |
rsample
¶
rsample(x: Tensor, sample_shape: Size = Size()) -> Tensor
Ancestral sampling: y ~ f(x, .), then z ~ g(y, .).
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Inputs to the composition.
TYPE:
|
sample_shape
|
Additional sample dimensions.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Samples from the composed morphism. |
Source code in src/quivers/continuous/morphisms.py
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 | |
log_prob
¶
log_prob(x: Tensor, y: Tensor) -> Tensor
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
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 | |
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
894 895 896 897 898 899 900 901 902 903 | |
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
929 930 931 932 933 934 935 936 | |
push_base
¶
push_base(x: Tensor, base: Tensor) -> Tensor
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
938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 | |
FanOutMorphism
¶
FanOutMorphism(components: list)
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
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 | |
rsample
¶
rsample(x: Tensor, sample_shape: Size = Size()) -> Tensor
Sample from all components and concatenate outputs.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Input tensor (broadcast to all components).
TYPE:
|
sample_shape
|
Additional leading sample dimensions.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Concatenated outputs from all components. |
Source code in src/quivers/continuous/morphisms.py
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 | |
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
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 | |
push_base
¶
push_base(x: Tensor, base: Tensor) -> Tensor
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
1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 | |
log_prob
¶
log_prob(x: Tensor, y: Tensor) -> Tensor
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
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 | |
DiscreteAsContinuous
¶
DiscreteAsContinuous(inner: object)
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
1160 1161 1162 1163 1164 1165 1166 1167 | |
log_prob
¶
log_prob(x: Tensor, y: Tensor) -> Tensor
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
1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 | |
rsample
¶
rsample(x: Tensor, sample_shape: Size = Size()) -> Tensor
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.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
Sampled codomain indices. Shape (*sample_shape, batch). |
Source code in src/quivers/continuous/morphisms.py
1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 | |
dimension_probe
¶
dimension_probe(x: Tensor) -> Tensor
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
44 45 46 47 48 49 50 51 | |
sobol_normal_points
¶
sobol_normal_points(dimension: int, count: int, device: device, dtype: dtype) -> Tensor
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
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
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
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | |
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
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |
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 |
Source code in src/quivers/continuous/morphisms.py
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | |