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.
Files changed (68) hide show
  1. derivkit/__init__.py +22 -0
  2. derivkit/calculus/__init__.py +17 -0
  3. derivkit/calculus/calculus_core.py +152 -0
  4. derivkit/calculus/gradient.py +97 -0
  5. derivkit/calculus/hessian.py +528 -0
  6. derivkit/calculus/hyper_hessian.py +296 -0
  7. derivkit/calculus/jacobian.py +156 -0
  8. derivkit/calculus_kit.py +128 -0
  9. derivkit/derivative_kit.py +315 -0
  10. derivkit/derivatives/__init__.py +6 -0
  11. derivkit/derivatives/adaptive/__init__.py +5 -0
  12. derivkit/derivatives/adaptive/adaptive_fit.py +238 -0
  13. derivkit/derivatives/adaptive/batch_eval.py +179 -0
  14. derivkit/derivatives/adaptive/diagnostics.py +325 -0
  15. derivkit/derivatives/adaptive/grid.py +333 -0
  16. derivkit/derivatives/adaptive/polyfit_utils.py +513 -0
  17. derivkit/derivatives/adaptive/spacing.py +66 -0
  18. derivkit/derivatives/adaptive/transforms.py +245 -0
  19. derivkit/derivatives/autodiff/__init__.py +1 -0
  20. derivkit/derivatives/autodiff/jax_autodiff.py +95 -0
  21. derivkit/derivatives/autodiff/jax_core.py +217 -0
  22. derivkit/derivatives/autodiff/jax_utils.py +146 -0
  23. derivkit/derivatives/finite/__init__.py +5 -0
  24. derivkit/derivatives/finite/batch_eval.py +91 -0
  25. derivkit/derivatives/finite/core.py +84 -0
  26. derivkit/derivatives/finite/extrapolators.py +511 -0
  27. derivkit/derivatives/finite/finite_difference.py +247 -0
  28. derivkit/derivatives/finite/stencil.py +206 -0
  29. derivkit/derivatives/fornberg.py +245 -0
  30. derivkit/derivatives/local_polynomial_derivative/__init__.py +1 -0
  31. derivkit/derivatives/local_polynomial_derivative/diagnostics.py +90 -0
  32. derivkit/derivatives/local_polynomial_derivative/fit.py +199 -0
  33. derivkit/derivatives/local_polynomial_derivative/local_poly_config.py +95 -0
  34. derivkit/derivatives/local_polynomial_derivative/local_polynomial_derivative.py +205 -0
  35. derivkit/derivatives/local_polynomial_derivative/sampling.py +72 -0
  36. derivkit/derivatives/tabulated_model/__init__.py +1 -0
  37. derivkit/derivatives/tabulated_model/one_d.py +247 -0
  38. derivkit/forecast_kit.py +783 -0
  39. derivkit/forecasting/__init__.py +1 -0
  40. derivkit/forecasting/dali.py +78 -0
  41. derivkit/forecasting/expansions.py +486 -0
  42. derivkit/forecasting/fisher.py +298 -0
  43. derivkit/forecasting/fisher_gaussian.py +171 -0
  44. derivkit/forecasting/fisher_xy.py +357 -0
  45. derivkit/forecasting/forecast_core.py +313 -0
  46. derivkit/forecasting/getdist_dali_samples.py +429 -0
  47. derivkit/forecasting/getdist_fisher_samples.py +235 -0
  48. derivkit/forecasting/laplace.py +259 -0
  49. derivkit/forecasting/priors_core.py +860 -0
  50. derivkit/forecasting/sampling_utils.py +388 -0
  51. derivkit/likelihood_kit.py +114 -0
  52. derivkit/likelihoods/__init__.py +1 -0
  53. derivkit/likelihoods/gaussian.py +136 -0
  54. derivkit/likelihoods/poisson.py +176 -0
  55. derivkit/utils/__init__.py +13 -0
  56. derivkit/utils/concurrency.py +213 -0
  57. derivkit/utils/extrapolation.py +254 -0
  58. derivkit/utils/linalg.py +513 -0
  59. derivkit/utils/logger.py +26 -0
  60. derivkit/utils/numerics.py +262 -0
  61. derivkit/utils/sandbox.py +74 -0
  62. derivkit/utils/types.py +15 -0
  63. derivkit/utils/validate.py +811 -0
  64. derivkit-1.0.0.dist-info/METADATA +50 -0
  65. derivkit-1.0.0.dist-info/RECORD +68 -0
  66. derivkit-1.0.0.dist-info/WHEEL +5 -0
  67. derivkit-1.0.0.dist-info/licenses/LICENSE +21 -0
  68. 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
+ }