File size: 3,964 Bytes
c711202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
export build_time_precision

using LinearAlgebra
using SparseArrays

# -- Block-tridiagonal time precision ---------------------------------------
"""
    build_time_precision(sde, Δts::AbstractVector{<:Real}; P0=:stationary) -> SparseMatrixCSC
    build_time_precision(As::Vector{<:AbstractMatrix}, Qs::Vector{<:AbstractMatrix}; P0=:stationary, P0_mat=nothing) -> SparseMatrixCSC
    build_time_precision(A::AbstractMatrix, Q::AbstractMatrix, T::Integer; P0=:stationary, P0_mat=nothing) -> SparseMatrixCSC

Construct the block-tridiagonal prior precision for the Markov chain of states
`u₁, …, u_T` arising from the discretized SDE.

You can provide:
  • an `sde` and a vector of step sizes `Δts` (computes `(A_k,Q_k)` via Van Loan), or
  • explicit vectors `As, Qs` of length `T-1`, or
  • a fixed pair `(A,Q)` and the horizon `T`.

`P0` can be:
  - `:stationary` (default): uses `Pinf(sde)` if available, or a discrete Lyapunov solve if `(A,Q)` (or `As,Qs`) are given and `P0_mat` not provided.
  - a Matrix: directly uses the provided covariance for `u₁`.

Returns a symmetric sparse CSC matrix of size `(nT)×(nT)` with block size `n`.
"""
function build_time_precision(sde::LinearSDE, Δts::AbstractVector{<:Real}; P0=:stationary, Q0=nothing)
    nsteps = length(Δts)
    As = Vector{Matrix{Float64}}(undef, nsteps)
    Qs = Vector{Matrix{Float64}}(undef, nsteps)
    for k in 1:nsteps
        As[k], Qs[k] = discretize_vanloan(sde, Δts[k])
    end
    P0_mat = if P0 === :stationary
        Pinf(sde)
    elseif Q0 === nothing
        Matrix(P0)
    else
        nothing
    end
    return build_time_precision(As, Qs; P0=P0, P0_mat=P0_mat, Q0=Q0)
end

function build_time_precision(A::AbstractMatrix, Q::AbstractMatrix, T::Integer; P0=:stationary, P0_mat=nothing, Q0=nothing)
    As = fill(Matrix(A), T-1)
    Qs = fill(Matrix(Q), T-1)
    if P0 === :stationary && P0_mat === nothing
        P0_mat = _stationary_cov_from_AQ(A, Q)
    elseif P0 !== :stationary && Q0 !== nothing
        P0_mat = Matrix(P0)
    end
    return build_time_precision(As, Qs; P0=:given, P0_mat=P0_mat, Q0=Q0)
end

function build_time_precision(As::Vector{<:AbstractMatrix}, Qs::Vector{<:AbstractMatrix}; P0=:stationary, P0_mat=nothing, Q0=nothing)
    @assert length(As) == length(Qs)
    T = length(As) + 1
    n = size(As[1], 1)

    Qi = [cholesky(Symmetric(Qs[k])) \ I for k in 1:length(Qs)]

    rows = Int[]; cols = Int[]; vals = Float64[]

    # k = 1
    if Q0 !== nothing
        J11 = Q0
    else
        J11 = (P0 === :stationary || P0 === :given) ? inv(Symmetric(P0_mat)) : inv(Symmetric(P0))
    end
    J11 = Matrix(J11)
    J11 .+= As[1]' * Qi[1] * As[1]
    J11 = 0.5 * (J11 + J11')
    J12 = -As[1]' * Qi[1]
    for j in 1:n, i in 1:n
        push!(rows, i); push!(cols, j); push!(vals, J11[i,j])
        push!(rows, i); push!(cols, j+n); push!(vals, J12[i,j])
    end

    # interior blocks
    for k in 2:T-1
        r = (k-1)*n
        Jkm1 = -Qi[k-1] * As[k-1]
        Jkk  =  Qi[k-1] + As[k]' * Qi[k] * As[k]
        Jkk = 0.5 * (Jkk + Jkk')
        Jkp1 = -As[k]' * Qi[k]
        for j in 1:n, i in 1:n
            push!(rows, r+i); push!(cols, r-n+j); push!(vals, Jkm1[i,j])
            push!(rows, r+i); push!(cols, r+j);   push!(vals, Jkk[i,j])
            push!(rows, r+i); push!(cols, r+n+j); push!(vals, Jkp1[i,j])
        end
    end

    # k = T
    rT = (T-1)*n
    JTkm1 = -Qi[end] * As[end]
    JTT   =  Qi[end]
    for j in 1:n, i in 1:n
        push!(rows, rT+i); push!(cols, rT-n+j); push!(vals, JTkm1[i,j])
        push!(rows, rT+i); push!(cols, rT+j);   push!(vals, JTT[i,j])
    end

    Jmat = sparse(rows, cols, vals, n*T, n*T)
    return Symmetric(Jmat)
end

# Compute stationary covariance from (A,Q) by solving discrete Lyapunov
function _stationary_cov_from_AQ(A::AbstractMatrix, Q::AbstractMatrix)
    n = size(A,1)
    K = I(n*n) - kron(A, A)
    p = K \ vec(Q)
    return Matrix(Symmetric(reshape(p, n, n)))
end