| using GaussianMarkovRandomFields |
| using GaussianMarkovRandomFields: mean |
| using LinearAlgebra |
| using SparseArrays |
|
|
| export ConstantLinearGaussianSSM |
| export joint_gmrf |
|
|
| """ |
| ConstantLinearGaussianSSM |
| |
| Linear Gaussian SSM with constant transition and noise matrices. |
| """ |
| struct ConstantLinearGaussianSSM{G<:GMRF, TA<:AbstractMatrix, TQ<:AbstractMatrix} |
| x0::G |
| A::TA |
| Q_noise::TQ |
| end |
|
|
| _safe_nnz(A::SparseMatrixCSC) = nnz(A) |
| _safe_nnz(A::AbstractMatrix) = prod(size(A)) |
|
|
| function joint_gmrf(ssm::ConstantLinearGaussianSSM, n_t::Int) |
| n_t >= 1 || throw(ArgumentError("n_t must be at least 1")) |
| |
| n_t == 1 && return ssm.x0 |
| |
| x0 = ssm.x0 |
| Q0 = precision_matrix(x0) |
| A = ssm.A |
| Q_noise = ssm.Q_noise |
| |
| n = length(x0) |
| size(A, 1) == n || |
| throw(DimensionMismatch("state transition matrix has incompatible row dimension")) |
| size(A, 2) == n || |
| throw(DimensionMismatch("state transition matrix has incompatible column dimension")) |
| size(Q_noise, 1) == n == size(Q_noise, 2) || |
| throw(DimensionMismatch("noise precision has incompatible dimensions")) |
| |
| QA = Q_noise * A |
| AtQA = sparse(transpose(A) * QA) |
| QA = sparse(QA) |
| off_lower = -QA |
| diag_first = sparse(Q0 + AtQA) |
| Q_noise_sparse = sparse(Q_noise) |
| diag_middle = sparse(Q_noise_sparse + AtQA) |
| diag_last = Q_noise_sparse |
| |
| nnz_diag_first = nnz(diag_first) |
| nnz_diag_middle = n_t > 2 ? nnz(diag_middle) : 0 |
| nnz_diag_last = nnz(diag_last) |
| nnz_off = nnz(off_lower) |
| |
| total_nnz = nnz_diag_first + nnz_diag_last + (n_t - 2) * nnz_diag_middle + 2 * (n_t - 1) * nnz_off |
| rows = Vector{Int}(undef, 0) |
| cols = Vector{Int}(undef, 0) |
| vals = Vector{eltype(Q_noise)}(undef, 0) |
| sizehint!(rows, total_nnz) |
| sizehint!(cols, total_nnz) |
| sizehint!(vals, total_nnz) |
|
|
| function push_block!(I::Vector{Int}, J::Vector{Int}, V, block::SparseMatrixCSC, bi::Int, bj::Int) |
| roffset = (bi - 1) * n |
| coffset = (bj - 1) * n |
| r, c, v = findnz(block) |
| append!(I, roffset .+ r) |
| append!(J, coffset .+ c) |
| append!(V, v) |
| end |
| |
| function push_block_transpose!(I::Vector{Int}, J::Vector{Int}, V, block::SparseMatrixCSC, bi::Int, bj::Int) |
| roffset = (bi - 1) * n |
| coffset = (bj - 1) * n |
| r, c, v = findnz(block) |
| append!(I, roffset .+ c) |
| append!(J, coffset .+ r) |
| append!(V, v) |
| end |
| |
| push_block!(rows, cols, vals, diag_first, 1, 1) |
| for t in 2:n_t-1 |
| push_block!(rows, cols, vals, diag_middle, t, t) |
| end |
| push_block!(rows, cols, vals, diag_last, n_t, n_t) |
| |
| for t in 1:n_t-1 |
| push_block!(rows, cols, vals, off_lower, t + 1, t) |
| push_block_transpose!(rows, cols, vals, off_lower, t, t + 1) |
| end |
| |
| joint_precision = sparse(rows, cols, vals, n * n_t, n * n_t) |
| |
| μ0 = mean(x0) |
| T = promote_type(eltype(μ0), eltype(Q_noise)) |
| joint_mean = Vector{T}(undef, n * n_t) |
| joint_mean[1:n] = μ0 |
|
|
| prev = copy(μ0) |
| curr = similar(prev) |
| for t in 2:n_t |
| mul!(curr, A, prev) |
| joint_mean[(t - 1) * n + 1:t * n] = curr |
| prev, curr = curr, prev |
| end |
|
|
| return GMRF(joint_mean, Symmetric(joint_precision)) |
| end |
|
|