Resolving package versions...
No Changes to `/workspaces/GaussianMvStateInference/Project.toml`
No Changes to `/workspaces/GaussianMvStateInference/Manifest.toml`
Resolving package versions...
No Changes to `/workspaces/GaussianMvStateInference/Project.toml`
No Changes to `/workspaces/GaussianMvStateInference/Manifest.toml`
Resolving package versions...
No Changes to `/workspaces/GaussianMvStateInference/Project.toml`
No Changes to `/workspaces/GaussianMvStateInference/Manifest.toml`
Resolving package versions...
No Changes to `/workspaces/GaussianMvStateInference/Project.toml`
No Changes to `/workspaces/GaussianMvStateInference/Manifest.toml`
Resolving package versions...
No Changes to `/workspaces/GaussianMvStateInference/Project.toml`
No Changes to `/workspaces/GaussianMvStateInference/Manifest.toml`
Pkg.status()
Status `/workspaces/GaussianMvStateInference/Project.toml`
[6e4b80f9] BenchmarkTools v1.8.0
[31c24e10] Distributions v0.25.131
[b964fa9f] LaTeXStrings v1.4.1
⌃ [91a5bcdd] Plots v1.41.6
⌃ [86711068] RxInfer v3.7.1
[860ef19b] StableRNGs v1.0.4
Info Packages marked with ⌃ have new versions available and may be upgradable.
Multivariate Linear Gaussian State Space Model
A multivariate Multivariate Linear Gaussian State Space Model (LGSSM) can be described with the equations:
where \(\mathbf{x}_t\) are hidden states, \(\mathbf{y}_t\) are noisy observations, \(\mathbf{\breve{A}}\), \(\mathbf{\breve{C}}\) are state transition and observation matrices, \(\mathbf{Q}\) and \(\mathbf{R}\) are state transition noise and observation noise covariance matrices.
To make things more interesting, we will use a state space model that is subject to rotation in 3 dimensions, i.e. along the x, y, and z axes. This is a common use case for flying aircraft that rotate relative to a ground-based frame of coordinates. Once we have the transition and observation matrices for rotation around all 3 axes, we will use these matrices for the final state space model. Eventually, we will perform bayesian multivariate inference of the random vector \(\mathbf{x}_t\). Note that this state space model does not capture the complete dynamics of a rotating object - only the location is included.
The provision function, provides another covariate vector. Because this is a sequential system, the provision function defines the transition between the previous state and the current state. This special case of the provision function is known as a transition function and it returns a provision/pre-state:
The response function, provides the response to the covariate vector, called the response: \[\mathbf{r^*}_{t} = f_r(\mathbf{x^*}_{t}) = f_C(\mathbf{x^*}_{t}) = \mathbf{C^*} \mathbf{x^*}_{t} \]
After combining with observation noise the observation is produced:
## Data comes from either a simulation/lab (sim|lab) OR from the field (fld)## Data are handled either in batches (batch) OR online as individual points (point)## Batch data accumulates either## along the depth/examples dimension/axis (into the screen/page), OR## typical for supervised & unsupervised learning## along the time dimension/axis (down the screen page)## typical for sequential decision learning (reinforcement learning & active inference)functionsim_batch_data(rng, T, _Aˣ, _Cˣ, _Qˣ, _Rˣ) ## simulated batch data _xˣₜ₋₁ = _xˣ₀ _pˣ_seq =Vector{Vector{Float64}}(undef, T) _xˣ_seq =Vector{Vector{Float64}}(undef, T) _rˣ_seq =Vector{Vector{Float64}}(undef, T) _y_seq =Vector{Vector{Float64}}(undef, T)for t in1:T## _pˣ_seq[t] = _Aˣ * _xˣₜ₋₁ _pˣ_seq[t] =fₚ(_Aˣ=_Aˣ, _xˣₜ₋₁=_xˣₜ₋₁) _xˣ_seq[t] =rand(rng, MvNormal(_pˣ_seq[t], _Qˣ))## _rˣ_seq[t] = _Cˣ * _xˣ_seq[t] _rˣ_seq[t] =fᵣ(_Cˣ=_Cˣ, _xˣₜ=_xˣ_seq[t]) _y_seq[t] =rand(rng, MvNormal(_rˣ_seq[t], _Rˣ)) _xˣₜ₋₁ = _xˣ_seq[t]endreturn _xˣ_seq, _y_seqend
Let’s visualize the synthesized dataset. Lines represent the hidden states that need to be estimated/inferred. We only have acces to noisy observations which are represented as dots.
_p =plot(title="Hidden states with noisy observations")_p =plot!(_p, getindex.(_xˣ_seq, 1), label="Hidden Signal "* L"x^*_1", color=:red)_p =scatter!(_p, getindex.(_y_seq, 1), label=false, markersize=2, color=:red)_p =plot!(_p, getindex.(_xˣ_seq, 2), label="Hidden Signal "* L"x^*_2", color=:green)_p =scatter!(_p, getindex.(_y_seq, 2), label=false, markersize=2, color=:green)_p =plot!(_p, getindex.(_xˣ_seq, 3), label="Hidden Signal "* L"x^*_3", color=:blue)_p =scatter!(_p, getindex.(_y_seq, 3), label=false, markersize=2, color=:blue)plot(_p)
The Generative Model
We now use RxInfer:
@modelfunctionrotate_ssm(_y_seq, _x₀, _A, _C, _Q, _R) _x_prior ~MvNormalMeanCovariance(mean(_x₀), cov(_x₀)) _xₜ₋₁ = _x_priorfor t in1:length(_y_seq) _x_seq[t] ~MvNormalMeanCovariance(_A*_xₜ₋₁, _Q) ## `s` is a sequence of hidden states##- s[t] ~ MvNormalMeanCovariance(f(B= B, sₜ₋₁= sₜ₋₁), Q) ## `s` is a sequence of hidden states _y_seq[t] ~MvNormalMeanCovariance(_C*_x_seq[t], _R) ## `y` is a sequence of "clamped" observations _xₜ₋₁ = _x_seq[t]endend
## We assume the _B̃, _Ã, _Q̃, _R̃ are known, i.e. not hidden, even though the tildes## in their names indicate that they are hidden result =infer( model=rotate_ssm(_x₀=_xˣ₀, _A=_Aˣ, _C=_Cˣ, _Q=_Qˣ, _R=_Rˣ), data= (_y_seq = _y_seq,), free_energy=true);
_p =plot(title="Estimated states from noisy observations")_p =plot!(_p, getindex.(_xˣ_seq, 1), label="Hidden Signal "* L"x^*_1", color=:red, linestyle=:dash)_p =plot!(_p, getindex.(_xˣ_seq, 2), label="Hidden Signal "* L"x^*_2", color=:green, linestyle=:dash)_p =plot!(_p, getindex.(_xˣ_seq, 3), label="Hidden Signal "* L"x^*_3", color=:blue, linestyle=:dash)_p =plot!(_p, getindex.(mean.(xmarginals), 1), ribbon=getindex.(var.(xmarginals), 1) .|> sqrt, fillalpha=0.5, label="Estimated Signal "* L"x_1", color=:pink)_p =plot!(_p, getindex.(mean.(xmarginals), 2), ribbon=getindex.(var.(xmarginals), 2) .|> sqrt, fillalpha=0.5, label="Estimated Signal "* L"x_2", color=:lightgreen)_p =plot!(_p, getindex.(mean.(xmarginals), 3), ribbon=getindex.(var.(xmarginals), 3) .|> sqrt, fillalpha=0.5, label="Estimated Signal "* L"x_3", color=:lightblue)plot(_p)
As we can see from our plot, estimated signal resembles closely to the real hidden states with small variance. We maybe also interested in the value for minus log evidence:
## given the analytical solution, the free energy will be equal to the negative log evidencelogevidence =-result.free_energy; logevidence