File size: 3,731 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 113 114 115 116 117 118 119 | # ------------------------------------------------------------------------------
# Direct Conditioning from Precision Matrix
# ------------------------------------------------------------------------------
using LinearAlgebra
using SparseArrays
using GaussianMarkovRandomFields: GMRF, InformationVector
export condition_precision
"""
condition_precision(Q::AbstractMatrix; A, Q_ϵ, y, b=zeros(size(A,1)), prior_mean=nothing)
Condition a prior precision matrix on linear observations, returning a posterior GMRF.
This provides a cleaner API than constructing a prior GMRF and then calling `linear_condition`,
especially when you have multiple conditioning steps or a zero prior mean.
# Arguments
- `Q`: Prior precision matrix (sparse or dense)
- `A`: Observation matrix (y = A*x + b + ε)
- `Q_ϵ`: Precision of observation noise
- `y`: Observation values
- `b`: Offset vector (default: zeros)
- `prior_mean`: Prior mean (default: zeros, which is typical for GP priors)
# Returns
- `GMRF`: Posterior GMRF with updated precision and information vector
# Model
The observation model is: y = A*x + b + ε where ε ~ N(0, Q_ϵ⁻¹)
The posterior has:
- Precision: Q_post = Q_prior + A'*Q_ϵ*A
- Information: η_post = Q_prior*μ_prior + A'*Q_ϵ*(y - b)
For zero prior mean (the default), this simplifies to:
- η_post = A'*Q_ϵ*(y - b)
# Example
```julia
# Build sparse precision
approx = sparse_precision([...], kernel; ρ=2.0)
# Condition directly on linear constraints
x_posterior = condition_precision(approx.Q;
A = A_constraints,
Q_ϵ = (1/σ²) * I,
y = constraint_values
)
# Extract posterior statistics
μ = mean(x_posterior)
σ = std(x_posterior)
```
"""
function condition_precision(Q::AbstractMatrix;
A::AbstractMatrix,
Q_ϵ::Union{AbstractMatrix, UniformScaling},
y::AbstractVector,
b::AbstractVector = zeros(size(A, 1)),
prior_mean::Union{AbstractVector, Nothing} = nothing)
n = size(Q, 1)
# Compute posterior precision
obs_precision_contrib = A' * Q_ϵ * A
Q_posterior = Q + obs_precision_contrib
# Compute posterior information vector
# η_post = Q_prior * μ_prior + A' * Q_ϵ * (y - b)
if prior_mean === nothing || iszero(prior_mean)
# Zero prior mean (common case) - skip the Q*μ term
info_posterior = A' * (Q_ϵ * (y - b))
else
info_posterior = Q * prior_mean + A' * (Q_ϵ * (y - b))
end
return GMRF(InformationVector(info_posterior), Symmetric(Q_posterior))
end
"""
condition_precision(Q::AbstractMatrix, conditions::Vector; prior_mean=nothing)
Apply multiple conditioning steps efficiently.
# Arguments
- `Q`: Prior precision matrix
- `conditions`: Vector of NamedTuples, each with fields (A, Q_ϵ, y) and optional (b)
- `prior_mean`: Prior mean (default: zeros)
# Example
```julia
x_posterior = condition_precision(approx.Q, [
(A=A_fvm, Q_ϵ=Q_fvm, y=zeros(n_fvm)),
(A=A_bc, Q_ϵ=Q_bc, y=bc_values),
])
```
"""
function condition_precision(Q::AbstractMatrix, conditions::Vector;
prior_mean::Union{AbstractVector, Nothing} = nothing)
n = size(Q, 1)
# Accumulate precision contributions
Q_posterior = copy(Q)
info_posterior = prior_mean === nothing ? zeros(n) : Q * prior_mean
for cond in conditions
A = cond.A
Q_ϵ = cond.Q_ϵ
y = cond.y
b = hasproperty(cond, :b) ? cond.b : zeros(size(A, 1))
Q_posterior += A' * Q_ϵ * A
info_posterior += A' * (Q_ϵ * (y - b))
end
return GMRF(InformationVector(info_posterior), Symmetric(Q_posterior))
end
|