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
Working notation (FoAI Spec v1.23, with local choices for this notebook):
where \(\boldsymbol{x}^{(t)}\) are hidden states, \(\boldsymbol{y}^{(t)}\) are noisy observations, \(\boldsymbol{\mathsf{A}}\), \(\boldsymbol{\mathsf{C}}\) are state transition and observation matrices, \(\boldsymbol{\mathsf{Q}}\) and \(\boldsymbol{\mathsf{R}}\) are state transition noise and observation noise covariance matrices (bold upright sans-serif \boldsymbol{\mathsf{\ldots}}: classical LTI / engineering matrices, Spec D40–D41 v1.23; not AIF \(\boldsymbol{A}\)–\(\boldsymbol{D}\)).
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 \(\boldsymbol{x}^{(t)}\). Note that this state space model does not capture the complete dynamics of a rotating object - only the location is included.
where: \[
\boldsymbol{x}^{(t)} = \begin{bmatrix} x^{(t)[1]} \\ x^{(t)[2]} \\ x^{(t)[3]} \end{bmatrix}
\] and these entries are the location coordinates along the spatial \(x\), \(y\), and \(z\) axes.
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: \[\boldsymbol{r}^{\ast(t)} = f_r(\boldsymbol{x}^{\ast(t)}) = f_{\mathsf{C}}(\boldsymbol{x}^{\ast(t)}) = \boldsymbol{\mathsf{C}}^{\ast} \boldsymbol{x}^{\ast(t)}\]
After combining with observation noise the observation is produced:
The process asterisk \(^{\ast}\) marks true (generative-process) quantities; it precedes any indices (e.g. \(\boldsymbol{x}^{\ast(t)}\)). Observations \(\boldsymbol{y}^{(t)}\) carry no asterisk.
## response function, provides the response to a covariate vectorfunctionfᵣ(; _Cˣ, _xˣₜ)return _Cˣ * _xˣₜendfᵣ(; _Cˣ=_Cˣ, _xˣₜ=_xˣ₀)
## 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ˣː =Vector{Vector{Float64}}(undef, T) _xˣː =Vector{Vector{Float64}}(undef, T) _rˣː =Vector{Vector{Float64}}(undef, T) _yː =Vector{Vector{Float64}}(undef, T)for t in1:T## _pˣː[t] = _Aˣ * _xˣₜ₋₁ _pˣː[t] =fₚ(_Aˣ=_Aˣ, _xˣₜ₋₁=_xˣₜ₋₁) _xˣː[t] =rand(rng, MvNormal(_pˣː[t], _Qˣ))## _rˣː[t] = _Cˣ * _xˣː[t] _rˣː[t] =fᵣ(_Cˣ=_Cˣ, _xˣₜ=_xˣː[t]) _yː[t] =rand(rng, MvNormal(_rˣː[t], _Rˣ)) _xˣₜ₋₁ = _xˣː[t]endreturn _xˣː, _yːend
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", legendfontsize=12)_p =plot!(_p, getindex.(_xˣː, 1), label="Hidden Signal "* L"x^{\ast[1]}", color=:red)_p =scatter!(_p, getindex.(_yː, 1), label=false, markersize=2, color=:red)_p =plot!(_p, getindex.(_xˣː, 2), label="Hidden Signal "* L"x^{\ast[2]}", color=:green)_p =scatter!(_p, getindex.(_yː, 2), label=false, markersize=2, color=:green)_p =plot!(_p, getindex.(_xˣː, 3), label="Hidden Signal "* L"x^{\ast[3]}", color=:blue)_p =scatter!(_p, getindex.(_yː, 3), label=false, markersize=2, color=:blue)plot(_p)
## We assume the process matrices _Aˣ, _Cˣ, _Qˣ, _Rˣ are known (not inferred).## The ˣ mark is the process asterisk (Spec D36), not a tilde.result =infer( model=rotate_ssm(_x₀=_xˣ₀, _A=_Aˣ, _C=_Cˣ, _Q=_Qˣ, _R=_Rˣ), data= (_yː = _yː,), free_energy=true);
_p =plot(title="Estimated states from noisy observations", legendfontsize=12)_p =plot!(_p, getindex.(_xˣː, 1), label="Hidden Signal "* L"x^{\ast[1]}", color=:red, linestyle=:dash)_p =plot!(_p, getindex.(_xˣː, 2), label="Hidden Signal "* L"x^{\ast[2]}", color=:green, linestyle=:dash)_p =plot!(_p, getindex.(_xˣː, 3), label="Hidden Signal "* L"x^{\ast[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