implementation

Sources: code: statistical_game.jl

Theory (CT-ML wiki): Lax Functor · Bayesian Inversion · Statistical Game · Para Construction · Variational Free Energy · Lens

Implements: Factors are Parameterized Statistical Games (Def. 20), Composition of Statistical Games (Defs. 22, 25), Factors are Parameterized Statistical Games (Defs. 27, 28) — i.e. the factor interface itself.

The composition laws, side by side

AutoBayes Def. 22here
energycompose_energy → a GradedEnergy pair
entropycompose_entropy, same shape, into a summand
lossThm 23: compose_free_energy, same
defect—chain_rule_defect = the Jensen gap

The paper’s + for energies has become a direct sum. Everything else is unchanged in shape. Applying a GradedScalarisation recovers the paper exactly when is linear, and differs by chain_rule_defect when it is convex. See Scalar and Multivariate Energy.

Two adjectives that are the whole content of Definition 22

compose_entropy(Hc_samples, Hd) takes samples of at drawn from the downstream inversion, at the pushforward prior. Get either adjective wrong and you have a plausible-looking number that is not the free energy. The docstring says so; there is no way to check it from inside this function, because it receives the samples already drawn. The correctness burden sits in the caller — i.e. in the graph walker that does not exist yet. Flagging it now so it is not discovered later.

ComposedFactor is a ContainerFactor

struct ComposedFactor{A,B,C} <: AbstractLenticulumContainerFactor{(:first, :second)}

so initialparameters produces (; first = ..., second = ...) — the product of Definition 28, realised as literally the same NamedTuple structure Lux uses for Chain. That is not a coincidence worth hiding; it is the clearest evidence that both libraries are doing Para.

Inheriting the LuxCore interface

The for op in (:initialparameters, :initialstates) loop extends LuxCore.$op for the three factor abstract types, mirroring LuxCore’s own definitions. Deliberately not subtyping AbstractLuxLayer — see abstract_types §“Why factors are not a subtype”.

Implementation difficulties

1. Almost everything here is a stub, and that is the honest state

free_energy, energy, entropy, assemble, invert, forward, pushforward are function f end declarations. The composition combinators are real and tested; the things they combine are not implemented, because there is no concrete factor yet.

This is the right place to stop, but it means the design is currently unfalsified by contact with a real model. The next step should be one worked example end to end — a Gaussian factor with an exact inversion, composed with a prior, reproducing Example 1’s claim that — rather than more abstraction.

2. compose_entropy and compose_free_energy take a plain collection of samples

They average with reduce(+, samples) * (1/length(samples)), which assumes an unweighted Monte Carlo estimate. A weighted particle set, a quadrature rule, or an analytic expectation in the conjugate case all need something else. The signature should eventually take a belief plus a functional, not a vector of evaluations — but that requires the belief interface that open_model §4 says is still an open question. Interim: the current signature is adequate for sampling-based inversions and wrong for the others.

3. TensorFactor has no energy/entropy law implemented

Definition 25 says both halves add across summands. TensorFactor currently only derives energyspace and scalarisation; there is no compose_tensor_energy. Straightforward, but absent — and it inherits the mean-field laxness of lens §3, so it should not be added without deciding how to report that.

4. chain_rule_defect measures one edge, not a path

chain_rule_defect(σ, Fc_samples) gives the Jensen gap across a single composition. For a chain of factors the total discrepancy is a sum of per-edge gaps, each taken under a different downstream posterior — it does not telescope into one variance. Any “total laxness” diagnostic must accumulate per edge during the walk, not be computed once at the end. Currently there is no walker, so this is a note for whoever writes it.

5. setup is defined here rather than re-exported

LuxCore.setup would work verbatim, but defining LenticulumCore.setup for AbstractLenticulumFactor keeps the factor interface self-contained and avoids a user needing using LuxCore to set up a factor. The cost is a name that shadows nothing but duplicates something. Reconsider if Lenticulum.jl ends up re-exporting LuxCore anyway.

6. AbstractGradientCoupling is declared but unused

The type exists and ComposedFactor stores one, but nothing dispatches on it — the gradient machinery is not written. It is here so that the edge annotation is part of the data model from the start rather than retrofitted, since retrofitting a per-edge choice into a global one is much harder than the reverse. But it is currently inert, and should not be mistaken for a working feature.

Related: Factors are Parameterized Statistical Games, Composition of Statistical Games, Composition of Gradients, energy, LenticulumCore