derivkit 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- derivkit/__init__.py +22 -0
- derivkit/calculus/__init__.py +17 -0
- derivkit/calculus/calculus_core.py +152 -0
- derivkit/calculus/gradient.py +97 -0
- derivkit/calculus/hessian.py +528 -0
- derivkit/calculus/hyper_hessian.py +296 -0
- derivkit/calculus/jacobian.py +156 -0
- derivkit/calculus_kit.py +128 -0
- derivkit/derivative_kit.py +315 -0
- derivkit/derivatives/__init__.py +6 -0
- derivkit/derivatives/adaptive/__init__.py +5 -0
- derivkit/derivatives/adaptive/adaptive_fit.py +238 -0
- derivkit/derivatives/adaptive/batch_eval.py +179 -0
- derivkit/derivatives/adaptive/diagnostics.py +325 -0
- derivkit/derivatives/adaptive/grid.py +333 -0
- derivkit/derivatives/adaptive/polyfit_utils.py +513 -0
- derivkit/derivatives/adaptive/spacing.py +66 -0
- derivkit/derivatives/adaptive/transforms.py +245 -0
- derivkit/derivatives/autodiff/__init__.py +1 -0
- derivkit/derivatives/autodiff/jax_autodiff.py +95 -0
- derivkit/derivatives/autodiff/jax_core.py +217 -0
- derivkit/derivatives/autodiff/jax_utils.py +146 -0
- derivkit/derivatives/finite/__init__.py +5 -0
- derivkit/derivatives/finite/batch_eval.py +91 -0
- derivkit/derivatives/finite/core.py +84 -0
- derivkit/derivatives/finite/extrapolators.py +511 -0
- derivkit/derivatives/finite/finite_difference.py +247 -0
- derivkit/derivatives/finite/stencil.py +206 -0
- derivkit/derivatives/fornberg.py +245 -0
- derivkit/derivatives/local_polynomial_derivative/__init__.py +1 -0
- derivkit/derivatives/local_polynomial_derivative/diagnostics.py +90 -0
- derivkit/derivatives/local_polynomial_derivative/fit.py +199 -0
- derivkit/derivatives/local_polynomial_derivative/local_poly_config.py +95 -0
- derivkit/derivatives/local_polynomial_derivative/local_polynomial_derivative.py +205 -0
- derivkit/derivatives/local_polynomial_derivative/sampling.py +72 -0
- derivkit/derivatives/tabulated_model/__init__.py +1 -0
- derivkit/derivatives/tabulated_model/one_d.py +247 -0
- derivkit/forecast_kit.py +783 -0
- derivkit/forecasting/__init__.py +1 -0
- derivkit/forecasting/dali.py +78 -0
- derivkit/forecasting/expansions.py +486 -0
- derivkit/forecasting/fisher.py +298 -0
- derivkit/forecasting/fisher_gaussian.py +171 -0
- derivkit/forecasting/fisher_xy.py +357 -0
- derivkit/forecasting/forecast_core.py +313 -0
- derivkit/forecasting/getdist_dali_samples.py +429 -0
- derivkit/forecasting/getdist_fisher_samples.py +235 -0
- derivkit/forecasting/laplace.py +259 -0
- derivkit/forecasting/priors_core.py +860 -0
- derivkit/forecasting/sampling_utils.py +388 -0
- derivkit/likelihood_kit.py +114 -0
- derivkit/likelihoods/__init__.py +1 -0
- derivkit/likelihoods/gaussian.py +136 -0
- derivkit/likelihoods/poisson.py +176 -0
- derivkit/utils/__init__.py +13 -0
- derivkit/utils/concurrency.py +213 -0
- derivkit/utils/extrapolation.py +254 -0
- derivkit/utils/linalg.py +513 -0
- derivkit/utils/logger.py +26 -0
- derivkit/utils/numerics.py +262 -0
- derivkit/utils/sandbox.py +74 -0
- derivkit/utils/types.py +15 -0
- derivkit/utils/validate.py +811 -0
- derivkit-1.0.0.dist-info/METADATA +50 -0
- derivkit-1.0.0.dist-info/RECORD +68 -0
- derivkit-1.0.0.dist-info/WHEEL +5 -0
- derivkit-1.0.0.dist-info/licenses/LICENSE +21 -0
- derivkit-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Laplace approximation utilities.
|
|
2
|
+
|
|
3
|
+
Implements a Gaussian approximation to a posterior around a point ``theta_map``.
|
|
4
|
+
This is typically the *maximum a posteriori* (MAP). It uses a second-order Taylor expansion
|
|
5
|
+
of the negative log-posterior::
|
|
6
|
+
|
|
7
|
+
neg_logpost(theta) = -logposterior(theta)
|
|
8
|
+
neg_logpost(theta) ≈ neg_logpost(theta_map) + 0.5 * d^T H d
|
|
9
|
+
|
|
10
|
+
where ``d = theta - theta_map`` and ``H`` is the Hessian of ``neg_logpost`` evaluated
|
|
11
|
+
at ``theta_map``. The approximate covariance is ``cov ≈ H^{-1}``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
from numpy.typing import NDArray
|
|
21
|
+
|
|
22
|
+
from derivkit.calculus_kit import CalculusKit
|
|
23
|
+
from derivkit.utils.linalg import (
|
|
24
|
+
make_spd_by_jitter,
|
|
25
|
+
solve_or_pinv,
|
|
26
|
+
symmetrize_matrix,
|
|
27
|
+
)
|
|
28
|
+
from derivkit.utils.validate import (
|
|
29
|
+
validate_square_matrix_finite,
|
|
30
|
+
validate_theta_1d_finite,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"build_negative_logposterior",
|
|
35
|
+
"build_laplace_hessian",
|
|
36
|
+
"build_laplace_covariance",
|
|
37
|
+
"build_laplace_approximation",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_negative_logposterior(
|
|
42
|
+
theta: Sequence[float] | NDArray[np.float64],
|
|
43
|
+
*,
|
|
44
|
+
logposterior: Callable[[NDArray[np.float64]], float],
|
|
45
|
+
) -> float:
|
|
46
|
+
"""Computes the negative log-posterior at ``theta``.
|
|
47
|
+
|
|
48
|
+
This converts a user-provided log-posterior (often returned as a log-likelihoods
|
|
49
|
+
plus log-prior) into the objective most optimizers and curvature-based methods
|
|
50
|
+
work with: the negative log-posterior.
|
|
51
|
+
|
|
52
|
+
In practice, this is the scalar function whose Hessian at the *maximum a posteriori* (MAP)
|
|
53
|
+
point defines the Laplace (Gaussian) approximation to the posterior, and it is also
|
|
54
|
+
the quantity minimized by MAP estimation routines.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
theta: 1D array-like parameter vector.
|
|
58
|
+
logposterior: Callable that accepts a 1D float64 array and returns a scalar float.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Negative log-posterior value as a float.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ValueError: If ``theta`` is not a finite 1D vector or if the negative log-posterior
|
|
65
|
+
evaluates to a non-finite value.
|
|
66
|
+
"""
|
|
67
|
+
theta_array = validate_theta_1d_finite(theta, name="theta")
|
|
68
|
+
negative_log_posterior_value = -float(logposterior(theta_array))
|
|
69
|
+
|
|
70
|
+
if not np.isfinite(negative_log_posterior_value):
|
|
71
|
+
raise ValueError("Negative log-posterior evaluated to a non-finite value.")
|
|
72
|
+
|
|
73
|
+
return negative_log_posterior_value
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_laplace_hessian(
|
|
77
|
+
*,
|
|
78
|
+
neg_logposterior: Callable[[NDArray[np.float64]], float],
|
|
79
|
+
theta_map: Sequence[float] | NDArray[np.float64],
|
|
80
|
+
method: str | None = None,
|
|
81
|
+
n_workers: int = 1,
|
|
82
|
+
dk_kwargs: Mapping[str, Any] | None = None,
|
|
83
|
+
) -> NDArray[np.float64]:
|
|
84
|
+
"""Computes the Hessian of the negative log-posterior at ``theta_map``.
|
|
85
|
+
|
|
86
|
+
The Hessian at ``theta_map`` measures the local curvature of the posterior peak.
|
|
87
|
+
In the Laplace approximation, this Hessian plays the role of a local precision
|
|
88
|
+
matrix, and its inverse provides a fast Gaussian estimate of parameter
|
|
89
|
+
uncertainties and correlations.
|
|
90
|
+
|
|
91
|
+
Internally, this uses :class:`derivkit.calculus_kit.CalculusKit` (and therefore
|
|
92
|
+
DerivKit's Hessian construction machinery) to differentiate the scalar objective
|
|
93
|
+
``neg_logposterior(theta)``.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
neg_logposterior: Callable returning the scalar negative log-posterior.
|
|
97
|
+
theta_map: Point where the curvature is evaluated (typically the MAP).
|
|
98
|
+
method: Derivative method name/alias forwarded to the calculus machinery.
|
|
99
|
+
n_workers: Outer parallelism forwarded to Hessian construction.
|
|
100
|
+
dk_kwargs: Extra keyword arguments forwarded to :meth:`DerivativeKit.differentiate`.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
A symmetric 2D array with shape ``(p, p)`` giving the Hessian of
|
|
104
|
+
``neg_logposterior`` evaluated at ``theta_map``.
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
TypeError: If ``neg_logposterior`` is not scalar-valued (Hessian is not 2D).
|
|
108
|
+
ValueError: If inputs are invalid or the Hessian is not a finite square matrix.
|
|
109
|
+
"""
|
|
110
|
+
theta_map_array = validate_theta_1d_finite(theta_map, name="theta_map")
|
|
111
|
+
|
|
112
|
+
derivative_options = dict(dk_kwargs) if dk_kwargs is not None else {}
|
|
113
|
+
calculus_kit = CalculusKit(neg_logposterior, x0=theta_map_array)
|
|
114
|
+
|
|
115
|
+
hessian_raw = np.asarray(
|
|
116
|
+
calculus_kit.hessian(method=method, n_workers=n_workers, **derivative_options),
|
|
117
|
+
dtype=np.float64,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if hessian_raw.ndim != 2:
|
|
121
|
+
raise TypeError(
|
|
122
|
+
"laplace_hessian requires a scalar negative log-posterior; "
|
|
123
|
+
f"got Hessian with ndim={hessian_raw.ndim} and shape {hessian_raw.shape}."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
hessian_matrix = validate_square_matrix_finite(hessian_raw, name="Hessian")
|
|
127
|
+
|
|
128
|
+
expected_shape = (theta_map_array.size, theta_map_array.size)
|
|
129
|
+
if hessian_matrix.shape != expected_shape:
|
|
130
|
+
raise ValueError(f"Hessian must have shape {expected_shape}, got {hessian_matrix.shape}.")
|
|
131
|
+
|
|
132
|
+
return symmetrize_matrix(hessian_matrix)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def build_laplace_covariance(
|
|
136
|
+
hessian: NDArray[np.float64],
|
|
137
|
+
*,
|
|
138
|
+
rcond: float = 1e-12,
|
|
139
|
+
) -> NDArray[np.float64]:
|
|
140
|
+
"""Computes the Laplace covariance matrix from a Hessian.
|
|
141
|
+
|
|
142
|
+
In the Laplace (Gaussian) approximation, the Hessian of the negative
|
|
143
|
+
log-posterior at the expansion point acts like a local precision matrix.
|
|
144
|
+
The approximate posterior covariance is the matrix inverse of that Hessian.
|
|
145
|
+
|
|
146
|
+
This helper inverts the Hessian using DerivKit's robust linear-solve routine.
|
|
147
|
+
It falls back to a pseudoinverse when the Hessian is ill-conditioned and returns
|
|
148
|
+
a symmetrized covariance matrix suitable for downstream use, such as Gaussian
|
|
149
|
+
approximations, uncertainty summaries, and proposal covariances.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
hessian: 2D square Hessian matrix. This is usually the Hessian of the negative
|
|
153
|
+
log-posterior evaluated at the expansion point.
|
|
154
|
+
rcond: Cutoff for small singular values used by the pseudoinverse fallback.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
A 2D symmetric covariance matrix with the same shape as ``hessian``.
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
ValueError: If ``hessian`` is not a finite square matrix.
|
|
161
|
+
"""
|
|
162
|
+
hessian_matrix = validate_square_matrix_finite(hessian, name="Hessian")
|
|
163
|
+
hessian_matrix = symmetrize_matrix(hessian_matrix)
|
|
164
|
+
|
|
165
|
+
n_parameters = hessian_matrix.shape[0]
|
|
166
|
+
identity_matrix = np.eye(n_parameters, dtype=np.float64)
|
|
167
|
+
|
|
168
|
+
covariance_matrix = solve_or_pinv(
|
|
169
|
+
hessian_matrix,
|
|
170
|
+
identity_matrix,
|
|
171
|
+
rcond=rcond,
|
|
172
|
+
assume_symmetric=True,
|
|
173
|
+
warn_context="Laplace covariance",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
covariance_matrix = validate_square_matrix_finite(covariance_matrix, name="covariance")
|
|
177
|
+
return symmetrize_matrix(covariance_matrix)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def build_laplace_approximation(
|
|
181
|
+
*,
|
|
182
|
+
neg_logposterior: Callable[[NDArray[np.float64]], float],
|
|
183
|
+
theta_map: Sequence[float] | NDArray[np.float64],
|
|
184
|
+
method: str | None = None,
|
|
185
|
+
n_workers: int = 1,
|
|
186
|
+
dk_kwargs: Mapping[str, Any] | None = None,
|
|
187
|
+
ensure_spd: bool = True,
|
|
188
|
+
rcond: float = 1e-12,
|
|
189
|
+
) -> dict[str, Any]:
|
|
190
|
+
"""Computes a Laplace (Gaussian) approximation around ``theta_map``.
|
|
191
|
+
|
|
192
|
+
The Laplace approximation replaces the posterior near its peak with a Gaussian.
|
|
193
|
+
It does this by measuring the local curvature of the negative log-posterior
|
|
194
|
+
using its Hessian at ``theta_map``. The Hessian acts like a local precision
|
|
195
|
+
matrix, and its inverse is the approximate covariance.
|
|
196
|
+
|
|
197
|
+
This is useful when a fast, local summary of the posterior is needed without
|
|
198
|
+
running a full MCMC sampler. The output includes the expansion point,
|
|
199
|
+
negative log-posterior value there, the Hessian, and the covariance matrix.
|
|
200
|
+
In this approximation the Hessian acts as a local precision matrix, and its
|
|
201
|
+
inverse is the approximate covariance.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
neg_logposterior: Callable that accepts a 1D float64 parameter vector and
|
|
205
|
+
returns a scalar negative log-posterior value.
|
|
206
|
+
theta_map: Expansion point for the approximation. This is often the *maximum a
|
|
207
|
+
posteriori estimate* (MAP), which is the parameter vector that maximizes the
|
|
208
|
+
posterior.
|
|
209
|
+
method: Derivative method name/alias forwarded to the Hessian builder.
|
|
210
|
+
n_workers: Outer parallelism forwarded to Hessian construction.
|
|
211
|
+
dk_kwargs: Extra keyword arguments forwarded to :meth:`derivkit.DerivativeKit.differentiate`.
|
|
212
|
+
ensure_spd: If ``True``, attempt to regularize the Hessian to be symmetric positive definite
|
|
213
|
+
(SPD) by adding diagonal jitter. This ensures the Gaussian approximation has a valid
|
|
214
|
+
covariance matrix.
|
|
215
|
+
rcond: Cutoff for small singular values used by the pseudoinverse fallback
|
|
216
|
+
when computing the covariance.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
A dictionary with key-value pairs:
|
|
220
|
+
|
|
221
|
+
- "theta_map": 1D array of the expansion point (dtype ``float64``)).
|
|
222
|
+
- "neg_logposterior_at_map": negative log-posterior at the expansion point (dtype ``float``).
|
|
223
|
+
- "hessian": array containing the Hessian of the negative log-posterior with shape ``(p, p)`` (local precision, dtype ``float64``).
|
|
224
|
+
- "cov": covariance matrix with shape ``(p, p)`` (approximate inverse Hessian).
|
|
225
|
+
- "jitter": amount of diagonal jitter added (0.0 if ``None``, dtype ``float``).
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
TypeError: If ``neg_logposterior`` is not scalar-valued.
|
|
229
|
+
ValueError: If inputs are invalid or non-finite values are encountered.
|
|
230
|
+
np.linalg.LinAlgError: If ``ensure_spd=True`` and the Hessian cannot be
|
|
231
|
+
regularized to be SPD.
|
|
232
|
+
"""
|
|
233
|
+
theta_map_array = validate_theta_1d_finite(theta_map, name="theta_map")
|
|
234
|
+
|
|
235
|
+
negative_logposterior_at_map = float(neg_logposterior(theta_map_array))
|
|
236
|
+
if not np.isfinite(negative_logposterior_at_map):
|
|
237
|
+
raise ValueError("Negative log-posterior evaluated to a non-finite value at theta_map.")
|
|
238
|
+
|
|
239
|
+
hessian_matrix = build_laplace_hessian(
|
|
240
|
+
neg_logposterior=neg_logposterior,
|
|
241
|
+
theta_map=theta_map_array,
|
|
242
|
+
method=method,
|
|
243
|
+
n_workers=n_workers,
|
|
244
|
+
dk_kwargs=dk_kwargs,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
diagonal_jitter = 0.0
|
|
248
|
+
if ensure_spd:
|
|
249
|
+
hessian_matrix, diagonal_jitter = make_spd_by_jitter(hessian_matrix)
|
|
250
|
+
|
|
251
|
+
covariance_matrix = build_laplace_covariance(hessian_matrix, rcond=rcond)
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
"theta_map": theta_map_array,
|
|
255
|
+
"neg_logposterior_at_map": float(negative_logposterior_at_map),
|
|
256
|
+
"hessian": hessian_matrix,
|
|
257
|
+
"cov": covariance_matrix,
|
|
258
|
+
"jitter": float(diagonal_jitter),
|
|
259
|
+
}
|