Quadrature & Moment Matching¶
Gaussian integration: \(\mathbb{E}_{x \sim \mathcal{N}(\mu, \Sigma)}[f(x)]\) via
deterministic rules (Gauss-Hermite, unscented / cubature, Taylor) or Monte
Carlo, behind one AbstractIntegrator interface. Everything that needs an
expectation — expected log-likelihoods, EP tilted moments, uncertain-input GP
predictions — takes an integrator argument, so swapping the rule never touches
the model.
State & integrators¶
GaussianState pairs a mean with a covariance operator; integrators
propagate functions of it.
Structured linear algebra and Gaussian primitives for JAX.
GaussianState
¶
Bases: Module
Gaussian distribution as (mean, covariance operator) pair.
Attributes:
| Name | Type | Description |
|---|---|---|
mean |
Float[Array, ' N']
|
Mean vector, shape |
cov |
AbstractLinearOperator
|
Covariance operator, shape |
Source code in src/gaussx/_quadrature/_types.py
PropagationResult
¶
Bases: Module
Output of uncertainty propagation through a nonlinear function.
Tag convention for state.cov: lineax.positive_semidefinite_tag
when the rule guarantees a PSD covariance at the input dimension
(integrator.guarantees_psd(N): Gauss-Hermite, spherical cubature,
unscented with non-negative weights, Monte Carlo, ADF, Taylor), else
lineax.symmetric_tag (negative-weight rules such as the default
scaled unscented transform or the fifth-order cubature rule).
Dispatch on lineax.is_positive_semidefinite therefore depends only
on whether PSD is guaranteed, not on which rule produced the state.
Attributes:
| Name | Type | Description |
|---|---|---|
state |
GaussianState
|
Output Gaussian distribution. |
cross_cov |
Float[Array, 'N_in N_out'] | None
|
Input-output cross-covariance, shape |
Source code in src/gaussx/_quadrature/_types.py
AbstractIntegrator
¶
Bases: Module
Protocol for Gaussian integral approximation.
Subclasses implement integrate to propagate a Gaussian through
a nonlinear function, returning an approximate output distribution
and (optionally) input-output cross-covariance.
Source code in src/gaussx/_quadrature/_integrator.py
integrate(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState) -> PropagationResult
abstractmethod
¶
Propagate a Gaussian through fn, returning output moments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Float[Array, ' N']], Float[Array, ' M']]
|
Nonlinear function mapping |
required |
state
|
GaussianState
|
Input Gaussian distribution. |
required |
Returns:
| Type | Description |
|---|---|
PropagationResult
|
|
PropagationResult
|
cross-covariance. |
Source code in src/gaussx/_quadrature/_integrator.py
guarantees_psd(dim: int) -> bool
¶
Whether the rule's moment triple is PSD by construction at dim.
A rule whose covariance weights are all non-negative (or a
linearisation, which propagates the covariance exactly) returns a
consistent joint over (x, g(x)), so its output covariance and
the implied conditional are PSD. Consumers such as
gaussx.nonlinear_kalman_filter skip their definiteness checks for
such rules (gh-331). The default is False, so a custom rule is
always checked; the built-in positive-weight rules override it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dim
|
int
|
Input dimension |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in src/gaussx/_quadrature/_integrator.py
points_and_weights(state: GaussianState) -> tuple[Float[Array, 'P N'], Float[Array, ' P'], Float[Array, ' P']]
¶
Return the raw quadrature points and weights for state.
Point-based rules override this so that consumers needing the raw
evaluations — gaussx.moment_match,
gaussx.statistical_linear_regression — can reuse a single pass
over the points instead of re-deriving the rule.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
GaussianState
|
Input Gaussian distribution. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, 'P N']
|
Tuple |
Float[Array, ' P']
|
|
Float[Array, ' P']
|
|
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the integrator is not point-based, e.g.
|
Source code in src/gaussx/_quadrature/_integrator.py
GaussHermiteIntegrator
¶
Bases: AbstractIntegrator
Gauss-Hermite quadrature integrator.
Approximates Gaussian expectations using tensor-product Gauss-Hermite quadrature:
E[g(f)] \approx \sum_i w_i \cdot g(\mu + L z_i)
where (z_i, w_i) are GH points/weights in standard normal space
and L is the square root of the covariance.
Exact for polynomials up to degree 2 * order - 1.
Cost: a tensor-product rule with P = order^dim points, each one an
evaluation of the integrand. At the default order=20 that is 20
points at dim=1, 400 at dim=2, 8 000 at dim=3 and 160 000 at
dim=4. Use it for dim <= 2 (or lower order); for larger
dim prefer CubatureIntegrator (2 dim points) or
FifthOrderCubatureIntegrator (2 dim^2 + 1 points).
Attributes:
| Name | Type | Description |
|---|---|---|
order |
int
|
Number of quadrature points per dimension. Default |
Source code in src/gaussx/_quadrature/_gauss_hermite.py
guarantees_psd(dim: int) -> bool
¶
integrate(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState) -> PropagationResult
¶
Propagate Gaussian via Gauss-Hermite quadrature.
Source code in src/gaussx/_quadrature/_gauss_hermite.py
points_and_weights(state: GaussianState) -> tuple[Float[Array, 'P N'], Float[Array, ' P'], Float[Array, ' P']]
¶
Return the tensor-product Gauss-Hermite points and weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
GaussianState
|
Input Gaussian distribution. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, 'P N']
|
Tuple |
Float[Array, ' P']
|
uses a single weight set, so |
Float[Array, ' P']
|
array, normalised to sum to one. |
Source code in src/gaussx/_quadrature/_gauss_hermite.py
UnscentedIntegrator
¶
Bases: AbstractIntegrator
Unscented transform: deterministic sigma points.
Generates 2N+1 sigma points around the mean, propagates them
through the nonlinear function, and reconstructs output moments:
chi_i = mu + sqrt((N + lambda) * Sigma) @ xi_i
y_i = f(chi_i)
mu_y = sum(w_m * y_i)
Sigma_y = sum(w_c * (y_i - mu_y)(y_i - mu_y)^T)
cross_cov = sum(w_c * (chi_i - mu)(y_i - mu_y)^T)
where lambda = alpha^2 * (N + kappa) - N.
Warning
The default alpha=1e-3 (the classic Wan-van der Merwe scaled
transform) puts a centre mean weight of about -1e6 on the
points and recovers the moments by cancellation, losing about six
digits: in float32 a smooth nonlinearity is wrong in the second
digit (gh-310). Prefer UnscentedIntegrator(alpha=1.0), the
symmetric 2N+1 rule with a zero centre mean weight (exact for
affine maps and positive covariance weights), which is what
moment_transform and the nonlinear Kalman filters use by
default. A UserWarning is emitted when the centre
mean weight reaches magnitude 1e3 with a float32 state.
Attributes:
| Name | Type | Description |
|---|---|---|
alpha |
float
|
Spread parameter. Default |
beta |
float
|
Prior knowledge parameter (2.0 optimal for Gaussian). |
kappa |
float
|
Secondary scaling. Default |
Source code in src/gaussx/_quadrature/_unscented.py
integrate(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState) -> PropagationResult
¶
Propagate Gaussian via unscented transform.
Source code in src/gaussx/_quadrature/_unscented.py
guarantees_psd(dim: int) -> bool
¶
Whether every scaled-unscented covariance weight is non-negative.
With lambda = alpha^2 (N + kappa) - N the weights are
1 / (2 (N + lambda)) off-centre and
lambda / (N + lambda) + 1 - alpha^2 + beta at the centre, so
small alpha (the 1e-3 default) makes the centre weight
large and negative; alpha = 1 does not.
Source code in src/gaussx/_quadrature/_unscented.py
points_and_weights(state: GaussianState) -> tuple[Float[Array, 'P N'], Float[Array, ' P'], Float[Array, ' P']]
¶
Return the scaled unscented sigma points and weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
GaussianState
|
Input Gaussian distribution. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, 'P N']
|
Tuple |
Float[Array, ' P']
|
covariance weights differ in the centre point. |
Source code in src/gaussx/_quadrature/_unscented.py
CubatureIntegrator
¶
Bases: AbstractIntegrator
Third-order spherical-radial cubature.
The 2N-point, equally weighted rule of Arasaratnam & Haykin
(2009), exact for polynomials up to total degree 3. Driving
gaussx.nonlinear_kalman_filter with it gives the textbook cubature
Kalman filter (CKF).
It is the degree-3 sibling of gaussx.FifthOrderCubatureIntegrator,
and closely related to gaussx.UnscentedIntegrator: the unscented
transform with alpha=1, beta=0, kappa=0 places its 2N
non-centre points identically, differing only by the centre point and
its weight. Unlike the scaled unscented transform, every weight here is
positive (1 / 2N), so an assembled covariance cannot be dragged
indefinite by a negative weight.
Source code in src/gaussx/_quadrature/_cubature.py
guarantees_psd(dim: int) -> bool
¶
integrate(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState) -> PropagationResult
¶
Propagate Gaussian via third-order cubature.
Source code in src/gaussx/_quadrature/_cubature.py
points_and_weights(state: GaussianState) -> tuple[Float[Array, 'P N'], Float[Array, ' P'], Float[Array, ' P']]
¶
Return the third-order cubature points and weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
GaussianState
|
Input Gaussian distribution. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, 'P N']
|
Tuple |
Float[Array, ' P']
|
single weight set, so |
Source code in src/gaussx/_quadrature/_cubature.py
FifthOrderCubatureIntegrator
¶
Bases: AbstractIntegrator
Fifth-order fully symmetric cubature (McNamee & Stenger).
Exact for polynomials up to total degree 5 in N variables, using
2 N^2 + 1 points. This is the practical middle ground between the
degree-3 sigma-point rules and a tensor-product Gauss-Hermite rule:
================================== ============== ================
Rule Points Exact to degree
================================== ============== ================
UnscentedIntegrator 2N + 1 3
FifthOrderCubatureIntegrator 2 N^2 + 1 5
GaussHermiteIntegrator (order H) H^N 2H − 1
================================== ============== ================
It is the default integrator in BayesNewton, and the lowest-degree rule that gets the curvature right for likelihoods whose log-density behaves like a quartic near the mode (Poisson, Bernoulli with heavy-tailed inputs).
Note
The rule carries negative weights on the axis shell for N > 4
— see gaussx.fifth_order_cubature_points. Moments through degree
5 remain exact, but an output covariance assembled from very few
function evaluations is not guaranteed PSD in high dimension.
Source code in src/gaussx/_quadrature/_fifth_order.py
integrate(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState) -> PropagationResult
¶
Propagate Gaussian via fifth-order cubature.
Source code in src/gaussx/_quadrature/_fifth_order.py
guarantees_psd(dim: int) -> bool
¶
points_and_weights(state: GaussianState) -> tuple[Float[Array, 'P N'], Float[Array, ' P'], Float[Array, ' P']]
¶
Return the fifth-order cubature points and weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
GaussianState
|
Input Gaussian distribution. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, 'P N']
|
Tuple |
Float[Array, ' P']
|
set, so |
Source code in src/gaussx/_quadrature/_fifth_order.py
TaylorIntegrator
¶
Bases: AbstractIntegrator
1st or 2nd order Taylor expansion for uncertainty propagation.
1st order (EKF):
mu_y = f(mu_x)
Sigma_y = J @ Sigma_x @ J^T
cross_cov = Sigma_x @ J^T
2nd order:
mu_y_i += 0.5 * tr(H_i @ Sigma_x)
Sigma_y += correction from Hessians
Attributes:
| Name | Type | Description |
|---|---|---|
order |
int
|
Taylor expansion order (1 or 2). Default 1. |
correct_variance |
bool
|
If True and order=2, apply 2nd-order covariance
correction using 4th Gaussian moments. Default True to preserve
the historical |
Source code in src/gaussx/_quadrature/_taylor.py
guarantees_psd(dim: int) -> bool
¶
A linearisation propagates a consistent joint (J P Jᵀ plus a PSD
second-order correction).
integrate(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState) -> PropagationResult
¶
Propagate Gaussian via Taylor expansion.
Source code in src/gaussx/_quadrature/_taylor.py
MonteCarloIntegrator
¶
Bases: AbstractIntegrator
Monte Carlo moment matching: sample, propagate, compute moments.
Propagates uncertainty by drawing samples from the input Gaussian, evaluating the function at each sample, and computing empirical output moments:
x_i ~ N(mu, Sigma) (n_samples points)
y_i = f(x_i)
mu_y = mean(y_i)
Sigma_y = cov(y_i) + regularization * I
cross_cov = cov(x_i, y_i)
Attributes:
| Name | Type | Description |
|---|---|---|
n_samples |
int
|
Number of Monte Carlo samples. Default |
regularization |
float
|
Diagonal jitter for numerical stability. |
key |
Array | None
|
PRNG key. If |
Source code in src/gaussx/_quadrature/_monte_carlo.py
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 | |
guarantees_psd(dim: int) -> bool
¶
integrate(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState) -> PropagationResult
¶
Propagate Gaussian via Monte Carlo sampling.
Source code in src/gaussx/_quadrature/_monte_carlo.py
points_and_weights(state: GaussianState) -> tuple[Float[Array, 'P N'], Float[Array, ' P'], Float[Array, ' P']]
¶
Return the Monte Carlo samples and their weights.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
GaussianState
|
Input Gaussian distribution. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, 'P N']
|
Tuple |
Float[Array, ' P']
|
weights are |
Float[Array, ' P']
|
correction |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/gaussx/_quadrature/_monte_carlo.py
AssumedDensityFilter
¶
Bases: AbstractIntegrator
KL-optimal Gaussian projection via moment matching.
Projects the (possibly non-Gaussian) output distribution onto the
Gaussian family by matching first and second moments. Equivalent to
argmin_q KL(p(y) || q(y)) within the Gaussian family.
Adds adaptive regularization and optional diagnostics for detecting non-Gaussianity:
eps = eps_base * trace(Sigma_y) / n_dim
Attributes:
| Name | Type | Description |
|---|---|---|
n_samples |
int
|
Number of Monte Carlo samples. Default |
regularization |
float
|
Base regularization. Default |
adaptive_regularization |
bool
|
Scale regularization by output
variance. Default |
key |
Array | None
|
PRNG key. If |
Source code in src/gaussx/_quadrature/_adf.py
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 138 139 140 141 142 143 144 145 | |
guarantees_psd(dim: int) -> bool
¶
points_and_weights(state: GaussianState) -> tuple[Float[Array, 'P N'], Float[Array, ' P'], Float[Array, ' P']]
¶
The MonteCarloIntegrator samples and weights for this rule.
Lets gaussx.moment_match / gaussx.statistical_linear_regression
use the same samples integrate does.
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/gaussx/_quadrature/_adf.py
integrate(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState) -> PropagationResult
¶
Propagate Gaussian via assumed density filtering.
Source code in src/gaussx/_quadrature/_adf.py
integrate_with_diagnostics(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState) -> tuple[PropagationResult, dict]
¶
Propagate Gaussian and return non-Gaussianity diagnostics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Float[Array, ' N']], Float[Array, ' M']]
|
Nonlinear function mapping |
required |
state
|
GaussianState
|
Input Gaussian distribution. |
required |
Returns:
| Type | Description |
|---|---|
PropagationResult
|
Tuple |
dict
|
|
tuple[PropagationResult, dict]
|
|
Source code in src/gaussx/_quadrature/_adf.py
moment_transform(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], mean: Float[Array, ' N'], cov: Float[Array, 'N N'], *, integrator: AbstractIntegrator | None = None) -> tuple[Float[Array, ' M'], Float[Array, 'M M'], Float[Array, 'N M']]
¶
Moment triple of (x, fn(x)) for x Gaussian.
The Gaussian moment transform,
with \(\mu_g = \mathbb{E}[g(x)]\), \(\Sigma_g = \mathrm{Cov}[g(x)]\) and \(\Sigma_{xg} = \mathrm{Cov}[x, g(x)]\).
This is the one method-specific step of every Gaussian filter: the
filter loop, the smoother backward pass and the log-likelihood are
identical across methods, and only \(\mathcal{T}\) changes. Taking
dense arrays and returning dense arrays, it is the convenient form for
building a filter — gaussx.nonlinear_kalman_filter is built on it —
while AbstractIntegrator.integrate is the operator-typed form.
Unlike calling integrate directly, this requires the
cross-covariance and says so if it is missing, rather than letting a
None propagate into a silently zero Kalman gain.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Float[Array, ' N']], Float[Array, ' M']]
|
The map |
required |
mean
|
Float[Array, ' N']
|
Input mean \(\mu\), shape |
required |
cov
|
Float[Array, 'N N']
|
Input covariance \(\Sigma\), shape |
required |
integrator
|
AbstractIntegrator | None
|
Rule realising \(\mathcal{T}\). Defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
Float[Array, ' M']
|
Tuple |
Float[Array, 'M M']
|
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If the integrator does not supply a cross-covariance. Raised at trace time, so it costs nothing per step. |
Source code in src/gaussx/_quadrature/_integrator.py
The integrators are also what selects the filter in
nonlinear_kalman_filter — EKF, UKF, CKF, GHKF and the
Monte-Carlo variant are the same loop under a different rule.
Quadrature rules¶
The raw point sets behind the integrators, for when a recipe needs direct control.
Structured linear algebra and Gaussian primitives for JAX.
gauss_hermite_points(order: int, dim: int, dtype: DTypeLike | None = None) -> tuple[Float[Array, 'P D'], Float[Array, ' P']]
¶
Gauss-Hermite quadrature points and weights.
Generates tensor-product Gauss-Hermite quadrature points for
integrating functions against a standard Gaussian measure
(probabilists' Hermite polynomials).
P = order^dim total points.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
int
|
Number of quadrature points per dimension. |
required |
dim
|
int
|
Dimensionality of the integration domain. |
required |
dtype
|
DTypeLike | None
|
Floating dtype of the returned points and weights.
Defaults to JAX's default float dtype. Unlike the other rules
here this one has no mean to take a dtype from, so callers
propagating an input dtype must pass it explicitly —
|
None
|
Returns:
| Type | Description |
|---|---|
Float[Array, 'P D']
|
Tuple |
Float[Array, ' P']
|
|
Source code in src/gaussx/_quadrature/_quadrature.py
cubature_points(mean: Float[Array, ' N'], cov: lx.AbstractLinearOperator) -> tuple[Float[Array, 'P N'], Float[Array, ' P']]
¶
Spherical-radial cubature points and weights.
Generates 2N cubature points with equal weights 1/(2N).
This is the cubature Kalman filter (CKF) point set.
Uses gaussx.sqrt(cov) for structured square root dispatch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mean
|
Float[Array, ' N']
|
Mean vector, shape |
required |
cov
|
AbstractLinearOperator
|
Covariance operator, shape |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, 'P N']
|
Tuple |
Float[Array, ' P']
|
|
tuple[Float[Array, 'P N'], Float[Array, ' P']]
|
|
Source code in src/gaussx/_quadrature/_quadrature.py
fifth_order_cubature_points(mean: Float[Array, ' N'], cov: lx.AbstractLinearOperator) -> tuple[Float[Array, 'P N'], Float[Array, ' P']]
¶
Fifth-order fully symmetric cubature points and weights.
The McNamee & Stenger (1967) degree-5 rule: exact for polynomials up
to total degree 5 in N variables, using P = 2 N^2 + 1 points
laid out on two shells plus the origin,
========== =========================== ============== ====================
Shell Points Multiplicity Weight
========== =========================== ============== ====================
origin 0 1 2 / (N + 2)
axis ±r₁ eᵢ 2N (4 − N) / (2 (N + 2)²)
diagonal ±r₂ (eᵢ ± eⱼ), i < j 2N(N − 1) 1 / (N + 2)²
========== =========================== ============== ====================
with r₁ = sqrt(N + 2) and r₂ = sqrt((N + 2) / 2). Points are
mapped into the input space as χᵢ = mean + S zᵢ where
S = gaussx.sqrt(cov).
This rule sits between sigma_points / cubature_points (2N + 1
and 2N points, degree 3) and a tensor-product gauss_hermite_points
rule (order^N points), and is the default integrator in BayesNewton.
Note
The axis weight is negative for N > 4 (and zero at N = 4).
That is inherent to the rule, not a bug: the weights still sum to
one and every moment through degree 5 is reproduced exactly, but
the rule is not a positive quadrature in high dimension.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mean
|
Float[Array, ' N']
|
Mean vector, shape |
required |
cov
|
AbstractLinearOperator
|
Covariance operator, shape |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, 'P N']
|
Tuple |
Float[Array, ' P']
|
|
tuple[Float[Array, 'P N'], Float[Array, ' P']]
|
|
Source code in src/gaussx/_quadrature/_quadrature.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | |
sigma_points(mean: Float[Array, ' N'], cov: lx.AbstractLinearOperator, alpha: float = 0.001, beta: float = 2.0, kappa: float = 0.0) -> tuple[Float[Array, 'P N'], Float[Array, ' P'], Float[Array, ' P']]
¶
Unscented transform sigma points and weights.
Generates 2N+1 deterministic sigma points for a Gaussian with
the given mean and covariance, using the scaled unscented transform.
Uses gaussx.sqrt(cov) for structured square root dispatch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mean
|
Float[Array, ' N']
|
Mean vector, shape |
required |
cov
|
AbstractLinearOperator
|
Covariance operator, shape |
required |
alpha
|
float
|
Spread parameter. Controls how far sigma points are
from the mean. Default |
0.001
|
beta
|
float
|
Prior distribution parameter. |
2.0
|
kappa
|
float
|
Secondary scaling parameter. Default |
0.0
|
Warning
With lambda = alpha^2 (N + kappa) - N the centre mean weight is
lambda / (N + lambda) \approx 1 - 1/alpha^2: about -1e6 at
the default alpha=1e-3. The moments are then recovered by
cancellation between weights of order 1e6, which costs about
six digits; in float32 a smooth nonlinearity comes out wrong in
the second digit (gh-310). alpha=1.0 (with beta=2,
kappa=0) is the symmetric 2N+1 rule with a zero centre mean
weight, exact for affine maps, and is what every gaussx-internal
default (moment_transform, the nonlinear Kalman filters) uses.
A UserWarning is emitted when the actual centre mean weight
(computed from alpha, kappa and N) has magnitude
>= 1e3 with a float32 (or lower precision) mean; it is skipped
when alpha or kappa is traced.
Returns:
| Type | Description |
|---|---|
Float[Array, 'P N']
|
Tuple |
Float[Array, ' P']
|
|
Float[Array, ' P']
|
|
tuple[Float[Array, 'P N'], Float[Array, ' P'], Float[Array, ' P']]
|
|
Source code in src/gaussx/_quadrature/_quadrature.py
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 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 | |
Likelihoods¶
Observation models with quadrature-friendly log_prob surfaces, shared by the
expectation helpers and the SSM / CVI recipes. Site-factorising likelihoods
also give site_derivatives, the per-site gradient and Hessian diagonal that
Newton-type inference (newton_update, laplace_mode) consumes; binomial and
negative binomial supply closed forms.
Structured linear algebra and Gaussian primitives for JAX.
AbstractLikelihood
¶
Bases: Module
Base class for likelihood functions with optional analytical ELL.
Subclasses that support closed-form expected log-likelihood under
a Gaussian variational distribution should override
has_analytical_ell to return True and implement
analytical_expected_log_likelihood.
Source code in src/gaussx/_quadrature/_likelihood.py
log_prob(f: Float[Array, ' N']) -> Float[Array, '']
abstractmethod
¶
Evaluate log p(y | f) for fixed observations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
f
|
Float[Array, ' N']
|
Latent function values, shape |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar log-likelihood. |
Source code in src/gaussx/_quadrature/_likelihood.py
site_derivatives(f: Float[Array, ' N']) -> tuple[Float[Array, ' N'], Float[Array, ' N']]
¶
Per-site gradient and Hessian diagonal of log p(y | f) in f.
For a likelihood that factorises over sites,
\(\log p(y\mid f) = \sum_i \log p(y_i\mid f_i)\), the Hessian is
diagonal and this returns (∂ log p / ∂f_i, ∂² log p / ∂f_i²), the
inputs of gaussx.newton_update's diagonal path and of
gaussx.laplace_mode. The default differentiates log_prob: one
reverse pass for the gradient and one forward pass of it along
1 for the diagonal (exact only when the Hessian is diagonal, so
not for SoftmaxLikelihood or HeteroscedasticGaussianLikelihood).
Subclasses may override it with closed forms.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
f
|
Float[Array, ' N']
|
Latent function values, shape |
required |
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ' N'], Float[Array, ' N']]
|
Tuple |
Source code in src/gaussx/_quadrature/_likelihood.py
has_analytical_ell() -> bool
¶
analytical_expected_log_likelihood(q_mu: Float[Array, ' N'], q_cov: lx.AbstractLinearOperator) -> Float[Array, '']
¶
Closed-form E_q[log p(y | f)] where q = N(q_mu, q_cov).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q_mu
|
Float[Array, ' N']
|
Variational mean, shape |
required |
q_cov
|
AbstractLinearOperator
|
Variational covariance operator, shape |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar expected log-likelihood. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If no analytical form exists. |
Source code in src/gaussx/_quadrature/_likelihood.py
GaussianLikelihood
¶
Bases: AbstractLikelihood
Gaussian likelihood log N(y | f, noise_var * I).
Supports closed-form expected log-likelihood:
E_q[log N(y | f, \sigma^2 I)]
= log N(y | q_\mu, \sigma^2 I)
- 0.5 / \sigma^2 \cdot tr(q_{cov})
Attributes:
| Name | Type | Description |
|---|---|---|
y |
Float[Array, ' N']
|
Observed targets, shape |
noise_var |
float
|
Observation noise variance (scalar). |
Source code in src/gaussx/_quadrature/_likelihood.py
log_prob(f: Float[Array, ' N']) -> Float[Array, '']
¶
Evaluate log N(y | f, noise_var * I).
Source code in src/gaussx/_quadrature/_likelihood.py
has_analytical_ell() -> bool
¶
analytical_expected_log_likelihood(q_mu: Float[Array, ' N'], q_cov: lx.AbstractLinearOperator) -> Float[Array, '']
¶
Closed-form E_q[log N(y | f, \sigma^2 I)].
Uses:
E_q[log N(y|f,R)] = log N(y | q_mu, R) - 0.5 tr(R^{-1} q_cov)
where R = noise_var * I. Delegates the log-density term to
gaussx.gaussian_log_prob (which exploits the diagonal
noise structure) and computes the trace correction directly via
the structural trace(q_cov) / noise_var shortcut, so
Kronecker/BlockDiag-structured q_cov keeps its O(n)
prod(trace_factor) / per-block trace fast paths instead
of materializing through trace_product(R^{-1}, q_cov).
Source code in src/gaussx/_quadrature/_likelihood.py
HeteroscedasticGaussianLikelihood
¶
Bases: AbstractLikelihood
Heteroscedastic Gaussian likelihood with input-dependent noise.
Attributes:
| Name | Type | Description |
|---|---|---|
y |
Float[Array, ' N']
|
Observations, shape |
Source code in src/gaussx/_quadrature/_likelihoods.py
log_prob(f: Float[Array, ' 2N']) -> Float[Array, '']
¶
Evaluate heteroscedastic Gaussian log-likelihood.
Source code in src/gaussx/_quadrature/_likelihoods.py
BernoulliLikelihood
¶
Bases: AbstractLikelihood
Bernoulli likelihood with logit link.
Attributes:
| Name | Type | Description |
|---|---|---|
y |
Float[Array, ' N']
|
Binary observations, shape |
Source code in src/gaussx/_quadrature/_likelihoods.py
log_prob(f: Float[Array, ' N']) -> Float[Array, '']
¶
Evaluate Bernoulli log-likelihood with logit link.
BinomialLikelihood
¶
Bases: AbstractLikelihood
Binomial likelihood with logit link.
\(y_i \sim \operatorname{Bin}(n_i, \sigma(f_i))\), so
with site gradient \(y_i - n_i\sigma(f_i)\) and Hessian
\(-n_i\sigma(f_i)\sigma(-f_i)\) (log-concave). n_trials = 1 is
BernoulliLikelihood.
Attributes:
| Name | Type | Description |
|---|---|---|
y |
Float[Array, ' N']
|
Success counts, shape |
n_trials |
Float[Array, ' N'] | float
|
Numbers of trials \(n_i\), a scalar or shape |
Examples:
import jax.numpy as jnp
import gaussx as gx
lik = gx.BinomialLikelihood(jnp.array([3.0, 0.0, 5.0]), n_trials=5.0)
lp = lik.log_prob(jnp.zeros(3))
grad, hess = lik.site_derivatives(jnp.zeros(3)) # [0.5, -2.5, 2.5], -1.25
Source code in src/gaussx/_quadrature/_likelihoods.py
log_prob(f: Float[Array, ' N']) -> Float[Array, '']
¶
Evaluate the binomial log-likelihood with logit link.
Source code in src/gaussx/_quadrature/_likelihoods.py
site_derivatives(f: Float[Array, ' N']) -> tuple[Float[Array, ' N'], Float[Array, ' N']]
¶
Closed-form (y − nσ(f), −nσ(f)σ(−f)).
Source code in src/gaussx/_quadrature/_likelihoods.py
PoissonLikelihood
¶
Bases: AbstractLikelihood
Poisson likelihood with log link.
Attributes:
| Name | Type | Description |
|---|---|---|
y |
Float[Array, ' N']
|
Count observations, shape |
Source code in src/gaussx/_quadrature/_likelihoods.py
log_prob(f: Float[Array, ' N']) -> Float[Array, '']
¶
Evaluate Poisson log-likelihood with log link.
NegativeBinomialLikelihood
¶
Bases: AbstractLikelihood
Negative-binomial likelihood with log link (NB2, a gamma-Poisson mixture).
\(y_i\) has mean \(\mu_i = e^{f_i}\) and variance \(\mu_i + \mu_i^2/r\), with
\(r\) the concentration (R-INLA's size; numpyro's
NegativeBinomial2 concentration). \(r\) is the dispersion
hyperparameter: a leaf, so it can be traced and differentiated as part
of \(\theta\). With \(s_i = \sigma(f_i - \log r) = \mu_i/(r + \mu_i)\),
with site gradient \(y_i - (r + y_i)s_i\) and Hessian
\(-(r + y_i)s_i(1 - s_i)\) (log-concave). \(r\to\infty\) is
PoissonLikelihood.
Attributes:
| Name | Type | Description |
|---|---|---|
y |
Float[Array, ' N']
|
Count observations, shape |
concentration |
Float[Array, ' N'] | float
|
\(r > 0\), a scalar or shape |
Examples:
import jax.numpy as jnp
import gaussx as gx
lik = gx.NegativeBinomialLikelihood(jnp.array([0.0, 4.0]), concentration=2.0)
lp = lik.log_prob(jnp.log(jnp.array([1.0, 3.0])))
grad, hess = lik.site_derivatives(jnp.zeros(2))
Source code in src/gaussx/_quadrature/_likelihoods.py
log_prob(f: Float[Array, ' N']) -> Float[Array, '']
¶
Evaluate the negative-binomial log-likelihood with log link.
Source code in src/gaussx/_quadrature/_likelihoods.py
site_derivatives(f: Float[Array, ' N']) -> tuple[Float[Array, ' N'], Float[Array, ' N']]
¶
Closed-form (y − (r + y)s, −(r + y)s(1 − s)), s = σ(f − log r).
Source code in src/gaussx/_quadrature/_likelihoods.py
SoftmaxLikelihood
¶
Bases: AbstractLikelihood
Softmax (categorical) likelihood for multi-class classification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
Int[Array, ' N']
|
Integer class labels, shape |
required |
num_classes
|
int
|
Number of classes C. |
required |
Source code in src/gaussx/_quadrature/_likelihoods.py
log_prob(f: Float[Array, ' NC']) -> Float[Array, '']
¶
Evaluate softmax log-likelihood.
Source code in src/gaussx/_quadrature/_likelihoods.py
StudentTLikelihood
¶
Bases: AbstractLikelihood
Student-t likelihood for robust regression.
Attributes:
| Name | Type | Description |
|---|---|---|
y |
Float[Array, ' N']
|
Observations, shape |
df |
float
|
Degrees of freedom (> 0). |
scale |
float
|
Scale parameter (> 0). |
Source code in src/gaussx/_quadrature/_likelihoods.py
log_prob(f: Float[Array, ' N']) -> Float[Array, '']
¶
Evaluate Student-t log-likelihood.
Source code in src/gaussx/_quadrature/_likelihoods.py
Expectations & EP moments¶
Expected log-likelihoods (the ELL term of every ELBO), generic mean / gradient / cost expectations, and the tilted-moment matching at the heart of expectation propagation.
Structured linear algebra and Gaussian primitives for JAX.
elbo(likelihood: AbstractLikelihood, state: GaussianState, kl: Float[Array, ''], integrator: AbstractIntegrator | None = None) -> Float[Array, '']
¶
Evidence lower bound (ELBO).
Computes:
ELBO = E_q[log p(y | f)] - KL(q || p)
Dispatches to analytical expected log-likelihood when available (e.g. Gaussian likelihood), or uses numerical integration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
likelihood
|
AbstractLikelihood
|
Likelihood object with |
required |
state
|
GaussianState
|
Variational Gaussian distribution |
required |
kl
|
Float[Array, '']
|
KL divergence |
required |
integrator
|
AbstractIntegrator | None
|
Integration method for non-conjugate likelihoods. Ignored when the likelihood has an analytical fast path. |
None
|
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar ELBO value. |
Source code in src/gaussx/_quadrature/_expectations.py
expected_log_likelihood(likelihood: AbstractLikelihood, state: GaussianState, integrator: AbstractIntegrator | None = None) -> Float[Array, '']
¶
Unified expected log-likelihood with analytical dispatch.
Computes E_q[log p(y | f)] where q = N(mu, Sigma).
If the likelihood has a closed-form expected log-likelihood
(e.g. GaussianLikelihood), it is used directly without
an integrator. Otherwise, an integrator must be provided
for numerical approximation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
likelihood
|
AbstractLikelihood
|
Likelihood object with |
required |
state
|
GaussianState
|
Variational Gaussian distribution. |
required |
integrator
|
AbstractIntegrator | None
|
Integration method. Required for non-conjugate likelihoods; ignored when the likelihood has an analytical fast path. |
None
|
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar expected log-likelihood. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no integrator is provided and the likelihood has no analytical form. |
Source code in src/gaussx/_quadrature/_expectations.py
log_likelihood_expectation(likelihood_fn: Callable[[Float[Array, ' N']], Float[Array, '']], state: GaussianState, integrator: AbstractIntegrator) -> Float[Array, '']
¶
Compute E[log p(y_{obs} | f(x))] where x ~ N(mu, Sigma).
For non-conjugate likelihoods (Bernoulli, Poisson, etc.) where the expectation has no closed form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
likelihood_fn
|
Callable[[Float[Array, ' N']], Float[Array, '']]
|
Function mapping latent values to scalar
log-likelihood: |
required |
state
|
GaussianState
|
Input Gaussian distribution. |
required |
integrator
|
AbstractIntegrator
|
Integration method. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar expected log-likelihood. |
Source code in src/gaussx/_quadrature/_expectations.py
mean_expectation(fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], state: GaussianState, integrator: AbstractIntegrator) -> Float[Array, ' M']
¶
Compute E[f(x)] where x ~ N(mu, Sigma).
For a point-based rule (one that implements points_and_weights)
this is the weighted sum \sum_p w^{(m)}_p f(\chi_p) alone: the
output covariance that integrator.integrate would also assemble is
never built, which matters for wide outputs (M = N^2 for a
flattened outer product). Other integrators (e.g. Taylor) fall back to
integrator.integrate(fn, state).state.mean.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Float[Array, ' N']], Float[Array, ' M']]
|
Function mapping |
required |
state
|
GaussianState
|
Input Gaussian distribution. |
required |
integrator
|
AbstractIntegrator
|
Integration method (Taylor, Unscented, MC, etc.). |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, ' M']
|
Expected function value, shape |
Source code in src/gaussx/_quadrature/_expectations.py
gradient_expectation(fn: Callable[[Float[Array, ' N']], Float[Array, '']], state: GaussianState, integrator: AbstractIntegrator) -> Float[Array, ' N']
¶
Compute E[nabla f(x)] via Stein's lemma.
Uses the identity:
E[nabla f(x)] = Sigma^{-1} Cov[x, f(x)]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[[Float[Array, ' N']], Float[Array, '']]
|
Scalar-valued function mapping |
required |
state
|
GaussianState
|
Input Gaussian distribution. |
required |
integrator
|
AbstractIntegrator
|
Integration method. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, ' N']
|
Expected gradient, shape |
Source code in src/gaussx/_quadrature/_expectations.py
cost_expectation(prediction_fn: Callable[[Float[Array, ' N']], Float[Array, ' M']], cost_fn: Callable[[Float[Array, ' M'], Float[Array, ' M']], Float[Array, '']], state: GaussianState, target: Float[Array, ' M'], integrator: AbstractIntegrator) -> Float[Array, '']
¶
Compute E[Cost(f(x), target)] where x ~ N(mu, Sigma).
For model-based RL: expected cost of a policy under state uncertainty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prediction_fn
|
Callable[[Float[Array, ' N']], Float[Array, ' M']]
|
Maps state to prediction, |
required |
cost_fn
|
Callable[[Float[Array, ' M'], Float[Array, ' M']], Float[Array, '']]
|
Cost function, |
required |
state
|
GaussianState
|
Input Gaussian distribution (uncertain state). |
required |
target
|
Float[Array, ' M']
|
Target value, shape |
required |
integrator
|
AbstractIntegrator
|
Integration method. |
required |
Returns:
| Type | Description |
|---|---|
Float[Array, '']
|
Scalar expected cost. |
Source code in src/gaussx/_quadrature/_expectations.py
ep_tilted_moments(log_lik_fn: Callable[[Float[Array, '']], Float[Array, '']], cav_mean: Float[Array, ' *batch'], cav_var: Float[Array, ' *batch'], *, order: int | None = None, integrator: AbstractIntegrator | None = None, power: float = 1.0) -> tuple[Float[Array, ' *batch'], Float[Array, ' *batch']]
¶
Moments of the scalar EP tilted distribution, elementwise over a batch.
For each cavity N(f | m, v) computes the mean and variance of the
tilted distribution
with the points f_i and mean weights w_i of a point-based rule
applied to the cavity:
These are the same tilted moments as m + v g and v + v H v from
gaussx.moment_match (Stein's lemma), and share its log-sum-exp shifted
weights. The default rule is Gauss-Hermite of order points, the
behaviour before integrator existed.
Pseudocode (per batch element):
if v <= 0: return m, v # invalid site, passed through
f, w = integrator.points_and_weights(N(m, v))
w~ = w * exp(a * log_lik(f) - max(a * log_lik(f)))
m_t = sum(w~ f) / sum(w~); v_t = sum(w~ (f - m_t)^2) / sum(w~)
return m_t, max(v_t, eps * v)
A site whose cavity variance is not positive -- EP's usual failure mode,
when the site holds more precision than the current posterior -- has no
tilted distribution. Its cavity moments are returned unchanged, so the
moment-matched site update 1/t_var - 1/cav_var is zero and nothing
NaN reaches the site parameters; skip or damp such sites in the
caller. Valid sites are unaffected, and gradients stay finite for both.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_lik_fn
|
Callable[[Float[Array, '']], Float[Array, '']]
|
Scalar function mapping latent value |
required |
cav_mean
|
Float[Array, ' *batch']
|
Cavity means, shape |
required |
cav_var
|
Float[Array, ' *batch']
|
Cavity variances, shape |
required |
order
|
int | None
|
Number of Gauss-Hermite points of the default rule. Default
|
None
|
integrator
|
AbstractIntegrator | None
|
Point-based rule (e.g. |
None
|
power
|
float
|
Power-EP exponent |
1.0
|
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ' *batch'], Float[Array, ' *batch']]
|
Tuple |
Raises:
| Type | Description |
|---|---|
ValueError
|
If both |
NotImplementedError
|
If |
Source code in src/gaussx/_quadrature/_tilted_moments.py
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 120 121 122 123 124 125 | |
Moment matching & linearisation¶
moment_match returns the tilted log-normaliser and its cavity-mean
derivatives from a single pass over the quadrature points; the tilted
moments and the EP site follow from those in closed form (see its
docstring — the derivatives are not themselves the site naturals).
statistical_linear_regression returns the moment-matched linear-Gaussian
surrogate \(p(y \mid f) \approx \mathcal{N}(y \mid Af + b, \Omega)\) behind
posterior linearisation and the iterated Kalman smoother. Both take any
point-based integrator.
Structured linear algebra and Gaussian primitives for JAX.
MomentMatchResult
¶
Bases: Module
Tilted log-normaliser and its derivatives w.r.t. the cavity mean.
Attributes:
| Name | Type | Description |
|---|---|---|
log_Z |
Float[Array, '']
|
Scalar tilted log-normaliser |
d_log_Z |
Float[Array, ' D']
|
Gradient |
d2_log_Z |
Float[Array, 'D D']
|
Hessian |
Source code in src/gaussx/_quadrature/_moment_match.py
SLRResult
¶
Bases: Module
Linear-Gaussian surrogate produced by statistical linear regression.
Encodes p(y | f) ≈ N(y | A f + b, Ω), the moment-matched
approximation of a nonlinear conditional under a Gaussian state.
Attributes:
| Name | Type | Description |
|---|---|---|
A |
Float[Array, 'M D']
|
Linearisation gain, shape |
b |
Float[Array, ' M']
|
Linearisation offset, shape |
omega |
Float[Array, 'M M']
|
Residual noise covariance, shape |
mu |
Float[Array, ' M']
|
Expected conditional mean |
Source code in src/gaussx/_quadrature/_slr.py
moment_match(log_lik_fn: Callable[[Float[Array, ' D']], Float[Array, '']], state: GaussianState, integrator: AbstractIntegrator, *, power: float = 1.0) -> MomentMatchResult
¶
EP moment matching: tilted log-normaliser and its first two derivatives.
Computes the log normaliser of the tilted distribution,
together with
This is the EP quantity, not E_q[log p(y|f)] (which is what
variational inference needs — see gaussx.expected_log_likelihood).
Writing g = d_log_Z and H = d2_log_Z, the tilted moments follow
directly from the two derivatives:
The Gaussian site is the natural-parameter difference between the tilted distribution and the cavity, which reduces to
in the same nat2 = +Λ convention gaussx.cavity_distribution uses.
That site approximates p(y | f) ** power, so divide both naturals by
power to recover the site for p(y | f) itself.
Do not feed (g, -H) in as the site: it drops the -H m term
and the (I + H C)^{-1} factor. Nor is gaussx.newton_update the
right conversion here — it yields (g - H m, -H), which is the
local-Newton site, i.e. only the first-order approximation to the EP
site above.
All three outputs come from a single pass over the quadrature points: applying Stein's lemma to the derivatives of the Gaussian density turns them into reweightings of the same likelihood evaluations, so no autodiff runs through the integrator and no extra likelihood evaluations are needed.
The sum over points is evaluated with the log-sum-exp shift, so
likelihoods that underflow in the raw scale are handled. Note that
quadrature weights may be negative (the scaled unscented transform and
the degree-5 cubature rule both have negative weights in places), so
Z is not guaranteed positive for a badly matched rule; prefer a
rule whose points cover the likelihood's mass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_lik_fn
|
Callable[[Float[Array, ' D']], Float[Array, '']]
|
Maps a latent |
required |
state
|
GaussianState
|
Cavity distribution |
required |
integrator
|
AbstractIntegrator
|
Point-based rule supplying the points and weights,
e.g. |
required |
power
|
float
|
Power-EP exponent |
1.0
|
Returns:
| Type | Description |
|---|---|
MomentMatchResult
|
|
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If |
ValueError
|
If |
Source code in src/gaussx/_quadrature/_moment_match.py
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 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 | |
statistical_linear_regression(conditional_mean_fn: Callable[[Float[Array, ' D']], Float[Array, ' M']], conditional_var_fn: Callable[[Float[Array, ' D']], Float[Array, 'M M']], state: GaussianState, integrator: AbstractIntegrator) -> SLRResult
¶
Moment-match a nonlinear conditional to a linear-Gaussian surrogate.
Given a conditional p(y | f) described by its first two moments and
a Gaussian state q(f) = N(m_q, C_ff), returns the linear-Gaussian
model p(y | f) ≈ N(y | A f + b, Ω) whose moments match those of the
true conditional under q.
With
the surrogate is
This is the shared primitive behind posterior linearisation, the
iterated Kalman smoother (IPLF / IPLS), and the conjugate Gaussian site
that an EP cavity meets. Feeding (A, b, omega) into a standard
linear-Gaussian Kalman update reproduces posterior-linearisation
inference exactly.
All expectations are evaluated with integrator, so the accuracy /
cost trade-off is chosen by the caller. Only the conditional moments
are needed — no autodiff runs through the quadrature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conditional_mean_fn
|
Callable[[Float[Array, ' D']], Float[Array, ' M']]
|
Maps a latent |
required |
conditional_var_fn
|
Callable[[Float[Array, ' D']], Float[Array, 'M M']]
|
Maps a latent |
required |
state
|
GaussianState
|
Gaussian state |
required |
integrator
|
AbstractIntegrator
|
Point-based rule used for the expectations, e.g.
|
required |
Returns:
| Type | Description |
|---|---|
SLRResult
|
|
SLRResult
|
expected conditional mean. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If |
ValueError
|
If |
Source code in src/gaussx/_quadrature/_slr.py
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 | |
Hyperparameter designs (INLA)¶
theta_design turns a hyperparameter log-posterior
\(\log\tilde\pi(\theta \mid y)\) and its mode \(\theta^\ast\) into a small set of
integration points \(\theta_k\) and normalised log-weights, the way R-INLA
integrates over \(\theta\) (Rue, Martino & Chopin, 2009, §6.5). Points are laid
out in the standardised coordinates \(\theta(z) = \theta^\ast + V\Lambda^{-1/2}z\),
where \(-\nabla^2\log\tilde\pi(\theta^\ast) = V\Lambda V^\top\) (exact, from
jax.hessian), and each design weight is corrected by the evaluated
log-posterior:
"eb"— empirical Bayes, the mode alone;"grid"— an axis-wise grid in \(z\), truncatedgrid_thresholdlog-units below the mode (the default for \(m \le 2\));"ccd"— a central composite design: centre, \(2m\) axial points and a resolution-V fractional factorial on the sphere of radius \(f_0\sqrt m\), \(O(m^2)\) points (15 at \(m = 3\), 27 at \(m = 5\)) against \(3^m\) for a grid (the default for \(m > 2\)).
The design is a pure function of any callable, so mode-finding stays with the
caller. A typical INLA step fits one Laplace approximation per design point in
a single vmap (laplace_mode lands with G8):
pts, logw = gaussx.theta_design(log_post, theta_star, method="ccd") # (15, 3), (15,)
fits = jax.vmap(lambda th: gaussx.laplace_mode(prior_at(th), lik, y))(pts)
post_mean = einx.dot("k, k n -> n", jnp.exp(logw), fits.mode)
Structured linear algebra and Gaussian primitives for JAX.
theta_design(log_post: Callable[[Array], Array], mode: Float[Array, ' m'], *, method: Literal['eb', 'grid', 'ccd'] | None = None, hessian: Float[Array, 'm m'] | None = None, grid_step: float = 1.0, grid_threshold: float = 2.5, ccd_f0: float = 1.1) -> tuple[Float[Array, 'K m'], Float[Array, ' K']]
¶
Integration points and log-weights over hyperparameters \(\theta\).
Builds the design R-INLA uses to integrate over \(\theta\). With \(-\nabla^2 \log\tilde\pi(\theta^\ast \mid y) = V \Lambda V^\top\) at the mode \(\theta^\ast\), points are placed in the standardised coordinates \(z\) of
in which the posterior is approximately \(\mathcal N(0, I)\). Each point gets a design weight \(\Delta_k\), which is corrected by the evaluated posterior, \(\log w_k = \log\Delta_k + \log\tilde\pi(\theta_k \mid y)\), and normalised so that \(\sum_k w_k = 1\). Then \(\tilde\pi(x_i \mid y) \approx \sum_k w_k\,\tilde\pi(x_i \mid \theta_k, y)\).
Methods:
"eb": empirical Bayes, the mode alone."grid": walk each \(z\)-axis in both directions in steps ofgrid_stepwhile \(\log\tilde\pi\) stays withingrid_thresholdlog-units of the mode, take the product grid of those axis ranges, and keep its points that are withingrid_thresholdof the mode. \(\Delta_k\) is constant. The point count depends onlog_post, so this method cannot be traced underjax.jit."ccd": a central composite design, \(O(m^2)\) points: the centre, \(2m\) axial points and a resolution-V fractional factorial \(2^{m-p}\), all non-centre points on the sphere of radius \(f_0\sqrt m\) (15 points at \(m = 3\), 27 at \(m = 5\)). The centre and shell weights are fixed, as in Rue et al. (2009) §6.5, so that the design integrates a Gaussian posterior exactly up to its second moments: \(\Delta_0 \propto 1 - f_0^{-2}\) and \(\Delta_{\text{shell}} \propto e^{m f_0^2/2} / ((K - 1) f_0^2)\).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_post
|
Callable[[Array], Array]
|
Unnormalised log-posterior \(\log\tilde\pi(\theta \mid y)\),
mapping a |
required |
mode
|
Float[Array, ' m']
|
Its mode \(\theta^\ast\), shape |
required |
method
|
Literal['eb', 'grid', 'ccd'] | None
|
|
None
|
hessian
|
Float[Array, 'm m'] | None
|
Hessian of |
None
|
grid_step
|
float
|
Step size in \(z\) for |
1.0
|
grid_threshold
|
float
|
Log-density drop from the mode beyond which
|
2.5
|
ccd_f0
|
float
|
Shell radius factor \(f_0 > 1\) for |
1.1
|
Returns:
| Type | Description |
|---|---|
Float[Array, 'K m']
|
Tuple |
Float[Array, ' K']
|
|
tuple[Float[Array, 'K m'], Float[Array, ' K']]
|
( |
Example
def log_post(theta):
return -0.5 * jnp.sum((theta - 1.0) ** 2 / jnp.array([1.0, 4.0, 9.0]))
pts, logw = gaussx.theta_design(log_post, jnp.ones(3), method="ccd")
pts.shape, logw.shape # (15, 3), (15,)
post_mean = einx.dot("k, k m -> m", jnp.exp(logw), pts) # == 1.0
# In INLA, one batched Laplace fit per design point:
fits = jax.vmap(lambda th: gaussx.laplace_mode(prior_at(th), lik, y))(pts)
x_mean = einx.dot("k, k n -> n", jnp.exp(logw), fits.mode)
Source code in src/gaussx/_quadrature/_theta_design.py
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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | |
Kernel expectations & uncertain-input GPs¶
The \(\Psi\)-statistics \(\Psi_0 = \mathbb{E}[k(x,x)]\), \(\Psi_1 = \mathbb{E}[k(x, X)]\), \(\Psi_2 = \mathbb{E}[k(x,\cdot)k(x,\cdot)^\top]\) and the GP / SVGP / VGP / BGPLVM predictive equations for inputs that are themselves Gaussian.
Structured linear algebra and Gaussian primitives for JAX.
AnalyticalPsiStatistics
¶
Bases: Protocol
Protocol for kernels with closed-form Ψ statistics.
Ψ statistics are required for uncertain-input GP models (e.g., BGPLVM). A kernel implementing this protocol provides analytical formulae instead of requiring numerical integration.
Source code in src/gaussx/_quadrature/_psi_statistics.py
kernel_expectations(kernel_fn: Callable[[Float[Array, ' D'], Float[Array, ' D']], Float[Array, '']], state: GaussianState, X_train: Float[Array, 'N_train D'], integrator: AbstractIntegrator) -> tuple[Float[Array, ''], Float[Array, ' N_train'], Float[Array, 'N_train N_train']]
¶
Compute kernel expectations Psi_0, Psi_1, Psi_2 for uncertain inputs.
These are the core quantities for GP inference with uncertain inputs:
Psi_0 = E[k(x, x)] scalar
Psi_1_i = E[k(x, x_i)] (N_train,)
Psi_2_{ij} = E[k(x, x_i) k(x, x_j)] (N_train, N_train)
For a point-based rule with points chi_p and mean weights w_p
they are computed directly from the (P, N_train) kernel evaluations
K_{pi} = k(chi_p, x_i):
so no (N_train^2, N_train^2) output covariance or per-point outer
product is ever built. Integrators without points (e.g. Taylor) go
through mean_expectation on the flattened outer product.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel_fn
|
Callable[[Float[Array, ' D'], Float[Array, ' D']], Float[Array, '']]
|
Kernel function |
required |
state
|
GaussianState
|
Uncertain input distribution |
required |
X_train
|
Float[Array, 'N_train D']
|
Training points, shape |
required |
integrator
|
AbstractIntegrator
|
Integration method. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ''], Float[Array, ' N_train'], Float[Array, 'N_train N_train']]
|
Tuple |
Source code in src/gaussx/_quadrature/_gp_predict.py
compute_psi_statistics(kernel: object, state: GaussianState, X_train: Float[Array, 'M D'], *, integrator: AbstractIntegrator | None = None) -> tuple[Float[Array, ''], Float[Array, ' M'], Float[Array, 'M M']]
¶
Compute Ψ statistics, dispatching to analytical or numerical.
If kernel implements AnalyticalPsiStatistics, uses
the closed-form methods. Otherwise, falls back to numerical
integration via the provided integrator:
Ψ₀ = E[k(x, x)] scalar
Ψ₁ᵢ = E[k(x, xᵢ)] (M,)
Ψ₂ᵢⱼ = E[k(x, xᵢ) k(x, xⱼ)] (M, M)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel
|
object
|
Kernel object, optionally implementing
|
required |
state
|
GaussianState
|
Input Gaussian distribution x ~ 𝒩(μ, Σ). |
required |
X_train
|
Float[Array, 'M D']
|
Training/inducing points, shape |
required |
integrator
|
AbstractIntegrator | None
|
Numerical integrator for fallback. Required if
|
None
|
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ''], Float[Array, ' M'], Float[Array, 'M M']]
|
Tuple |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/gaussx/_quadrature/_psi_statistics.py
uncertain_gp_predict(kernel_fn: Callable, X_train: Float[Array, 'N_train D'], alpha: Float[Array, ' N_train'], K_inv: lx.AbstractLinearOperator, state: GaussianState, integrator: AbstractIntegrator) -> tuple[Float[Array, ''], Float[Array, '']]
¶
Predictive mean and variance for GP with uncertain inputs.
Uses kernel expectations:
mu_pred = Psi_1 @ alpha
var_pred = Psi_0 - tr(K_inv @ Psi_2) + alpha^T @ Psi_2 @ alpha - mu_pred^2
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel_fn
|
Callable
|
Kernel function |
required |
X_train
|
Float[Array, 'N_train D']
|
Training points, shape |
required |
alpha
|
Float[Array, ' N_train']
|
Precomputed weights |
required |
K_inv
|
AbstractLinearOperator
|
Inverse of training kernel matrix operator. |
required |
state
|
GaussianState
|
Uncertain test input |
required |
integrator
|
AbstractIntegrator
|
Integration method. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ''], Float[Array, '']]
|
Tuple |
Source code in src/gaussx/_quadrature/_gp_predict.py
uncertain_gp_predict_mc(predict_fn: Callable[[Float[Array, ' D']], tuple[Float[Array, ''], Float[Array, '']]], state: GaussianState, n_particles: int = 100, key: jax.Array | None = None) -> tuple[Float[Array, ''], Float[Array, '']]
¶
Monte Carlo GP prediction with uncertain inputs.
Alternative to analytic kernel expectations when Psi integrals are intractable. Uses law of total variance:
mu = mean(particle_means)
var = var(particle_means) + mean(particle_vars)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predict_fn
|
Callable[[Float[Array, ' D']], tuple[Float[Array, ''], Float[Array, '']]]
|
GP predictor mapping |
required |
state
|
GaussianState
|
Uncertain test input |
required |
n_particles
|
int
|
Number of Monte Carlo particles. Default |
100
|
key
|
Array | None
|
PRNG key. If |
None
|
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ''], Float[Array, '']]
|
Tuple |
Source code in src/gaussx/_quadrature/_gp_predict.py
uncertain_svgp_predict(kernel_fn: Callable, Z: Float[Array, 'M D'], alpha: Float[Array, ' M'], Q: lx.AbstractLinearOperator, state: GaussianState, integrator: AbstractIntegrator) -> tuple[Float[Array, ''], Float[Array, '']]
¶
Predictive mean and variance for SVGP with uncertain inputs.
Uses kernel expectations with inducing points:
mu_pred = Psi_1 @ alpha
var_pred = Psi_0 + tr(Q @ Psi_2) + alpha^T @ Psi_2 @ alpha - mu_pred^2
where Q = K_{zz}^{-1} S K_{zz}^{-1} - K_{zz}^{-1} is the variance
adjustment operator (see gaussx.svgp_variance_adjustment).
The exact uncertain GP trace correction is recovered by setting
Q = -K_{zz}^{-1}; if Z equals the training inputs, this matches
gaussx.uncertain_gp_predict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel_fn
|
Callable
|
Kernel function |
required |
Z
|
Float[Array, 'M D']
|
Inducing points, shape |
required |
alpha
|
Float[Array, ' M']
|
Effective weights, shape |
required |
Q
|
AbstractLinearOperator
|
Variance adjustment operator |
required |
state
|
GaussianState
|
Uncertain test input |
required |
integrator
|
AbstractIntegrator
|
Integration method. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ''], Float[Array, '']]
|
Tuple |
Source code in src/gaussx/_quadrature/_gp_predict.py
uncertain_vgp_predict(kernel_fn: Callable, X_train: Float[Array, 'N_train D'], alpha: Float[Array, ' N_train'], Q: lx.AbstractLinearOperator, state: GaussianState, integrator: AbstractIntegrator) -> tuple[Float[Array, ''], Float[Array, '']]
¶
Predictive mean and variance for dense VGP with uncertain inputs.
Uses kernel expectations with training points:
mu_pred = Psi_1 @ alpha
var_pred = Psi_0 + tr(Q @ Psi_2) + alpha^T @ Psi_2 @ alpha - mu_pred^2
where Q = K^{-1} S K^{-1} - K^{-1} and alpha = K^{-1} m.
The exact uncertain GP is the special case Q = -K^{-1}. By contrast,
Q = 0 corresponds to S = K and removes only the trace correction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel_fn
|
Callable
|
Kernel function |
required |
X_train
|
Float[Array, 'N_train D']
|
Training points, shape |
required |
alpha
|
Float[Array, ' N_train']
|
Precomputed weights |
required |
Q
|
AbstractLinearOperator
|
Variance adjustment operator |
required |
state
|
GaussianState
|
Uncertain test input |
required |
integrator
|
AbstractIntegrator
|
Integration method. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ''], Float[Array, '']]
|
Tuple |
Source code in src/gaussx/_quadrature/_gp_predict.py
uncertain_bgplvm_predict(kernel_fn: Callable, X_train: Float[Array, 'N_train D'], alpha: Float[Array, 'N_train D_out'], K_inv: lx.AbstractLinearOperator, state: GaussianState, integrator: AbstractIntegrator) -> tuple[Float[Array, ' D_out'], Float[Array, ' D_out']]
¶
Multi-output uncertain GP prediction for BGPLVM.
Maps a latent variable z ~ N(mu, Sigma) to high-dimensional
reconstruction y using per-output GP weights:
mu_pred_d = Psi_1 @ alpha_d
var_pred_d = Psi_0 - tr(K_inv @ Psi_2)
+ alpha_d^T @ Psi_2 @ alpha_d - mu_pred_d^2
This intentionally uses the exact GP trace term for every output dimension.
Unlike gaussx.uncertain_vgp_predict and
gaussx.uncertain_svgp_predict, there is no separate variational
covariance correction operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel_fn
|
Callable
|
Kernel function |
required |
X_train
|
Float[Array, 'N_train D']
|
Training points, shape |
required |
alpha
|
Float[Array, 'N_train D_out']
|
Multi-output weights |
required |
K_inv
|
AbstractLinearOperator
|
Inverse of training kernel matrix operator. |
required |
state
|
GaussianState
|
Uncertain latent input |
required |
integrator
|
AbstractIntegrator
|
Integration method. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Float[Array, ' D_out'], Float[Array, ' D_out']]
|
Tuple |