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