File size: 2,896 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 | # ------------------------------------------------------------------------------
# Basic Utilities
# ------------------------------------------------------------------------------
using FunctionalGPs: Interval
using LinearAlgebra
using SparseArrays
using GaussianMarkovRandomFields: GMRF, ConstrainedGMRF, precision_matrix
export midpoint
"""
midpoint(interv::Interval)
Compute the midpoint of an interval.
"""
midpoint(interv::Interval) = 0.5 * (interv[1] + interv[2])
# ------------------------------------------------------------------------------
# Dense GMRF Utilities
# ------------------------------------------------------------------------------
export dense_std, dense_var, dense_cov, is_dense_gmrf
"""
is_dense_gmrf(gmrf::GMRF)
Check if a GMRF has a dense (non-sparse) precision matrix.
"""
is_dense_gmrf(gmrf::GMRF) = !(precision_matrix(gmrf) isa SparseMatrixCSC)
"""
dense_var(gmrf::GMRF)
dense_var(gmrf::ConstrainedGMRF)
Compute variance of a GMRF by computing the diagonal of Q⁻¹ via Cholesky.
For a Cholesky factorization Q = LLᵀ, we have Q⁻¹ = L⁻ᵀL⁻¹.
The diagonal elements are: Σᵢᵢ = ||L⁻ᵀeᵢ||² = Σⱼ (L⁻¹)ᵢⱼ²
This is O(n³) for the factorization plus O(n²) for extracting the diagonal.
"""
function dense_var(gmrf::GMRF)
Q = precision_matrix(gmrf)
F = cholesky(Symmetric(Matrix(Q)))
Linv = inv(F.L)
# Variance is sum of squared rows of L⁻¹
return vec(sum(Linv .^ 2, dims=1))
end
# For ConstrainedGMRF, compute var using the formula:
# var_c = var_base - diag(B*B^T) where B = Ã^T * L_c^(-T)
# The ConstrainedGMRF already has A_tilde_T and L_c precomputed
function dense_var(gmrf::ConstrainedGMRF)
# Get unconstrained variances using dense computation
σ_base = dense_var(gmrf.base_gmrf)
# Compute B^T = L_c^(-1) * A_tilde_T^T
B_T = gmrf.L_c.L \ gmrf.A_tilde_T'
# Compute diagonal of B*B^T as column-wise sum of squares of B^T
B_squared_rowsums = vec(sum(abs2, B_T, dims=1))
# Constrained variance = unconstrained variance - correction
σ_constrained = σ_base - B_squared_rowsums
# Ensure non-negative (numerical precision can cause tiny negative values)
σ_constrained .= max.(σ_constrained, zero(eltype(σ_constrained)))
return σ_constrained
end
"""
dense_std(gmrf::GMRF)
dense_std(gmrf::ConstrainedGMRF)
Compute standard deviation of a GMRF. See [`dense_var`](@ref) for details.
"""
dense_std(gmrf::GMRF) = sqrt.(dense_var(gmrf))
dense_std(gmrf::ConstrainedGMRF) = sqrt.(dense_var(gmrf))
"""
dense_cov(gmrf::GMRF)
Compute full covariance matrix of a GMRF via Cholesky inversion.
Warning: This is O(n³) in time and O(n²) in memory.
Only use for small-to-moderate sized problems.
"""
function dense_cov(gmrf::GMRF)
Q = precision_matrix(gmrf)
F = cholesky(Symmetric(Matrix(Q)))
return Matrix(inv(F))
end
|