Gaussian Processes¶
Layer 3 recipes for GP inference: conditioning, whitening, prediction caches, pathwise (Matheron) sampling, variational bounds, and cross-validation — all expressed over covariance operators so structured kernels keep their fast paths end to end. The modelling shell (kernels with hyperparameter priors, NumPyro sites) lives downstream; gaussx owns the math.
Conditioning & prediction¶
The standard posterior
plus a precomputed-cache variant for repeated test-time queries and a Kronecker-structured path for separable kernels on grids.
The sparse-GP helpers share one convention (gh-353):
| Object | Name | Shape / type |
|---|---|---|
| Inducing covariance (first argument) | K_zz |
(M, M) array or PSD operator |
| Cross-covariance (data × inducing) | K_xz |
(N, M) |
| Prior at the evaluation points | K_xx / K_xx_diag |
(N, N) / (N,) |
| Variational mean | q_mu |
(M, R) or (M,) |
| Variational root | q_sqrt |
(R, M, M) or (M, R) |
sparse_conditional follows it; base_conditional (cross-covariance
(M, N)) is deprecated in its favour. collapsed_elbo's K_diag= keyword
is now K_xx_diag=, and the unused solver= of collapsed_elbo and
gauss_kl is deprecated; the old forms warn until gaussx 0.7.0.
Structured linear algebra and Gaussian primitives for JAX.
PredictionCache
¶
Bases: Module
Cached training solve (and factor) for amortized predictions.
A cache is tied to the operator it was built from: rebuild it whenever
the kernel hyperparameters (and so K_y) change.
Attributes:
| Name | Type | Description |
|---|---|---|
alpha |
Float[Array, ' N']
|
Solved weights |
operator |
AbstractLinearOperator | None
|
The training covariance |
factor |
AbstractLinearOperator | None
|
Lower Cholesky factor |
Source code in src/gaussx/_gp/_prediction_cache.py
sparse_conditional(K_zz: Float[Array, 'M M'] | lx.AbstractLinearOperator, K_xz: Float[Array, 'N M'], K_xx: Float[Array, 'N N'] | Float[Array, ' N'], q_mu: Float[Array, 'M R'] | Float[Array, ' M'], *, q_sqrt: Float[Array, 'R M M'] | Float[Array, 'M R'] | Float[Array, 'M M'] | Float[Array, ' M'] | None = None, white: bool = False) -> tuple[Float[Array, 'N R'], Float[Array, ...]]
¶
Sparse-GP conditional q(f_x) = \int p(f_x | u) q(u) du.
Follows the gaussx._gp sparse-GP convention: inducing covariance
K_zz first (array or operator), cross-covariance K_xz with
shape (N, M) (data × inducing), prior K_xx at the evaluation
points, variational q(u) = N(q_mu, q_sqrt q_sqrtᵀ).
With L = chol(K_zz) and A = L^{-1} K_xz^\top (shape (M, N)),
where S_r is the r-th q_sqrt factor (diag(q_sqrt[:, r])
for the diagonal layout). Only the diagonal of Σ_r is formed when
K_xx is a vector. K_zz is factorised with gaussx.cholesky
and every solve with its factor is a structured triangular solve, so a
Kronecker or BlockDiag K_zz is never densified.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
K_zz
|
Float[Array, 'M M'] | AbstractLinearOperator
|
Prior covariance at the inducing points, |
required |
K_xz
|
Float[Array, 'N M']
|
Cross-covariance |
required |
K_xx
|
Float[Array, 'N N'] | Float[Array, ' N']
|
Prior covariance at |
required |
q_mu
|
Float[Array, 'M R'] | Float[Array, ' M']
|
Variational mean |
required |
q_sqrt
|
Float[Array, 'R M M'] | Float[Array, 'M R'] | Float[Array, 'M M'] | Float[Array, ' M'] | None
|
Optional variational root. Full |
None
|
white
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Float[Array, 'N R']
|
|
Float[Array, ...]
|
|
tuple[Float[Array, 'N R'], Float[Array, ...]]
|
|
tuple[Float[Array, 'N R'], Float[Array, ...]]
|
full covariance is returned unclipped. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/gaussx/_gp/_base_conditional.py
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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
build_prediction_cache(operator: lx.AbstractLinearOperator, y: Float[Array, ' N'], *, solver: AbstractSolveStrategy | None = None) -> PredictionCache
¶
Solve K_y alpha = y and cache the result.
For a PSD dense, diagonal, Kronecker or BlockDiag operator with
solver None or DenseSolver, computes L = cholesky(K_y)
once, solves alpha = L^{-T} L^{-1} y through it and stores L
so that predict_variance needs only triangular solves. Otherwise
(iterative strategies, other structured operators) alpha comes
from solver and factor is None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operator
|
AbstractLinearOperator
|
Training covariance operator |
required |
y
|
Float[Array, ' N']
|
Training targets, shape |
required |
solver
|
AbstractSolveStrategy | None
|
Optional solve strategy. When |
None
|
Returns:
| Type | Description |
|---|---|
PredictionCache
|
A |
PredictionCache
|
available, its Cholesky factor. |
Source code in src/gaussx/_gp/_prediction_cache.py
predict_mean(cache: PredictionCache, K_cross: Float[Array, 'Nt N']) -> Float[Array, ' Nt']
¶
Predictive mean: mu* = K_*f @ alpha.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache
|
PredictionCache
|
Prediction cache from |
required |
K_cross
|
Float[Array, 'Nt N']
|
Cross-covariance matrix, shape |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, ' Nt']
|
Predictive mean, shape |
Source code in src/gaussx/_gp/_prediction_cache.py
predict_variance(cache: PredictionCache | Float[Array, 'Nt N'] | None = None, K_cross: Float[Array, 'Nt N'] | Float[Array, ' Nt'] | None = None, K_test_diag: Float[Array, ' Nt'] | lx.AbstractLinearOperator | None = None, *, operator: lx.AbstractLinearOperator | None = None, solver: AbstractSolveStrategy | None = None) -> Float[Array, ' Nt']
¶
Predictive variance sigma^2_* = k_** - diag(K_*f K_y^{-1} K_f*).
Call as predict_variance(cache, K_cross, K_test_diag), with the
cache first as in predict_mean. With a cached factor L:
triangular solves only (no refactorisation of K_y). Without a
factor, solves K_y v_i = K_cross[i, :] for each test row with
solver and returns k_{**,i} - K_cross[i, :] v_i.
The old order predict_variance(K_cross, K_test_diag, operator)
(or operator= as a keyword) still works for one release, emits a
DeprecationWarning, and refactorises K_y on every call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache
|
PredictionCache | Float[Array, 'Nt N'] | None
|
Prediction cache from |
None
|
K_cross
|
Float[Array, 'Nt N'] | Float[Array, ' Nt'] | None
|
Cross-covariance matrix, shape |
None
|
K_test_diag
|
Float[Array, ' Nt'] | AbstractLinearOperator | None
|
Prior variance at test points, shape |
None
|
operator
|
AbstractLinearOperator | None
|
Deprecated; only for the old call form. |
None
|
solver
|
AbstractSolveStrategy | None
|
Optional solve strategy for the no-factor path. When
|
None
|
Returns:
| Type | Description |
|---|---|
Float[Array, ' Nt']
|
Predictive variance, shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the cache holds neither a factor nor an operator. |
Source code in src/gaussx/_gp/_prediction_cache.py
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | |
conditional_interpolate(A_fwd: Float[Array, 'd d'], Q_fwd: Float[Array, 'd d'], A_bwd: Float[Array, 'd d'], Q_bwd: Float[Array, 'd d'], mu_prev: Float[Array, ' d'], P_prev: Float[Array, 'd d'], mu_next: Float[Array, ' d'], P_next: Float[Array, 'd d'], *, solver: AbstractSolverStrategy | None = None) -> tuple[Float[Array, ' d'], Float[Array, 'd d']]
¶
Two-filter fusion at t from a filtered state and a backward message.
For an SDE-discretised state-space model with no observation at t,
combines two conditionally independent sources of information about
x_t by adding precisions (the two-filter smoother):
(mu_prev, P_prev): the forward-filtered state att^-,p(x_{t^-} \mid y_{\le t^-}) = N(mu_prev, P_prev);(mu_next, P_next): the backward-information message att^+, i.e. the likelihood of the data at and aftert^+written asp(y_{\ge t^+} \mid x_{t^+}) \propto N(mu_next; x_{t^+}, P_next)(no prior onx_{t^+}).
With those inputs it returns p(x_t \mid y_{\le t^-}, y_{\ge t^+})
exactly:
Warning
Do not pass smoothed marginals (e.g. from rts_smoother) for
both ends: each already contains all the data, so the data is
counted twice and the result is over-confident (about 20% too small
a covariance on a 3-state chain, gh-288). To interpolate from a
filtered state at t^- and a smoothed state at t^+, use
rts_interpolate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
A_fwd
|
Float[Array, 'd d']
|
Forward transition from |
required |
Q_fwd
|
Float[Array, 'd d']
|
Forward process noise, shape |
required |
A_bwd
|
Float[Array, 'd d']
|
Transition from |
required |
Q_bwd
|
Float[Array, 'd d']
|
Process noise from |
required |
mu_prev
|
Float[Array, ' d']
|
Forward-filtered mean at |
required |
P_prev
|
Float[Array, 'd d']
|
Forward-filtered covariance at |
required |
mu_next
|
Float[Array, ' d']
|
Mean of the backward-information message at |
required |
P_next
|
Float[Array, 'd d']
|
Covariance of the backward-information message at |
required |
solver
|
AbstractSolverStrategy | None
|
Optional solver strategy for structured linear algebra.
When |
None
|
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ' d'], Float[Array, 'd d']]
|
Tuple |
Source code in src/gaussx/_gp/_interpolation.py
rts_interpolate(A_fwd: Float[Array, 'd d'], Q_fwd: Float[Array, 'd d'], A_bwd: Float[Array, 'd d'], Q_bwd: Float[Array, 'd d'], filtered_mean_prev: Float[Array, ' d'], filtered_cov_prev: Float[Array, 'd d'], smoothed_mean_next: Float[Array, ' d'], smoothed_cov_next: Float[Array, 'd d'], *, solver: AbstractSolverStrategy | None = None) -> tuple[Float[Array, ' d'], Float[Array, 'd d']]
¶
Smoothed marginal at an unobserved t from filtered and smoothed states.
For the model of conditional_interpolate (no observation at t,
t^- < t < t^+), takes the forward-filtered state at t^- and
the smoothed state at t^+ — what kalman_filter and
rts_smoother return — and performs one Rauch-Tung-Striebel backward
step through t:
This is exact: given x_{t^+}, x_t is independent of the data at
and after t^+, so
p(x_t | y) = \int p(x_t | x_{t^+}, y_{\le t^-}) p(x_{t^+} | y) dx_{t^+}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
A_fwd
|
Float[Array, 'd d']
|
Forward transition from |
required |
Q_fwd
|
Float[Array, 'd d']
|
Forward process noise, shape |
required |
A_bwd
|
Float[Array, 'd d']
|
Transition from |
required |
Q_bwd
|
Float[Array, 'd d']
|
Process noise from |
required |
filtered_mean_prev
|
Float[Array, ' d']
|
Filtered mean |
required |
filtered_cov_prev
|
Float[Array, 'd d']
|
Filtered covariance |
required |
smoothed_mean_next
|
Float[Array, ' d']
|
Smoothed mean |
required |
smoothed_cov_next
|
Float[Array, 'd d']
|
Smoothed covariance |
required |
solver
|
AbstractSolverStrategy | None
|
Optional solver strategy for the |
None
|
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ' d'], Float[Array, 'd d']]
|
Tuple |
Source code in src/gaussx/_gp/_interpolation.py
kronecker_posterior_predictive(K_factors: list[lx.AbstractLinearOperator], y: Float[Array, ' N'], noise_var: float, grid_shape: tuple[int, ...], K_cross_factors: list[Float[Array, 'Ni_test Ni_train']], *, K_test_diag_factors: list[Float[Array, ' Ni_test']]) -> tuple[Float[Array, ' N_test'], Float[Array, ' N_test']]
¶
Posterior mean and variance for a Kronecker GP at test points.
Uses the eigendecomposition trick: projects cross-covariances onto the eigenbasis and weights by inverse eigenvalues:
mu_* = K_{*f} (K_{ff} + sigma^2 I)^{-1} y
var_* = k_{**} - K_{*f} (K_{ff} + sigma^2 I)^{-1} K_{f*}
Both computed via per-factor eigendecomposition in
O(sum n_i^3 + N + N_test) time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
K_factors
|
list[AbstractLinearOperator]
|
List of per-dimension training kernel operators. |
required |
y
|
Float[Array, ' N']
|
Observations, shape |
required |
noise_var
|
float
|
Observation noise variance |
required |
grid_shape
|
tuple[int, ...]
|
Grid shape, e.g. |
required |
K_cross_factors
|
list[Float[Array, 'Ni_test Ni_train']]
|
Per-dimension cross-covariance matrices,
each shape |
required |
K_test_diag_factors
|
list[Float[Array, ' Ni_test']]
|
Per-dimension prior diagonals at the test points,
each shape |
required |
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ' N_test'], Float[Array, ' N_test']]
|
Tuple |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/gaussx/_gp/_kronecker_gp.py
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | |
kronecker_mll(K_factors: list[lx.AbstractLinearOperator], y: Float[Array, ' N'], noise_var: float, grid_shape: tuple[int, ...]) -> Float[Array, '']
¶
Exact marginal log-likelihood for a Kronecker-structured GP.
For a GP with covariance K = K_1 \otimes K_2 \otimes \ldots + sigma^2 I,
computes the log marginal likelihood via per-factor eigendecomposition:
log p(y) = -0.5 * (y^T (K + sigma^2 I)^{-1} y
+ log|K + sigma^2 I|
+ N log(2 pi))
The Kronecker eigendecomposition avoids forming the full N x N matrix:
if K_i = Q_i Lambda_i Q_i^T, the combined eigenvalues are the outer
products of per-factor eigenvalues and the combined eigenvectors are the
Kronecker product of per-factor eigenvectors.
Complexity: O(sum n_i^3 + N) instead of O(N^3) where N = prod n_i.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
K_factors
|
list[AbstractLinearOperator]
|
List of per-dimension kernel operators. Each must be square and symmetric. |
required |
y
|
Float[Array, ' N']
|
Observations, shape |
required |
noise_var
|
float
|
Observation noise variance |
required |
grid_shape
|
tuple[int, ...]
|
Shape of the grid, e.g. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar log marginal likelihood. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/gaussx/_gp/_kronecker_gp.py
Pathwise sampling¶
Matheron's rule turns joint prior draws \((a, b)\) into posterior draws: \(a + \mathrm{Cov}(a,b)\,\mathrm{Cov}(b,b)^{-1}(\beta - b)\).
Structured linear algebra and Gaussian primitives for JAX.
matheron_update(prior_sample_target: Float[Array, 'S N_star'], prior_sample_conditioning: Float[Array, 'S M'], observed_value: Float[Array, ' M'], cross_covariance: lx.AbstractLinearOperator, conditioning_covariance: lx.AbstractLinearOperator, *, solver: AbstractSolveStrategy | None = None) -> Float[Array, 'S N_star']
¶
Posterior samples via Matheron's-rule correction.
Given joint prior draws (a, b) and an observed conditioning value
β, Matheron's rule samples from a | b = β by applying
This helper keeps both covariance arguments as lineax operators, so the conditioning solve uses the existing GaussX structural dispatch and the target correction is a rectangular matvec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prior_sample_target
|
Float[Array, 'S N_star']
|
Prior samples at target points, shape
|
required |
prior_sample_conditioning
|
Float[Array, 'S M']
|
Joint prior samples at conditioning
points, shape |
required |
observed_value
|
Float[Array, ' M']
|
Observed conditioning value, shape |
required |
cross_covariance
|
AbstractLinearOperator
|
Cross-covariance operator |
required |
conditioning_covariance
|
AbstractLinearOperator
|
Conditioning covariance operator
|
required |
solver
|
AbstractSolveStrategy | None
|
Optional solver strategy for the conditioning solve
(e.g. |
None
|
Returns:
| Type | Description |
|---|---|
Float[Array, 'S N_star']
|
Corrected posterior samples, shape |
Source code in src/gaussx/_gp/_matheron.py
Whitening¶
The whitened parameterization \(u = Lv\), \(v \sim \mathcal{N}(0, I)\) that keeps sparse-variational optimization well-conditioned, and the whitened SVGP predictive that consumes it.
Structured linear algebra and Gaussian primitives for JAX.
whiten_covariance = unwhiten_covariance
module-attribute
¶
unwhiten(m_tilde: Float[Array, ' M'], L: lx.AbstractLinearOperator) -> Float[Array, ' M']
¶
Unwhiten variational mean: m = L @ m_tilde.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
m_tilde
|
Float[Array, ' M']
|
Whitened mean vector, shape |
required |
L
|
AbstractLinearOperator
|
Cholesky factor, shape |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, ' M']
|
Unwhitened mean m, shape |
Source code in src/gaussx/_gp/_unwhiten.py
unwhiten_covariance(L: lx.AbstractLinearOperator, S_tilde: lx.AbstractLinearOperator) -> lx.MatrixLinearOperator
¶
Unwhiten variational covariance: S = L S̃ Lᵀ.
Delegates to cov_transform.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
L
|
AbstractLinearOperator
|
Cholesky factor, shape |
required |
S_tilde
|
AbstractLinearOperator
|
Whitened variational covariance, shape |
required |
Returns:
| Type | Description |
|---|---|
MatrixLinearOperator
|
Unwhitened covariance operator S. |
Source code in src/gaussx/_gp/_unwhiten.py
whitened_svgp_predict(K_zz_op: lx.AbstractLinearOperator, K_xz: Float[Array, 'N M'], u_mean: Float[Array, ' M'], u_chol: Float[Array, 'M M'], K_xx_diag: Float[Array, ' N']) -> tuple[Float[Array, ' N'], Float[Array, ' N']]
¶
Whitened SVGP prediction: mean and variance at test points.
Computes the predictive mean and variance for a sparse variational GP using the whitened parameterization:
L_{zz} = cholesky(K_{zz})
A = L_{zz}^{-1} K_{zx} (triangular solve)
f_{loc} = A^T u_{mean}
Q_{xx} = sum(A^2, axis=0) (prior variance reduction)
W = u_{chol}^T A
S_{contrib} = sum(W^2, axis=0) (posterior variance contribution)
f_{var} = K_{xx,diag} - Q_{xx} + S_{contrib}
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
K_zz_op
|
AbstractLinearOperator
|
Inducing-point covariance operator, shape |
required |
K_xz
|
Float[Array, 'N M']
|
Cross-covariance matrix, shape |
required |
u_mean
|
Float[Array, ' M']
|
Whitened variational mean, shape |
required |
u_chol
|
Float[Array, 'M M']
|
Whitened variational Cholesky factor, shape |
required |
K_xx_diag
|
Float[Array, ' N']
|
Prior diagonal variances at test points, shape |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, ' N']
|
Tuple |
Float[Array, ' N']
|
predictive variance shape |
Source code in src/gaussx/_gp/_svgp.py
svgp_variance_adjustment(K_zz_op: lx.AbstractLinearOperator, S_u: lx.AbstractLinearOperator) -> lx.AbstractLinearOperator
¶
Compute the SVGP variance adjustment operator.
Builds the operator Q = K_{zz}^{-1} S_u K_{zz}^{-1} - K_{zz}^{-1}
which appears in every sparse GP predictive variance computation:
Var[f_*] = k_{**} - k_{*z} (K_{zz}^{-1} - Q) k_{z*}
The returned value is exposed as a linear operator, but the current
implementation materializes dense (M, M) intermediates while building
it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
K_zz_op
|
AbstractLinearOperator
|
Inducing-point covariance operator, shape |
required |
S_u
|
AbstractLinearOperator
|
Variational covariance operator, shape |
required |
Returns:
| Type | Description |
|---|---|
AbstractLinearOperator
|
Operator |
AbstractLinearOperator
|
|
Source code in src/gaussx/_gp/_svgp_variance.py
Variational bounds & KL¶
ELBOs for Gaussian and Monte-Carlo variational families, the collapsed (Titsias) sparse bound, and the Gaussian-to-Gaussian KL term.
Structured linear algebra and Gaussian primitives for JAX.
variational_elbo_gaussian(y: Float[Array, ' N'], f_loc: Float[Array, ' N'], f_var: Float[Array, ' N'], noise_var: float, kl: Float[Array, '']) -> Float[Array, '']
¶
Uncollapsed (SVGP) ELBO for a Gaussian likelihood.
The stochastic-variational bound of Hensman et al. (2013): the
variational distribution q(u) is kept explicit, so the caller
supplies its marginals f_loc, f_var and the KL(q(u) || p(u))
term. Computes:
ELBO = E_q[log p(y|f)] - KL(q||p)
where the expected log-likelihood under a Gaussian variational distribution with diagonal variance has the closed form:
E_q[log N(y|f, sigma^2 I)]
= -0.5 * N * log(2 pi sigma^2)
-0.5 / sigma^2 * (||y - f_loc||^2 + sum(f_var))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
Float[Array, ' N']
|
Observations, shape |
required |
f_loc
|
Float[Array, ' N']
|
Variational mean, shape |
required |
f_var
|
Float[Array, ' N']
|
Variational marginal variances, shape |
required |
noise_var
|
float
|
Observation noise variance (scalar). |
required |
kl
|
Float[Array, '']
|
KL divergence term |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar ELBO value. |
See Also
collapsed_elbo: The Titsias (2009) collapsed bound, with the
optimal q(u) integrated out analytically.
References
Hensman, J., Fusi, N. and Lawrence, N. D. (2013). Gaussian processes for big data. UAI.
Titsias, M. (2009). Variational learning of inducing variables in sparse Gaussian processes. AISTATS.
Source code in src/gaussx/_gp/_elbo.py
variational_elbo_mc(log_likelihood_fn: Callable[[Float[Array, ' N']], Float[Array, '']], f_samples: Float[Array, 'S N'], kl: Float[Array, '']) -> Float[Array, '']
¶
Monte Carlo ELBO for non-conjugate likelihoods.
Computes:
ELBO = (1/S) sum_s log p(y|f_s) - KL(q||p)
where f_s ~ q(f) are samples from the variational distribution.
Supports any likelihood (Poisson, Bernoulli, Pareto, etc.).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_likelihood_fn
|
Callable[[Float[Array, ' N']], Float[Array, '']]
|
Function mapping latent samples to scalar
log-likelihood. Signature |
required |
f_samples
|
Float[Array, 'S N']
|
Samples from the variational posterior, shape
|
required |
kl
|
Float[Array, '']
|
KL divergence term |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar ELBO value. |
Source code in src/gaussx/_gp/_elbo.py
collapsed_elbo(y: Float[Array, ' N'], K_xx_diag: Float[Array, ' N'], K_xz: Float[Array, 'N M'], K_zz: Float[Array, 'M M'] | lx.AbstractLinearOperator, noise_var: float, *, jitter: float = 1e-06, solver: AbstractSolverStrategy | None = None) -> Float[Array, '']
¶
Collapsed ELBO (Titsias bound) for sparse GP regression.
Computes the variational lower bound on the log marginal likelihood using the matrix determinant lemma for O(NM² + M³) cost:
ELBO = log 𝒩(y | 0, Q_ff + σ²I) − ½σ⁻² tr(K_ff − Q_ff)
where Q_ff = K_xz K_zz⁻¹ K_xzᵀ is the Nyström approximation. With
L L^T = K_zz and V = L⁻¹ K_xzᵀ (structured triangular solves),
B = I + σ⁻² V Vᵀ is the only dense (M, M) factorisation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
Float[Array, ' N']
|
Observations, shape |
required |
K_xx_diag
|
Float[Array, ' N']
|
Diagonal of the full kernel matrix K_ff, shape |
required |
K_xz
|
Float[Array, 'N M']
|
Cross-covariance between data and inducing points,
shape |
required |
K_zz
|
Float[Array, 'M M'] | AbstractLinearOperator
|
Inducing point kernel matrix, |
required |
noise_var
|
float
|
Observation noise variance σ² (scalar). |
required |
jitter
|
float
|
Diagonal jitter added to |
1e-06
|
solver
|
AbstractSolverStrategy | None
|
Deprecated and ignored (the Cholesky factorisations take no solver); passing one warns. Removed in gaussx 0.7.0. |
None
|
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar ELBO value. |
See Also
variational_elbo_gaussian: The uncollapsed (SVGP; Hensman et al.,
2013) bound, which keeps q(u) explicit.
References
Titsias, M. (2009). Variational learning of inducing variables in sparse Gaussian processes. AISTATS.
Source code in src/gaussx/_gp/_collapsed_elbo.py
gauss_kl(q_mu: Float[Array, 'M R'], q_sqrt: Float[Array, 'R M M'] | Float[Array, 'M R'], K: Float[Array, 'M M'] | lx.AbstractLinearOperator | None = None, *, solver: AbstractSolverStrategy | None = None) -> Float[Array, '']
¶
KL(first ‖ second): KL[q(u) || p(u)] with p(u) = N(0, K).
Cholesky-parameterised variant of
gaussian_kl designed for
GP/SVGP models. The Cholesky representation avoids explicit covariance
matrix construction and supports both full and diagonal q_sqrt.
For lineax-operator covariances, use
gaussian_kl instead.
Computes the KL divergence where:
q = N(q_mu, q_sqrt @ q_sqrt^T)p = N(0, K)orp = N(0, I)ifK is None(white prior)
Handles both full and diagonal q_sqrt:
- Full
q_sqrt: shape(R, M, M)— lower-triangular Cholesky factors of the variational covariance per output dimension. - Diagonal
q_sqrt: shape(M, R)— diagonal standard deviations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q_mu
|
Float[Array, 'M R']
|
Variational mean, shape |
required |
q_sqrt
|
Float[Array, 'R M M'] | Float[Array, 'M R']
|
Variational Cholesky factor or diagonal std devs. |
required |
K
|
Float[Array, 'M M'] | AbstractLinearOperator | None
|
Prior covariance, |
None
|
solver
|
AbstractSolverStrategy | None
|
Deprecated and ignored (the Cholesky factorisation takes no solver); passing one warns. Removed in gaussx 0.7.0. |
None
|
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar KL divergence summed over all |
See Also
gaussian_kl: General KL
between two multivariate normals with lineax covariance operators.
Source code in src/gaussx/_gp/_gauss_kl.py
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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
Cross-validation & diagnostics¶
LOVE-style cached predictive variances and closed-form leave-one-out cross-validation from a single factorization.
Structured linear algebra and Gaussian primitives for JAX.
LOVECache
¶
Bases: Module
Cached rank-k Lanczos approximation of K^{-1}.
k steps of symmetric Lanczos from a start vector v_0 build an
orthonormal basis of the Krylov space
\mathcal{K}_k(K, v_0) = \mathrm{span}\{v_0, K v_0, \dots, K^{k-1} v_0\}
and the tridiagonal T_k = Q_k^\top K Q_k. With the eigendecomposition
T_k = S \Theta S^\top, the cache stores the Ritz vectors
Q = Q_k S and the inverse Ritz values 1 / \theta_i, so that
Ritz pairs approximate eigenpairs of K only once they have converged
(first at the extremes of the spectrum); for k < N they are not the
eigenpairs of K. K^{-1} - Q \Theta^{-1} Q^\top is positive
semi-definite, so the approximation never over-estimates a quadratic form
k_*^\top K^{-1} k_* (see love_variance).
Attributes:
| Name | Type | Description |
|---|---|---|
Q |
Float[Array, 'N k']
|
Ritz vectors (an orthonormal basis of the Krylov space),
shape |
inv_eigvals |
Float[Array, ' k']
|
Inverse Ritz values |
Source code in src/gaussx/_gp/_love.py
LOOResult
¶
Bases: Module
Result of leave-one-out cross-validation.
Attributes:
| Name | Type | Description |
|---|---|---|
loo_log_likelihood |
Float[Array, '']
|
Scalar LOO-CV log-likelihood. |
loo_means |
Float[Array, ' N']
|
Per-point LOO predictive means, shape |
loo_variances |
Float[Array, ' N']
|
Per-point LOO predictive variances, shape |
Source code in src/gaussx/_gp/_loo.py
love_cache(K_op: lx.AbstractLinearOperator, lanczos_order: int = 50, key: jax.Array | None = None, *, initial_vector: Float[Array, ' N'] | None = None) -> LOVECache
¶
Precompute a Lanczos approximation of K^{-1} for fast variance.
Runs k = min(lanczos_order, N) steps of symmetric Lanczos (with full
reorthogonalisation) on K from the start vector v_0 and returns
the Galerkin approximation
where P_k is the orthogonal projector onto
K^{1/2} \mathcal{K}_k(K, v_0). Because P_k \preceq I the
approximation is a lower bound on K^{-1} in the Loewner order: the
quadratic form returned by love_variance is biased low and the GP
predictive variance k_{**} - k_*^\top K^{-1} k_* built from it is
biased high, until the Krylov space captures every direction in
which k_* has weight. At k = N the result is exact.
Choosing lanczos_order: the required k is set by the number of
eigenvalues of K well above the noise level (the kernel's effective
rank at the noise variance), not by N. A smooth kernel with small
noise can need noticeably more steps than its numerical rank suggests.
Check convergence with love_residual / love_variance_error_bound on
representative test points.
Pseudocode:
v0 = initial_vector or normal(key, (N,))
Q_k, T_k = lanczos(K, v0, k) # k matvecs with K
theta, S = eigh(T_k)
Q = Q_k @ S; inv_eigvals = 1 / theta
Once cached, each test point costs O(Nk) instead of an O(N^2)
solve (Pleiss et al., 2018).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
K_op
|
AbstractLinearOperator
|
Training kernel operator |
required |
lanczos_order
|
int
|
Number of Lanczos steps |
50
|
key
|
Array | None
|
PRNG key for a random start vector. If |
None
|
initial_vector
|
Float[Array, ' N'] | None
|
Deterministic start vector |
None
|
A lineax.DiagonalLinearOperator, BlockDiag, Kronecker or
KroneckerSum K is not run through Lanczos: its exact structured
eigendecomposition is truncated to the top k eigenpairs (key
and initial_vector are unused). That is also a Galerkin
approximation, with the same one-signed bias.
Returns:
| Type | Description |
|---|---|
LOVECache
|
A |
Source code in src/gaussx/_gp/_love.py
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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | |
love_variance(cache: LOVECache, K_star_row: Float[Array, ' N']) -> Float[Array, '']
¶
Approximate k_*^\top K^{-1} k_* from a LOVE cache.
Computes, in O(Nk),
The inequality is the one-signed bias of the Lanczos approximation
(see love_cache): the GP predictive variance
is an upper bound on the exact predictive variance, tight once the
cache has converged for this k_* (check with love_residual).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache
|
LOVECache
|
A |
required |
K_star_row
|
Float[Array, ' N']
|
Cross-covariance vector |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar approximation of |
Source code in src/gaussx/_gp/_love.py
love_residual(cache: LOVECache, K_op: lx.AbstractLinearOperator, K_star_row: Float[Array, ' N']) -> Float[Array, '']
¶
Relative solve residual of a LOVE cache for one cross-covariance.
Convergence diagnostic for love_variance: with
a = Q \Theta^{-1} Q^\top k_* (the cache's approximation of
K^{-1} k_*), returns
r = 0 exactly when the cache solves K a = k_*, in which case
love_variance is exact for this k_* (k_* = 0 returns 0).
A value well above the working precision means lanczos_order is
too small for this test point. A small value is not sufficient on its
own for an ill-conditioned K: the variance error is
r^\top K^{-1} r, which can be large for a small \lVert r \rVert
when K has tiny eigenvalues; love_variance_error_bound turns the
residual into a bound in variance units. Costs one matvec with K.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache
|
LOVECache
|
A |
required |
K_op
|
AbstractLinearOperator
|
The operator the cache was built from, shape |
required |
K_star_row
|
Float[Array, ' N']
|
Cross-covariance vector |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar relative residual |
Source code in src/gaussx/_gp/_love.py
love_variance_error_bound(cache: LOVECache, K_op: lx.AbstractLinearOperator, K_star_row: Float[Array, ' N'], lambda_min: float | Float[Array, '']) -> Float[Array, '']
¶
Upper bound on the variance over-estimate of a LOVE cache.
The cache is a Galerkin approximation, so with
a = Q \Theta^{-1} Q^\top k_* and r = K a - k_* (Q^\top r = 0)
the error of the quadratic form is exactly r^\top K^{-1} r:
Unlike the relative residual of love_residual, this is in the units
of the variance and accounts for the conditioning of K. For a GP
with K = K_f + \sigma^2 I the noise variance \sigma^2 is a
valid lambda_min.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache
|
LOVECache
|
A |
required |
K_op
|
AbstractLinearOperator
|
The operator the cache was built from, shape |
required |
K_star_row
|
Float[Array, ' N']
|
Cross-covariance vector, shape |
required |
lambda_min
|
float | Float[Array, '']
|
A lower bound on the smallest eigenvalue of |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar upper bound on the absolute predictive-variance error. |
Source code in src/gaussx/_gp/_love.py
leave_one_out_cv(operator: lx.AbstractLinearOperator, y: Float[Array, ' N'], *, solver: AbstractSolveStrategy | None = None, diag_inv_method: str = 'solve', diag_inv_num_probes: int = 30, diag_inv_key: jax.Array | None = None) -> LOOResult
¶
LOO-CV via the bordered-system identity.
Computes leave-one-out predictive means, variances, and log-likelihood without refitting the model N times.
Math:
alpha = K_y^{-1} y
mu_LOO_i = y_i - alpha_i / [K_y^{-1}]_{ii}
sigma^2_LOO_i = 1 / [K_y^{-1}]_{ii}
LOO-CV = -(1/2) sum_i [ log sigma^2_LOO_i
+ (y_i - mu_LOO_i)^2 / sigma^2_LOO_i
+ log 2 pi ]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
operator
|
AbstractLinearOperator
|
A linear operator representing the (noise-inclusive) kernel matrix K_y. |
required |
y
|
Float[Array, ' N']
|
Observation vector of shape |
required |
solver
|
AbstractSolveStrategy | None
|
Optional solve strategy for computing K_y^{-1} y
and for the |
None
|
diag_inv_method
|
str
|
Method passed to |
'solve'
|
diag_inv_num_probes
|
int
|
Number of Hutchinson probes when
|
30
|
diag_inv_key
|
Array | None
|
PRNG key for probe generation when
|
None
|
Returns:
| Type | Description |
|---|---|
LOOResult
|
A |
LOOResult
|
predictive means, and predictive variances. |
Source code in src/gaussx/_gp/_loo.py
Multi-output projections¶
The orthogonal instantaneous linear mixing model (OILMM): project multi-output observations into independent latent processes and back.
Structured linear algebra and Gaussian primitives for JAX.
oilmm_project(Y: Float[Array, 'N P'], W: Float[Array, 'P L'], noise_var: Float[Array, ' P'] | float, *, check_orthonormal: bool = False, orthonormal_tol: float | None = None) -> tuple[Float[Array, 'N L'], Float[Array, ' L']]
¶
Project multi-output data to independent latent GPs via OILMM.
Given an orthonormal mixing matrix W ∈ ℝᴾˣᴸ with WᵀW = I_L, projects P-output observations to L independent latent channels:
Y_latent = Y W (N, L)
σ²_latent = diag(Wᵀ D W) = (W ⊙ W)ᵀ σ² (L,)
with D = diag(σ²). For isotropic noise Wᵀ D W = σ² I is exactly
diagonal. For heteroscedastic noise Wᵀ D W has off-diagonal terms;
only its diagonal is returned, which is the OILMM independence
approximation (the latent channels are treated as independent).
WᵀW = I is a precondition and is not checked by default: with a
non-orthonormal W the projection Y W is not the least-squares
one (that would be Y W (WᵀW)⁻¹) and the latent noise is wrong.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Y
|
Float[Array, 'N P']
|
Observations, shape |
required |
W
|
Float[Array, 'P L']
|
Orthonormal mixing matrix, shape |
required |
noise_var
|
Float[Array, ' P'] | float
|
Observation noise variance. Scalar for isotropic noise,
or shape |
required |
check_orthonormal
|
bool
|
If |
False
|
orthonormal_tol
|
float | None
|
Tolerance for |
None
|
Returns:
| Type | Description |
|---|---|
Float[Array, 'N L']
|
Tuple |
Float[Array, ' L']
|
and |
Source code in src/gaussx/_gp/_oilmm.py
oilmm_back_project(f_means: Float[Array, 'N L'], f_vars: Float[Array, 'N L'], W: Float[Array, 'P L']) -> tuple[Float[Array, 'N P'], Float[Array, 'N P']]
¶
Back-project latent GP predictions to the observation space.
Reconstructs observation-space predictions via:
y_means = f_means Wᵀ (N, P)
y_vars = f_vars (W ⊙ W)ᵀ (N, P)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
f_means
|
Float[Array, 'N L']
|
Latent predictive means, shape |
required |
f_vars
|
Float[Array, 'N L']
|
Latent predictive variances, shape |
required |
W
|
Float[Array, 'P L']
|
Orthogonal mixing matrix, shape |
required |
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, 'N P'], Float[Array, 'N P']]
|
Tuple |
Source code in src/gaussx/_gp/_oilmm.py
Deprecated aliases¶
These names still work but warn on use and will be removed in gaussx 0.7.0 (see the deprecation policy).
Structured linear algebra and Gaussian primitives for JAX.
base_conditional(K_mm: Float[Array, 'M M'], K_mn: Float[Array, 'M N'], K_nn: Float[Array, 'N N'] | Float[Array, ' N'], f: Float[Array, 'M R'] | Float[Array, ' M'], *, q_sqrt: Float[Array, 'R M M'] | Float[Array, 'M R'] | Float[Array, 'M M'] | Float[Array, ' M'] | None = None, white: bool = False, solver: AbstractSolverStrategy | None = None) -> tuple[Float[Array, 'N R'], Float[Array, ...]]
¶
Deprecated: use sparse_conditional (note K_xz = K_mn.T).
base_conditional(K_mm, K_mn, K_nn, f, q_sqrt=..., white=...) equals
sparse_conditional(K_mm, K_mn.T, K_nn, f, q_sqrt=..., white=...).
It takes the cross-covariance as (M, N) (inducing × data), the
transpose of every other sparse-GP helper; for M == N a transposed
argument is not detectable, so the convention is changed under a new
name rather than in place. solver was never used. Removed in
gaussx 0.7.0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
K_mm
|
Float[Array, 'M M']
|
Prior covariance at inducing points, shape |
required |
K_mn
|
Float[Array, 'M N']
|
Cross-covariance, shape |
required |
K_nn
|
Float[Array, 'N N'] | Float[Array, ' N']
|
Test-point covariance, full |
required |
f
|
Float[Array, 'M R'] | Float[Array, ' M']
|
Inducing function values, shape |
required |
q_sqrt
|
Float[Array, 'R M M'] | Float[Array, 'M R'] | Float[Array, 'M M'] | Float[Array, ' M'] | None
|
Optional variational root, as in |
None
|
white
|
bool
|
Whitened parameterisation, as in |
False
|
solver
|
AbstractSolverStrategy | None
|
Ignored. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, 'N R'], Float[Array, ...]]
|
|