BIFM

Overview

BIFM is one time slice of a linear state-space model with known matrices, as a single deterministic node: the previous state and an input make the next state, which makes the output. A chain of them, started by BIFMHelper and ended by a flat prior on the last state, is smoothed in two passes.

The backward pass sends information-form messages, in weighted-mean and precision form, towards the earlier states. At the start of the chain BIFMHelper turns it into the forward pass, whose messages are the marginals themselves. On a chain the result is the Rauch–Tung–Striebel smoother's.

Definition

\[z_{next} = A z_{prev} + B\, u, \qquad out = C z_{next}\]

A is the state transition, B the input matrix and C the output matrix; the node's algorithm carries all three.

The backward message towards zprev, from the messages on out, in and znext, in weighted-mean and precision form:

\[\begin{aligned} \xi_z &= C^\top \xi_{out} + \xi_{znext}, & \Lambda_z &= C^\top \Lambda_{out} C + \Lambda_{znext}, \\ H &= (\Lambda_{in} + B^\top \Lambda_z B)^{-1}, & \tilde\xi_z &= \xi_z + \Lambda_z B H (-\xi_{in} - B^\top \xi_z), \\ \tilde\Lambda_z &= \Lambda_z (I - B H B^\top \Lambda_z), & m_{zprev} &= \mathcal{N}^{-1}\big(A^\top \tilde\xi_z,\; A^\top \tilde\Lambda_z A\big). \end{aligned}\]

The forward rules take the marginal of zprev, N(μ, Σ), and recompute these quantities from the messages on their own edges. With F = I - Λ_z B H Bᵀ, the marginal of znext is N(Fᵀ A μ + B (Σ_in Bᵀ ξ̃_z + μ_in), Fᵀ A Σ Aᵀ F + B H Bᵀ) and the marginal of out its image under C; the marginal of in follows Wadehn (2016), On sparsity by NUV-EM, in the dual parameterisation.

Interfaces

using BIFMMessagePassingRules, MessagePassingRulesBase, BayesBase, ExponentialFamily
MessagePassingRulesBase.nodespec(BIFM)
NodeSpec: BIFM
inzprevznextoutBIFMdeterministic
interfacealiases
out
in
zprev
znext
default algorithmMessagePassingRulesBase.DefaultAlgorithm()
static inputsnone
definedsrc/bifm.jl:136

The node has four interfaces.

namemeaningmessages the rules take
outthe output, C znextMvNormal
inthe input uMvNormal
zprevthe previous stateTerminalProdArgument of an MvNormal, the forward pass's marginal
znextthe next stateMvNormal, possibly with zero precision at the end of the chain

Towards zprev the rule sends an MvNormal in weighted-mean and precision form; towards in, out and znext a TerminalProdArgument of an MvNormal, which the engine takes as the marginal without a product.

Algorithm

BIFMMessagePassingRules.BIFMSmoother — Type
BIFMSmoother(A, B, C)

The algorithm of BIFM: the matrices of znext = A zprev + B in and out = C znext, converted to a common element type. It keeps nothing between calls, so one smoother may serve every node of a chain; the statistics of the input come from the message on in. The node declares no algorithm of its own, so a model must give this one.

Arguments

  • A: the d × d state transition;
  • B: the d × k input matrix, for a k-dimensional input;
  • C: the p × d output matrix, for a p-dimensional output.

Throws

A DimensionMismatch when A is not square, B has not as many rows as A, or C has not as many columns as A has rows.

Examples

julia> using BIFMMessagePassingRules

julia> BIFMSmoother([1.0 0.0; 0.0 1.0], reshape([1.0, 0.0], 2, 1), [1.0 0.0]).C
1×2 Matrix{Float64}:
 1.0  0.0
source

BIFMSmoother is the node's algorithm. The node declares no algorithm of its own, so a model gives every BIFM node a BIFMSmoother. Under the default one, DefaultAlgorithm, no rule is found.

Under BIFMSmoother the node declares its dependencies, the inputs each target's rule takes:

MessagePassingRulesBase.dependencies_spec(BIFM, BIFMSmoother([1.0;;], [1.0;;], [1.0;;]))
DependenciesSpec: BIFMunder BIFMSmoother
defaultm[:out]outBIFMdefaultm[:in]inBIFMdefaultzprevBIFMdefaultm[:znext]znextBIFM
━▶ target─▶ message m··· the default scheme's inputs
free-energy partitionfrom the factorisation

Every target takes the messages of the default scheme. The forward rules, towards out, in and znext, also read the message on their own edge, to recompute the backward quantities. They keep only working memory between the steps of one call.

Supported rules

MessagePassingRulesBase.rule_coverage(BIFM)
Rule coverage for BIFM
DefaultAlgorithmBIFMSmoother
→ out✓
→ in✓
→ zprev✓
→ znext✓
q(in, zprev, znext)✓

Every message rule is belief propagation on the messages. The marginal rule for q(in, zprev, znext), the joint that the free energy of a deterministic node reads, exists only to throw a BIFMFreeEnergyError. There is no average energy, and no free energy.

Example

The backward message of a scalar random walk observed directly: the message on out is N(1, 1), the input's N(0, 1), and nothing is known of the next state, so the message on zprev is N(1, 2).

@call_message_update_rule(
    node = BIFM, target = :zprev, algorithm = BIFMSmoother([1.0;;], [1.0;;], [1.0;;]),
    m = (
        out = MvNormalMeanPrecision([1.0], [1.0;;]),
        in = MvNormalMeanPrecision([0.0], [1.0;;]),
        znext = MvNormalWeightedMeanPrecision([0.0], [0.0;;]),
    ),
)
RuleResult: message of BIFM towards :zprevmessages
outinznextzprevBIFM
Result
valueMvNormalWeightedMeanPrecision( xi: [0.5] Λ: [0.5;;] )
typeExponentialFamily.MvNormalWeightedMeanPrecision{Float64, Vector{Float64}, Matrix{Float64}}
log scaleundefined: the message rule for BIFM towards :zprev under BIFMSmoother declares no `logscale`
Inputs
edgevalue
outmMvNormalMeanPrecision( μ: [1.0] Λ: [1.0;;] )
inmMvNormalMeanPrecision( μ: [0.0] Λ: [1.0;;] )
znextmMvNormalWeightedMeanPrecision( xi: [0.0] Λ: [0.0;;] )
Rule
declared inputsm[:out]::Union{ExponentialFamily.MultivariateNormalDistributionsFamily{T}, ExponentialFamily.MultivariateGaussianDistributionsFamily{T}} where T, m[:in]::Union{ExponentialFamily.MultivariateNormalDistributionsFamily{T}, ExponentialFamily.MultivariateGaussianDistributionsFamily{T}} where T, m[:znext]::Union{ExponentialFamily.MultivariateNormalDistributionsFamily{T}, ExponentialFamily.MultivariateGaussianDistributionsFamily{T}} where T
algorithmBIFMSmoother{Float64}([1.0;;], [1.0;;], [1.0;;])
scratch(ξz = [1.0], Λz = [1.0;;], BHBt = [0.5;;], ξ̃z = [0.5], Λ̃z = [0.5;;], CtΛ = [1.0;;], BH = [0.5;;], z = [-0.5], zz = [0…
log scalenone
definedsrc/bifm.jl:189
body
(scratch, algo, args)->begin
        s = bifm_backward!(scratch, algo, args.m[:out], args.m[:in], args.m[:znext])
        A = algo.A
        T = promote_samplefloattype(args.m[:out], args.m[:in], args.m[:znext])
        convert(MvNormalWeightedMeanPrecision{T}, MvNormalWeightedMeanPrecision(A' * s.ξ̃z, A' * s.Λ̃z * A))
    end

@call_message_update_rule runs the rule and draws the three messages it read. The result is in weighted-mean and precision form, and getresult and mean_cov read it as N(1, 2):

julia> using BIFMMessagePassingRules, MessagePassingRulesBase, BayesBase, ExponentialFamily

julia> result = @call_message_update_rule(
           node = BIFM, target = :zprev, algorithm = BIFMSmoother([1.0;;], [1.0;;], [1.0;;]),
           m = (
               out = MvNormalMeanPrecision([1.0], [1.0;;]),
               in = MvNormalMeanPrecision([0.0], [1.0;;]),
               znext = MvNormalWeightedMeanPrecision([0.0], [0.0;;]),
           ),
       );

julia> m, V = mean_cov(getresult(result));

julia> m ≈ [1.0] && V ≈ [2.0;;]
true

Limitations

  • No free energy: the joint marginal of the inputs and the average energy of BIFMHelper throw a BIFMFreeEnergyError.
  • Multivariate normal messages only, and the forward rules need the marginal of zprev as a TerminalProdArgument, so the chain must start with BIFMHelper.
  • The matrices are known: BIFMSmoother carries them, and nothing learns them.

API

The node, and the error that asking for its free energy throws.

BIFMMessagePassingRules.BIFM — Type
BIFM

The deterministic node of a whole time slice of a linear state-space model, for backward-information-filter forward-marginal (BIFM) smoothing:

\[z_{next} = A z_{prev} + B u, \qquad out = C z_{next},\]

with the matrices A, B and C carried by its algorithm, BIFMSmoother, which the model must give each BIFM node. A chain of BIFM nodes starts with BIFMHelper.

Interfaces

  • out: the output, C znext, a vector;
  • in: the input u, a vector;
  • zprev: the previous state;
  • znext: the next state.

Every message the rules read is a multivariate normal; the forward rules read the message on zprev as a TerminalProdArgument wrapping one, the marginal that BIFMHelper or the previous slice sends.

Smoothing

Smoothing runs in two passes over the chain. The backward pass sends information-form messages towards zprev, from the messages on out, in and znext:

\[\begin{aligned} \xi_z &= C^\top \xi_{out} + \xi_{znext}, & \Lambda_z &= C^\top \Lambda_{out} C + \Lambda_{znext}, & H &= (\Lambda_{in} + B^\top \Lambda_z B)^{-1}, \\ \tilde\xi_z &= \xi_z + \Lambda_z B H (-\xi_{in} - B^\top \xi_z), & \tilde\Lambda_z &= \Lambda_z (I - B H B^\top \Lambda_z), & m_{zprev} &= \mathcal{N}^{-1}(A^\top \tilde\xi_z,\; A^\top \tilde\Lambda_z A). \end{aligned}\]

BIFMHelper turns it into the forward pass, whose messages towards in, out and znext are the marginals of those variables themselves, as TerminalProdArguments, so that the engine takes them as the marginals without a product. Each forward rule reads the message on its own edge, besides the others, and recomputes the backward quantities from them: the rules keep nothing between calls, so they are independent of the order they run in, and one BIFMSmoother may serve every node. On a chain the result is the Rauch–Tung–Striebel smoother's.

Limitations

  • No free energy. A deterministic node's free energy reads the joint marginal of its inputs, q(in, zprev, znext), whose rule here throws a BIFMFreeEnergyError, as does the average energy of BIFMHelper. Run the inference without the free energy.
  • Multivariate normal messages only, with a TerminalProdArgument on zprev for the forward rules: the chain must start with BIFMHelper.
  • Under the default algorithm, DefaultAlgorithm, no rule exists: every node must be given a BIFMSmoother.

Examples

A scalar random walk observed without transformation: the message on out is N(1, 1), the input's N(0, 1), and nothing is known of the next state yet, so the backward message on zprev is N(1, 2).

julia> algorithm = BIFMSmoother([1.0;;], [1.0;;], [1.0;;]);

julia> result = @call_message_update_rule(
           node = BIFM, target = :zprev, algorithm = algorithm,
           m = (
               out = MvNormalMeanPrecision([1.0], [1.0;;]),
               in = MvNormalMeanPrecision([0.0], [1.0;;]),
               znext = MvNormalWeightedMeanPrecision([0.0], [0.0;;]),
           ),
       );

julia> m, V = mean_cov(getresult(result));

julia> m ≈ [1.0] && V ≈ [2.0;;]
true

In a model, the chain of BIFM nodes, each under one BIFMSmoother, starts at a BIFMHelper whose input is the first state's prior, and ends at a flat prior on the last state, a normal of zero precision; the prior and the states are in separate clusters, q(z_prior) q(z). Each node's in is the input u[i] and its out the observed yt[i].

See also BIFMSmoother, BIFMHelper.

source
BIFMMessagePassingRules.BIFMFreeEnergyError — Type
BIFMFreeEnergyError(node::Symbol)

The error thrown when the free energy of a model with BIFM or BIFMHelper is asked for, which is not supported: BIFM smoothing computes the posteriors, and its Bethe free energy is not derived. node names the node that threw, :BIFM or :BIFMHelper.

source