skoro v0.0.0

The problem

The mathematics the core rests on, from quantities, their charts and their products, through operations and their derivatives of first and second order composed over programs, to queries, terms, importance weights, random keys and retirement.

Updated 2026-10-12 · 28 min read

The problem#

The core is the part of skoro that every later entry builds on: the quantities a model is made of, the operations that relate them, the derivatives of those operations, the log-density a model defines, and the changes a model goes through as it is built, branched and retired. It ships no distribution, but it must be exactly right about what a distribution, an estimator or a sampler will ask of it, because each of those will trust it without checking.

This document works out that mathematics, at the level of precision the implementation needs. Most of it is standard, and the standard parts are in the math notes it depends on: retractions, automatic differentiation over frames, derivatives in tangent charts and product manifolds. What is particular to skoro, and what this document settles, is how those pieces meet the charter: which quantities are differentiated and when, what order of derivative a computation needs from each step, what selection and discrete quantities do to derivatives, what a query does to a model’s density, how random keys make draws reproducible, and what retiring a quantity means.

Setting#

A model is a set of quantities and a set of terms. Each quantity takes values in its type: a manifold X\manifold of dimension d\dimension with a retraction, or a countable set. Each term tt is a real-valued function ℓt\logterm_t of some of the quantities, its log-density, and the model’s log-density is their sum (the measure theory underneath is in the appendix),

log⁡p(x)=∑tℓt(x).(1) \log \density(\point) = \sum_t \logterm_t(\point) . \tag{1}
(1)

For a manifold-valued quantity, x⊕v\point \oplus \tangent and y⊖x\otherPoint \ominus \point are its retraction and inverse retraction (2), and derivatives are read in tangent charts (1). A quantity’s dimension is that of its tangent space; its storage size is the number of reals that represent a point, which may be larger (a rotation has dimension 33 and is stored as 99 or 44 numbers). Every flat vector of derivative coordinates in this document is indexed by dimension, never by storage.

Computation is batched: a model is evaluated at NN points at once, one per row of a batch. Rows never interact: an evaluation at NN points is NN independent evaluations, and every derivative below is per row. Batching is therefore absent from the mathematics, and only the implementation needs to keep it straight.

A model is two graphs over the same objects, and the rest of this document uses both. Each answers different questions, and neither is ever called simply “the graph”. The running example is a vehicle at x0\point_0 and then x1\point_1, which measures a landmark at mm once from each position, as z0z_0 and z1z_1.

The compilation graph#

The compilation graph records how values are computed. Its nodes are the model’s values: its quantities, its constants, and its mapped values, the outputs of operations, among which are the terms’ log-densities. It has an edge from each input of an operation to the operation’s output, so a node with incoming edges is a mapped value, computed from the nodes those edges come from.

  • It is acyclic, because a model is built forward: an operation is added only after its inputs.
  • Its roots, the nodes with no incoming edge and so no ancestor, are the quantities and the constants. They are the inputs of the map that the model computes, and every mapped value is a function of the roots it is reached from.
  • A quantity is a root even when it was drawn from a distribution whose parameters are mapped values. That dependence belongs to the probabilistic graph below, not to this one, which is why no derivative ever flows through a sampler.
A compilation graph
x0\point_0 mm z0z_0 m−x0m - \point_0 z0−(⋅)z_0 - (\cdot) r⊙rr \odot r ∑i\textstyle\sum_i−12-\tfrac12 ℓ4\logterm_4
  • Quantity
  • Constant
  • Mapped value
  • A term's log-density
  • input of an operation
A compilation graph. The log-density of the term t4t_4, ℓ4=−12∑i(z0−(m−x0))i2\logterm_4 = -\tfrac12 \sum_i (z_0 - (m - \point_0))_i^2, as it is computed. Each edge runs from an input of an operation to its output. The roots are the quantities x0\point_0, mm, z0z_0 and the constant −12-\tfrac12; every other node is a function of them.

Evaluating a model compiles a program from the compilation graph: the subgraph of the nodes from which a requested target is reached, its operations taken in a topological order. That subgraph is the program’s computation graph, and its roots are the roots of the compilation graph that the targets reach.

The probabilistic graph#

The probabilistic graph records what depends on what in distribution. Its nodes are the model’s quantities and its terms. It has an edge from each quantity a term is conditioned on to the term, and an edge from each term to the quantity it scores, if any.

  • It is bipartite: every edge joins a quantity and a term.
  • Each quantity is scored by at most one term: the term of the distribution it was drawn from. A term that scores nothing is a likelihood factor over the quantities it is conditioned on: in Bayesian terms, the likelihood of data that is not a quantity of the model, such as the measurements a retirement conditioned on. A term that scores an observed quantity is a likelihood too, but only in the queries that observe it; a likelihood factor is one in every query.
  • It is acyclic, because a term is conditioned only on quantities that existed before the quantity it scores. A topological order of it is therefore an ancestral order, in which each quantity comes after everything its distribution is conditioned on.
  • Its edges are read off the compilation graph rather than declared: a term is conditioned on the quantities among the roots its log-density is reached from, other than the one it scores.
A probabilistic graph
t1t_1 p(x0)\density(\point_0)t2t_2 p(m)\density(m)x0\point_0 mm t3t_3 p(x1∣x0)\density(\point_1 \mid \point_0)t4t_4 p(z0∣x0,m)\density(z_0 \mid \point_0, m)x1\point_1 z0z_0 t5t_5 p(z1∣x1,m)\density(z_1 \mid \point_1, m)z1z_1
  • Quantity
  • Term
  • conditions a term
  • scores a quantity
A probabilistic graph. An arrow into a term comes from a quantity it is conditioned on; a dotted arrow out of a term points to the quantity it scores. Retiring x0\point_0 and z0z_0 replaces the terms of x0\point_0, x1\point_1 and z0z_0 by one term over the Markov blanket {x1,m}\{\point_1, m\}.

Quantities#

Euclidean quantities. The core’s flat types are the real line R\R, vectors Rn\R^n and matrices Rm×n\R^{m \times n}, with x⊕v=x+v\point \oplus \tangent = \point + \tangent and y⊖x=y−x\otherPoint \ominus \point = \otherPoint - \point. A matrix is flattened column by column into Rmn\R^{mn} for its tangent coordinates, so its dimension equals its storage size. The dimensions nn, mm are fixed for each quantity when it is created, and every operation checks them then.

Positive quantities. Scales, rates and variances live on the positive orthant (0,∞)n(0, \infty)^n, with the retraction of the example in retractions, elementwise:

x⊕v=x⊙ev,y⊖x=log⁡y−log⁡x.(2) \point \oplus \tangent = \point \odot e^{\tangent}, \qquad \otherPoint \ominus \point = \log \otherPoint - \log \point . \tag{2}
(2)

A tangent coordinate is a relative change, and a point is stored as its nn values, so its dimension equals its storage size. The positive orthant is not flat in the sense of the notes, since ⊕\oplus is not addition, but it is flat in disguise: log⁡(x⊕v)=log⁡x+v\log(\point \oplus \tangent) = \log \point + \tangent, so the logarithm is one chart of the whole space in which every retraction is a translation. Three things follow. The retraction is defined for every v∈Rn\tangent \in \R^n and never leaves the space. The identities (x⊕v)⊖x=v(\point \oplus \tangent) \ominus \point = \tangent and x⊖(x⊕v)=−v\point \ominus (\point \oplus \tangent) = -\tangent both hold exactly. And the chart Hessian equals the Hessian of the pull-back everywhere, not only at stationary points: in Proposition 5, φt(u)=tv+u\varphi_t(u) = t\tangent + u, whose derivative does not move, so the correction vanishes. What is not trivial is reading a positive quantity as a real number, through the inclusion (0,∞)n⊂Rn(0, \infty)^n \subset \R^n: its derivative in charts is diag⁡(x)\operatorname{diag}(\point), which moves with the point.

Discrete quantities. The core’s discrete types are the finite sets Categorical(n)={0,…,n−1}\mathrm{Categorical}(n) = \{0, \dots, n - 1\}, with Boolean=Categorical(2)\mathrm{Boolean} = \mathrm{Categorical}(2), and the integers Z\mathbb Z. A discrete quantity has no tangent space, no retraction and no dimension, and it is never a coordinate of a derivative. Its values are written k\discretePoint. A density over a discrete quantity is a probability mass, a density with respect to the counting measure.

Products. A product of continuous types is a continuous type: a quantity of type X1×⋯×Xn\manifold_1 \times \cdots \times \manifold_n is one point (x1,…,xn)(\point_1, \dots, \point_n), stored as its factors’ storage concatenated, with the stacked tangent space (1) and the factor-wise retraction of Proposition 2. The core has products of continuous types only. A quantity with a continuous part and a discrete part is two quantities, as the charter has it, and a joint density over several discrete quantities needs no product, since a term may be conditioned on any number of quantities.

A product’s factors may be of any continuous type, including products, and of dimensions known only at run time. That is what makes a product the natural type of a marginal over a Markov blanket, whose members are only known when a quantity is retired (Retirement).

Operations#

An operation is a smooth map

y=F(x1,…,xm;k1,…,kr)(3) \otherPoint = \map(\point_1, \dots, \point_m; \discretePoint_1, \dots, \discretePoint_r) \tag{3}
(3)

from continuous inputs xj∈Xj\point_j \in \manifold_j and discrete inputs ki\discretePoint_i to an output y∈Y\otherPoint \in \otherManifold, smooth in the continuous inputs for each value of the discrete ones. In the compilation graph it is a mapped node with an incoming edge from each input. Its partial derivatives ∂jF\partial_j \map are taken in tangent charts, and it may provide four maps, each over a frame of directions (automatic differentiation over frames):

mapgivenreturns
forwardthe inputsy\otherPoint
tangenta frame vj\tangent_j per continuous inputv=∑j∂jF vj\otherTangent = \sum_j \partial_j \map\, \tangent_j
cotangenta frame α\alpha at the output∂jF⊤α\partial_j \map\transpose \alpha per continuous input
second ordervelocities vj\tangent_j, and one cotangent α\alphahvp⁡j(α)=ddt∣t=0∂jF(x⊕tv)⊤α\hvp_j(\alpha) = \frac{\dd}{\dd t}\big\vert_{t=0} \partial_j \map(\point \oplus t\tangent)\transpose \alpha per input

The second-order map takes a velocity frame of several columns, one Hessian direction each, and a single cotangent column. Its definition in charts is the one under which a forward-over-reverse pass computes the chart Hessian (4), by Proposition 6.

Discrete inputs carry no derivatives, and a discrete output carries none either. A map that turns a discrete input into a continuous output (an embedding, such as an integer read as a real) has no partial derivative in it. A map that turns a continuous input into a discrete output (a discretization, such as the indicator 1[x≥0]\mathbb 1[\point \ge 0]) passes no cotangent back. For a step function that is the true derivative everywhere but at its steps, and the core makes no attempt to do better: a model that needs a derivative across a discretization needs a relaxation, which is a modelling choice, not the core’s.

Two gates#

A pass runs over a program’s computation graph, whose roots include the quantities it differentiates with respect to. Within a pass, an operation’s continuous inputs differ in two ways that the implementation must keep apart:

  • an input is differentiable if it is reached from a root the pass differentiates with respect to, which decides whether cotangents (α\alpha, and in a Hessian pass β\beta) are pulled back into it along the backpropagation graph;
  • an input carries a velocity if it is reached from a root seeded with a velocity in this pass, which decides whether its velocity enters hvp⁡\hvp.

The second implies the first, and not conversely: a Hessian pass is often seeded at one quantity while all of them are differentiated. An operation’s hvp⁡j\hvp_j is absent whenever ∂jF\partial_j \map is not needed, and a missing velocity counts as zero.

Order#

Every operation states its order order⁡(F)\order(\map): 00 if it provides only the forward map, 11 if it also provides the tangent and cotangent maps, 22 if it also provides the second-order map. Most primitives have order 22. An operation that runs burn’s automatic differentiation inside, such as a neural network, has order 11; one whose derivative does not exist or was not written has order 00. What a pass needs of a step depends on where the step sits in the program’s computation graph.

Definition 4(The order a pass requires).

Fix a pass over a program: the roots LL it differentiates with respect to, the roots S⊆LS \subseteq L seeded with velocities (for a Hessian pass), and the targets TT. A step kk with map Fk\map_k is on the pass when, in the program’s computation graph, its output is continuous and reaches a target in TT, and one of its continuous inputs is reached from a root in LL. The order the pass requires of step kk is

req⁡(k)={0if step k is not on the pass,2if it is, the pass is a Hessian pass, and an input of step k is reached from S,1otherwise. \req(k) = \begin{cases} 0 & \text{if step } k \text{ is not on the pass,} \\ 2 & \text{if it is, the pass is a Hessian pass, and an input of step } k \text{ is reached from } S, \\ 1 & \text{otherwise.} \end{cases}

The pass is admissible when order⁡(Fk)≥req⁡(k)\order(\map_k) \ge \req(k) for every step kk.

A pass that is not admissible is refused before it runs, naming a step kk with order⁡(Fk)<req⁡(k)\order(\map_k) < \req(k). This refines the charter’s “least order over the program’s steps” in two ways that matter in practice. A step that no differentiated root reaches, or that reaches no target, needs nothing, so a model may contain a first-order neural network and still be differentiated twice with respect to quantities that do not feed it. And a step that only pulls back cotangents in a Hessian pass, because no seeded velocity reaches it, needs only first order: its hvp⁡\hvp terms are zero by (4).

Selection#

Selection chooses one of several continuous values by a discrete index:

y=select⁡(k; x0,…,xn−1)=xk,k∈Categorical(n),  xi∈X.(5) \otherPoint = \operatorname{select}(\discretePoint;\, \point_0, \dots, \point_{n-1}) = \point_\discretePoint, \qquad \discretePoint \in \mathrm{Categorical}(n),\; \point_i \in \manifold . \tag{5}
(5)

All the xi\point_i and y\otherPoint are of one type. For each k\discretePoint the map is a projection, so its derivatives in charts are as simple as derivatives get: since y=xk\otherPoint = \point_\discretePoint, the chart at y\otherPoint is the chart at xk\point_\discretePoint, and

∂xiy={Ii=k,0i≠k,hvp⁡xi=0. \partial_{\point_i} \otherPoint = \begin{cases} I & i = \discretePoint, \\ 0 & i \ne \discretePoint, \end{cases} \qquad \hvp_{\point_i} = 0 .

The cotangent at y\otherPoint goes to the selected value and nowhere else, and the index receives nothing. Mixtures, switching models and data association are built on selection: a mixture whose component is a discrete quantity reads that component’s parameters through selection, and a measurement associated by a discrete quantity selects the target it came from. The derivative with respect to the values, at a fixed index, is exactly what a gradient-based algorithm conditioned on the discrete quantities needs.

The primitives#

The core ships all of the primitive operations below, with the derivative maps they carry; those on Euclidean quantities first. In the table, ⊙\odot is the elementwise product, va\tangent_a the velocity of an input aa in a Hessian pass (or its tangent, in the tangent map), and α\alpha the cotangent at the output. Each frame is handled column by column, so one column is shown.

operationtangentcotangent per inputsecond order per input
a+ba + bva+vb\tangent_a + \tangent_bα, α\alpha,\ \alpha0, 00,\ 0
a−ba - bva−vb\tangent_a - \tangent_bα, −α\alpha,\ -\alpha0, 00,\ 0
−a-a−va-\tangent_a−α-\alpha00
a⊙ba \odot bva⊙b+a⊙vb\tangent_a \odot b + a \odot \tangent_bb⊙α, a⊙αb \odot \alpha,\ a \odot \alphavb⊙α, va⊙α\tangent_b \odot \alpha,\ \tangent_a \odot \alpha
s as\,a, s∈Rs \in \Rvs a+s va\tangent_s\, a + s\, \tangent_aa⊤α, s αa\transpose \alpha,\ s\, \alphava⊤α, vs α\tangent_a\transpose \alpha,\ \tangent_s\, \alpha
ABA BvAB+AvB\tangent_A B + A \tangent_BαB⊤, A⊤α\alpha B\transpose,\ A\transpose \alphaαvB⊤, vA⊤α\alpha \tangent_B\transpose,\ \tangent_A\transpose \alpha
∑iai\sum_i a_i∑i(va)i\sum_i (\tangent_a)_iα1\alpha \mathbf 100
eae^{a} (elementwise)ea⊙vae^{a} \odot \tangent_aea⊙αe^{a} \odot \alphaea⊙va⊙αe^{a} \odot \tangent_a \odot \alpha
log⁡a\log a (elementwise)va/a\tangent_a / aα/a\alpha / a−va⊙α/a2-\tangent_a \odot \alpha / a^2
slice, concatenate, transposethe same mapits adjoint00
select(5)to the selected value00

The operations on positive quantities read their tangents and cotangents in the charts (2), that is, in logarithmic coordinates, where all but the inclusion are linear. Here aa and bb are positive, cc is Euclidean, and the products, quotients and powers are elementwise.

operationfrom, totangentcotangent per inputsecond order per input
aa as a real(0,∞)n→Rn(0, \infty)^n \to \R^na⊙vaa \odot \tangent_aa⊙αa \odot \alphaa⊙va⊙αa \odot \tangent_a \odot \alpha
ece^{c}Rn→(0,∞)n\R^n \to (0, \infty)^nvc\tangent_cα\alpha00
log⁡a\log a(0,∞)n→Rn(0, \infty)^n \to \R^nva\tangent_aα\alpha00
a⊙ba \odot b(0,∞)n→(0,∞)n(0, \infty)^n \to (0, \infty)^nva+vb\tangent_a + \tangent_bα, α\alpha,\ \alpha0, 00,\ 0
1/a1 / a(0,∞)n→(0,∞)n(0, \infty)^n \to (0, \infty)^n−va-\tangent_a−α-\alpha00

The exponential and logarithm here are not the rows of the first table: those map Rn\R^n to Rn\R^n, read in the flat charts, where their derivatives are eae^{a} and 1/a1/a; these change type, and in the charts of the positive side their derivatives are identities. Both are kept, because a model may want either. The retraction (2) is the composite x⊙ev\point \odot e^{\tangent} of the second and fourth rows, and its inverse log⁡y−log⁡x\log \otherPoint - \log \point of the third and a Euclidean subtraction, so every derivative of both, of either order, is an identity or zero.

Every row is checked the same way: the tangent map is the derivative of the forward map, the cotangent map its transpose, and the second-order map the derivative of the cotangent map along the velocities, holding α\alpha fixed. The bilinear operations (a⊙ba \odot b, s as\,a, ABA B) are the only ones whose second-order maps mix inputs: the pull-back into one input involves the other, so the other’s velocity appears. The linear ones have none. The discrete operations the core needs (integer arithmetic and comparison) and the embedding of an integer as a real carry no derivatives, by the conventions of Operations.

Programs, composites and passes#

A model is evaluated by compiling a program from the compilation graph, as in The compilation graph. Four passes run over a compiled program: the forward pass, the tangent pass (2) along the edges of its computation graph, the cotangent pass (3) along the edges of its backpropagation graph, and the Hessian pass (4), which runs both. Derivatives are collected at the program’s roots only, which are the leaves of its backpropagation graph, and Queries says which roots those are.

A composite operation is a named subgraph of the compilation graph, such as the retraction of a product, or (in later entries) a distribution’s log-density: a set of operations with designated inputs and one output, stored under one name. Its derivative maps are defined as the composition of its parts’, so expanding a composite into its parts at compilation changes no value that any pass computes, and its order is that of its parts under Definition 4. A hand-written fused form of a composite, introduced for speed or for numerical care, is a different operation with the same mathematics, and it is correct only if every pass over it agrees with every pass over the composite, to within rounding.

The product retraction is the composite the charter’s acceptance test is about.

Proposition 6(The composite product retraction agrees with its factors).

Let a product’s retraction be built as the composite of slicing the point and the tangent into blocks, applying each factor’s retraction to its blocks, and concatenating the results. Then every pass over a program containing it (values, tangent frames, cotangent frames, and Hessian products) computes the same values as the factor-wise computation, block by block.

Proof.

Slicing and concatenation are linear, so their tangent maps are themselves, their cotangent maps are their adjoints (scattering into a zero block and gathering a block), and their second-order maps are zero. In the tangent pass, slicing hands each factor’s retraction exactly its blocks of the frames, and concatenation stacks what they return. In the cotangent and Hessian passes, the concatenation’s adjoint hands each factor exactly its blocks of α\alpha and β\beta, the factors pull them back, and the slicing’s adjoint places each result in its own block, adding only zeros. By (4), with the linear steps’ hvp⁡\hvp zero, the product’s β\beta is the factors’ β\betas, stacked. So every pass computes the block-diagonal assembly of the factors’ passes, which is the derivative structure of (3).

∎

The proof introduces no arithmetic beyond additions of zero, so a test may hold the agreement to a tolerance of a few units in the last place.

The acceptance test#

The charter accepts its position on products on one condition: that the retraction of a runtime product, and its first and second derivatives, agree with the factor-wise computation. On the core’s own types that is a weak test at second order. In the charts of every one of them a retraction is a translation, so every second-order map along it is zero, and the second derivatives under test are zeros on both sides. The test is therefore run on a product that includes a curved factor, and composed with a function whose second derivative does not vanish.

A test-only sphere. The core’s tests carry a private type, never shipped: the unit sphere S2={x∈R3:∥x∥=1}S^2 = \{\point \in \R^3 : \lVert \point \rVert = 1\}, of dimension 22 and storage size 33. Its tangent plane at x\point is x⊥\point^\perp, identified with R2\R^2 by a matrix B(x)B(\point) whose two orthonormal columns span it. No such BB is smooth on the whole sphere (a smooth field of tangent bases on S2S^2 would give a nowhere-vanishing tangent vector field, which the hairy ball theorem rules out), so the identification is smooth on an open part of the sphere, and the tests stay inside it. Its retraction projects back onto the sphere:

x⊕v=x+B(x) v∥x+B(x) v∥,y⊖x=B(x)⊤yx⊤y(x⊤y>0).(7) \point \oplus \tangent = \frac{\point + B(\point)\,\tangent}{\lVert \point + B(\point)\,\tangent \rVert}, \qquad \otherPoint \ominus \point = \frac{B(\point)\transpose \otherPoint}{\point\transpose \otherPoint} \quad (\point\transpose \otherPoint > 0) . \tag{7}
(7)

The inverse follows from x⊤B(x)=0\point\transpose B(\point) = 0: the point y\otherPoint is (x+B(x)v)/n(\point + B(\point)\tangent)/n with nn the norm, so x⊤y=1/n\point\transpose \otherPoint = 1/n and B(x)⊤y=v/nB(\point)\transpose \otherPoint = \tangent / n. The derivative of the retraction at v=0\tangent = 0 is B(x)v−x (x⊤B(x)v)=B(x)vB(\point)\tangent - \point\,(\point\transpose B(\point)\tangent) = B(\point)\tangent, whose coordinates are v\tangent, so it is a retraction. Unlike the core’s types, the sphere is curved: its tangent charts do not commute, the correction term of Proposition 5 is not zero away from stationary points, and the retraction’s second-order maps are not zero. Its retraction is a composite of three private primitives with hand-written derivatives in charts: the inclusion S2⊂R3S^2 \subset \R^3, the map (x,v)↦B(x)v(\point, \tangent) \mapsto B(\point)\tangent, and the normalization w↦w/∥w∥w \mapsto w / \lVert w \rVert from R3∖{0}\R^3 \setminus \{0\} onto the sphere.

What it compares. The test builds a runtime product of two Euclidean factors, a real line, a positive factor and the sphere, and a nonlinear scalar function ff on it that reads the positive factor and the sphere as real numbers, through their inclusions. It checks, at points away from the singular set of BB:

  1. that every pass over the product’s retraction, values, tangent frames, cotangent frames and Hessian products, agrees with the factor-wise computation, block by block, as Proposition 6 says it must;
  2. that the chart gradient and the chart Hessian products of ff through the product’s retraction, computed by the passes, agree with the factor-wise assembly of (3), and with finite differences of their definitions (3) and (4);
  3. that on the sphere’s block, away from a stationary point, the chart Hessian differs from the Hessian of the pull-back by the correction term, and on every other block it does not.

The private primitives’ own derivatives are checked the same way first, against finite differences of (1), so that a failure of the acceptance test points at the composition rather than at a primitive.

Queries#

The charter puts conditioning in the query: the model holds the joint density (1), and each question asked of it says which quantities are fixed. A query partitions the quantities its targets reach in the compilation graph into

  • the observed quantities xO\point_O, fixed at values the query gives;
  • the free continuous quantities xF\point_F, the variables of the query;
  • the free discrete quantities kD\discretePoint_D, which a generation draws, and whose values any other query gives and holds fixed.

The query’s log-density is then a function of xF\point_F alone,

log⁡p(xF; xO,kD)=∑tℓt(xF,xO,kD),(8) \log \density(\point_F;\, \point_O, \discretePoint_D) = \sum_t \logterm_t(\point_F, \point_O, \discretePoint_D), \tag{8}
(8)

which is, up to the constant log⁡p(xO,kD)\log \density(\point_O, \discretePoint_D), the log-density of the conditional distribution of xF\point_F given the rest. One model answers many queries: with the measurements observed it is a posterior; with nothing observed, a prior to simulate from; with the states observed, a likelihood of measurements.

Roots. The roots of a query’s program, the roots of its computation graph, are the quantities and constants its targets reach. Of these, the free continuous quantities are the ones every pass differentiates with respect to, in their tangent charts; the observed and discrete quantities and the constants are fixed inputs. A quantity’s value is an input of the query even when the quantity was drawn from a distribution whose parameters depend on other quantities, since it is a root of the compilation graph: ∂log⁡p/∂xi\partial \log \density / \partial \point_i counts xi\point_i’s appearances in the terms, never its role in generating its children.

Folding. A mapped value whose inputs are all fixed is itself fixed, and need not be computed on every evaluation: it can be computed once and put in place of the subgraph that computes it, as a new root. Nothing a pass computes changes, since no derivative flows into a fixed value. A value is fixed at one of two levels, and is folded at that level.

  • In the model. A value reached only from constants is the same in every query, snapshot and branch, so it is folded once, when it is added to the compilation graph, and the model keeps the result, in double precision on the host like every value it stores. A query casts it to its own precision like any other constant. Conditioning in the query does not prevent this: a constant is not a quantity, so no query can observe or free it.
  • In a compiled query. A value reached from observed quantities, and from constants, is fixed only for the queries that observe those quantities, so it is folded when such a query is compiled, and reused by every evaluation of the compiled program.

A value reached from a free quantity is not folded. The difference shows in a Gaussian term written with a covariance factor LL, whose log-density reads its inverse and the logarithms of its diagonal (a later entry decides how the Gaussian takes its covariance). If LL is a constant, both are folded once in the model; if it is an observed quantity, once per compiled query; if it is free, they are computed on every evaluation, together with their derivatives with respect to LL.

Discrete quantities in a query. The gradient of (8) at fixed kD\discretePoint_D is the gradient of the conditional density of xF\point_F given kD\discretePoint_D. An algorithm that targets the distribution of xF\point_F alone, with kD\discretePoint_D unobserved, needs kD\discretePoint_D summed or sampled out, which a gradient cannot do; that is why the charter has a gradient-based algorithm refuse a query with a free discrete quantity, rather than silently condition on the value it was given.

Flattened coordinates. An estimator works with vectors, not with quantities. Ordered as the estimator chooses, the free quantities xF=(x1,…,xn)\point_F = (\point_1, \dots, \point_n) are one point of the product manifold X1×⋯×Xn\manifold_1 \times \cdots \times \manifold_n, a tangent of which is a vector v∈Rd\tangent \in \R^\dimension, d=∑idi\dimension = \sum_i \dimension_i, stacked by (1). An estimator’s step is that product’s retraction,

xF⊕v=(x1⊕v1,…,xn⊕vn),(9) \point_F \oplus \tangent = (\point_1 \oplus \tangent_1, \dots, \point_n \oplus \tangent_n), \tag{9}
(9)

and a Jacobian, gradient or Hessian over xF\point_F is the block matrix of (3). So the layout an estimator uses and a product quantity type are the same mathematical object, and the core should build them from one construction.

Terms and the importance weight#

A term tt is a node of the probabilistic graph. Its log-density ℓt\logterm_t is a node of the compilation graph, computed from the quantities it is reached from there. Following the charter, a term records the quantity it scores, sts_t, if any, and the quantities it is conditioned on, CtC_t, so that ℓt\logterm_t reads as log⁡pt(st∣Ct)\log \density_t(s_t \mid C_t). The quantities a term involves, Vt={st}∪CtV_t = \{s_t\} \cup C_t, its neighbours in the probabilistic graph, determine every structural question the core answers: which terms touch a quantity, what a quantity’s Markov blanket is, and what retiring it disturbs. They are only right if they contain every quantity ℓt\logterm_t depends on, so the core reads VtV_t off the compilation graph, as the quantities among the roots ℓt\logterm_t is reached from, rather than trusting a declaration; CtC_t is then VtV_t without sts_t. In the example, ℓ4\logterm_4 is reached from x0\point_0, mm and z0z_0, and the term t4t_4 scores z0z_0, so it is conditioned on x0\point_0 and mm.

The generative reading. A distribution may provide a sampler for the quantity it scores, which draws sts_t given CtC_t, with a log-density log⁡qt(st∣Ct)\log \proposal_t(s_t \mid C_t) of its own; an exact sampler has qt=pt\proposal_t = \density_t (charter, position 9). A query generates its free quantities, continuous and discrete, ancestrally: each from the sampler of the term that scores it, in a topological order of the probabilistic graph. In this section, xF\point_F stands for all of them. Generation is possible exactly when every free quantity is scored by a term with a sampler; it is refused otherwise, naming a quantity that is not. The importance weight of a generated point is

log⁡w(xF)=∑tℓt(xF,xO)−∑t : st∈Flog⁡qt(st∣Ct),(10) \log \weight(\point_F) = \sum_t \logterm_t(\point_F, \point_O) - \sum_{t \,:\, s_t \in F} \log \proposal_t(s_t \mid C_t), \tag{10}
(10)

which is the charter’s (1), with the observed quantities’ terms and the likelihood factors, the likelihoods of this query, contributing ℓt\logterm_t alone, and each term whose sampler is exact cancelling.

Proposition 11(Importance weights).

Let a query leave the quantities xF\point_F free and fix xO\point_O, and suppose that every free quantity is scored by exactly one term, whose sampler has density qt\proposal_t with qt>0\proposal_t > 0 wherever pt>0\density_t > 0, each pair with respect to the same base measure. If xF\point_F is drawn ancestrally from the samplers, then for every integrable hh,

Eq[w(xF) h(xF)]=∫h(xF) p(xF,xO) dxF=p(xO)  E[h(xF)∣xO]. \E_\proposal\bigl[\weight(\point_F)\, h(\point_F)\bigr] = \int h(\point_F)\, \density(\point_F, \point_O)\, \dd \point_F = \density(\point_O)\; \E\bigl[h(\point_F) \mid \point_O\bigr] .
Proof.

Ancestral generation draws xF\point_F with density q(xF)=∏t:st∈Fqt(st∣Ct)\proposal(\point_F) = \prod_{t : s_t \in F} \proposal_t(s_t \mid C_t) with respect to the product of the base measures, since each quantity is drawn given quantities drawn or fixed before it. Then w=p(xF,xO)/q(xF)\weight = \density(\point_F, \point_O) / \proposal(\point_F) wherever q>0\proposal > 0, which covers the support of p(⋅,xO)\density(\cdot, \point_O) by the positivity condition, and Eq[wh]=∫h p(xF,xO) dxF\E_\proposal[\weight h] = \int h\, \density(\point_F, \point_O)\, \dd \point_F.

∎

Two conditions in the statement are the core’s to check or to state. The base measures must match. In the core, a continuous quantity’s density is with respect to Lebesgue measure on the space its values lie in, Rn\R^n or the open subset (0,∞)n(0, \infty)^n of it, and a discrete quantity’s with respect to counting measure, so this holds as long as every distribution keeps to that convention. The one place it is easy to break is a density written in the logarithmic chart of a positive quantity, such as a log-normal written as a normal in u=log⁡xu = \log \point: with respect to Lebesgue measure on (0,∞)n(0, \infty)^n, it carries the Jacobian of the chart,

log⁡px(x)=log⁡pu(log⁡x)−∑ilog⁡xi.(12) \log \density_{\point}(\point) = \log \density_u(\log \point) - \sum_i \log \point_i . \tag{12}
(12)

Densities on manifolds without such a chart will need a base measure of their own when the first of them arrives. Positivity is the distribution’s responsibility, and the core cannot check it.

Random keys#

Every draw comes from a key (charter, position 10). Mathematically a key is an element of a finite set K\mathcal K, with two deterministic maps: split⁡:K→Kn\operatorname{split} : \mathcal K \to \mathcal K^n, and fold⁡:K×Σ→K\operatorname{fold} : \mathcal K \times \Sigma \to \mathcal K, which folds in one address segment from the set Σ\Sigma of names and integers. The key of the quantity at address a=a1/⋯/ama = a_1 / \cdots / a_m in a run with key ξ\key is

ξa=fold⁡(⋯fold⁡(fold⁡(ξ,a1),a2)⋯ ,am),(13) \key_a = \operatorname{fold}(\cdots \operatorname{fold}(\operatorname{fold}(\key, a_1), a_2) \cdots, a_m), \tag{13}
(13)

and its sampler turns ξa\key_a into the uniform variates it consumes. Because ξa\key_a depends on ξ\key and aa alone, a quantity’s draw does not depend on the order in which the model was built, on what was retired before it, or on which branch holds it, and a namespace’s keys share the prefix of folds that the namespace’s own address determines.

The independence of draws rests on an idealization: that fold⁡\operatorname{fold} and split⁡\operatorname{split} behave as a pseudorandom function, so that distinct addresses yield keys indistinguishable from independent uniform ones. Two things the core must ensure for that to mean anything. Distinct addresses must give distinct inputs: each segment’s encoding must be injective and tagged by its kind, so that the name "3" and the integer 33 differ, and folding segment by segment, rather than hashing a concatenated string, rules out collisions such as ab/c against a/bc. And each batch row of a run must have its own run key, split from the run’s, so that rows are independent.

Because the uniform variates are drawn on the host from the key and then converted to the precision a query computes in, one key gives the same draws at single and double precision, up to the rounding of the conversion.

Retirement#

Retiring is the one change that shrinks a model: the quantities under a namespace are removed, and what they said about the rest is kept. Retirement is read on the probabilistic graph. Let EE be the quantities to retire, TE\mathcal T_E the terms joined to any of them (Vt∩E≠∅V_t \cap E \neq \emptyset), and

B=(⋃t∈TEVt)∖E(14) B = \Bigl(\bigcup_{t \in \mathcal T_E} V_t\Bigr) \setminus E \tag{14}
(14)

their Markov blanket: the other quantities joined to those terms. In the example, retiring the first tick, E={x0,z0}E = \{\point_0, z_0\}, touches the terms t1t_1, t3t_3 and t4t_4, and its blanket is {x1,m}\{\point_1, m\}.

Retirement must condition as well as marginalize. The retired namespace of a tick holds that tick’s measurements as well as its states, and integrating the measurements out of the joint would discard exactly the information a sliding window keeps them for. So the quantities of EE split into those fixed at given values, EOE_O (usually the measurements, with the values the estimator observed), and those integrated out, EFE_F. Since conditioning belongs to the query, the values of EOE_O are given to the retirement itself, and once retired they become part of the model: what remains is the joint of the remaining quantities given those values.

Proposition 15(Retirement leaves the rest of the model unchanged).

Let EE be the quantities to retire, of which EOE_O are fixed at given values and EFE_F are integrated out, let TE\mathcal T_E be the terms joined to a quantity of EE in the probabilistic graph, and let BB be the other quantities joined to those terms. Replace the terms TE\mathcal T_E by the single term

ℓB(xB)=log⁡∫exp⁡(∑t∈TEℓt(xEF,xEO,xB)) dxEF. \logterm_B(\point_B) = \log \int \exp \Bigl(\sum_{t \in \mathcal T_E} \logterm_t\bigl(\point_{E_F}, \point_{E_O}, \point_B\bigr)\Bigr)\, \dd \point_{E_F} .

Then, as a function of every quantity outside EE, the new model’s density equals the old model’s density with EFE_F integrated out and EOE_O fixed at its values.

Proof.

Split (1) into the terms of TE\mathcal T_E, which involve only EE and BB, and the rest, which involve no quantity of EE. Fixing EOE_O and integrating over EFE_F, the second group comes out of the integral as a constant factor, by Tonelli’s theorem for the non-negative integrand, and what remains of the first group is exp⁡ℓB(xB)\exp \logterm_B(\point_B).

∎

The replacement must be computed from the terms TE\mathcal T_E alone. Computing the marginal of the whole model on BB instead would fold in the terms outside TE\mathcal T_E, which the model still holds, and count them twice. The integral defining ℓB\logterm_B rarely has a closed form. It has one for linear-Gaussian terms, where retiring the oldest state of a chain is the update-and-predict step of a Kalman filter; otherwise it is approximated, by a Laplace approximation at the current estimate in the archive’s sliding windows. Either way, computing it needs the distributions and the linear algebra of later entries; what the core needs is the structure: EE, TE\mathcal T_E, BB, the removal, and a place for the replacement, a likelihood factor over BB whose type, when it is one density over the blanket’s tangent coordinates jointly, is the product of the blanket’s types.

Two consequences for the rest of the model:

  • Mapped values. In the compilation graph, a mapped value under the retired namespace that a remaining node is reached from, and that is reached only from remaining roots, is not a function of EE and must not be removed with it. One reached from EE can only reach terms in TE\mathcal T_E, which are replaced, and other mapped values reached from EE, which go with it.
  • Generation after retirement. A blanket quantity whose scoring term was in TE\mathcal T_E (x1\point_1 in the example, whose transition t3t_3 was conditioned on x0\point_0) is scored by no term afterwards, only joined to the replacement. Unless the replacement is written in a generative form, as a term scoring that quantity with a sampler, generation from the new model is refused for it, by structure.

In skoro’s terms#

  • Quantities are Euclidean (real, vector, matrix), positive ((0,∞)n(0, \infty)^n, with the retraction (2)), and discrete (categorical, boolean, integer), and products of continuous quantities. Dimensions are fixed when a quantity is created, and every derivative coordinate is indexed by tangent dimension.
  • The compilation graph holds values and operations, and programs are compiled from it; the probabilistic graph holds quantities and terms, and is read off the compilation graph and each term’s scored quantity.
  • Operations are maps (3) with up to four derivative maps over frames, each with an order. The core’s primitives are those of The primitives, and every other operation, the product’s retraction included, is a composite of them.
  • Folding replaces a value reached only from fixed inputs with a root: in the model when they are constants, in a compiled query when they include observed quantities.
  • Passes are the forward, tangent, cotangent and Hessian passes over a compiled program, differentiating with respect to a query’s free continuous quantities in their charts, refused before running when not admissible by Definition 4.
  • The density is the sum of terms (1). A query fixes the observed quantities and the discrete ones, and its density is (8). Generation is ancestral from samplers, and its weight is (10).
  • Random keys are derived from addresses by (13).
  • Retirement removes a namespace’s quantities EE, conditioning on the given values of EOE_O and integrating out EFE_F, and replaces the terms TE\mathcal T_E with one term over the blanket (14).

Appendix: the probability space of a model#

A skoro model is a model built forward in the sense of models built forward, for the measure theorist, and every object in this document has a place in that note’s measure theory.

  • Quantities are the coordinates. Each quantity lives in a space with a reference measure: Lebesgue measure on Rn\R^n and Rm×n\R^{m \times n} and on the open subset (0,∞)n(0, \infty)^n, counting measure on Categorical(n)\mathrm{Categorical}(n) and on Z\mathbb Z, and product measures on products. The probability space can be taken to be the product of the quantities’ spaces, with the quantities as its coordinates, and the probabilistic graph’s ancestral order as the order in which they were built.
  • Terms are conditional densities. A term that scores a quantity has, as its exp⁡ℓt\exp \logterm_t, that quantity’s conditional density given those it is conditioned on, so (1) is the logarithm of the joint density of Proposition 5. A likelihood factor is the change of measure of Proposition 10.
  • Mapped values are functions of the quantities, and by Lemma 7 they are all the random variables a model can express. Their laws are image measures, with no density in general, which is why no term scores one.
  • A query conditions by dividing densities. Its log-density (8) is that of the regular conditional distribution of Proposition 9, up to the constant log⁡pO\log \density_O, which is why a query may drop it.
  • Every number an algorithm estimates is an integral against the joint density, by Corollary 8: an expectation, a marginal, the evidence pO\density_O. Generation draws from the conditional densities in the order the model was built, which is Ionescu-Tulcea’s construction, and importance weighting (10) estimates such an integral from those draws.
  • Retirement integrates and conditions. Proposition 15 integrates the retired free quantities out of the terms that touch them and conditions on the retired observed ones, which is the same marginalization and the same division of densities, carried out once and kept.

The test-only sphere has no reference measure in this sense, since it is not an open subset of a coordinate space; it carries no density in the core’s tests. Manifold-valued quantities beyond the positive orthant will need reference measures of their own (surface measure on a sphere, Haar measure on the rotations) when they arrive.

Open questions#