cuthbertlib 0.0.13__tar.gz → 0.0.15__tar.gz

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 (63) hide show
  1. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/PKG-INFO +2 -2
  2. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/__init__.py +1 -1
  3. cuthbertlib-0.0.15/cuthbertlib/ensemble_kalman/README.md +42 -0
  4. cuthbertlib-0.0.15/cuthbertlib/ensemble_kalman/__init__.py +15 -0
  5. {cuthbertlib-0.0.13/cuthbertlib/enkf → cuthbertlib-0.0.15/cuthbertlib/ensemble_kalman}/filtering.py +61 -12
  6. cuthbertlib-0.0.15/cuthbertlib/ensemble_kalman/localization.py +89 -0
  7. cuthbertlib-0.0.15/cuthbertlib/ensemble_kalman/smoothing.py +36 -0
  8. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linearize/moments.py +2 -2
  9. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/protocols.py +3 -0
  10. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/pyproject.toml +1 -1
  11. cuthbertlib-0.0.13/cuthbertlib/enkf/README.md +0 -14
  12. cuthbertlib-0.0.13/cuthbertlib/enkf/__init__.py +0 -1
  13. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/.gitignore +0 -0
  14. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/README.md +0 -0
  15. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/README.md +0 -0
  16. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/discrete/__init__.py +0 -0
  17. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/discrete/filtering.py +0 -0
  18. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/discrete/smoothing.py +0 -0
  19. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/kalman/README.md +0 -0
  20. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/kalman/__init__.py +0 -0
  21. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/kalman/filtering.py +0 -0
  22. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/kalman/generate.py +0 -0
  23. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/kalman/sampling.py +0 -0
  24. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/kalman/smoothing.py +0 -0
  25. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linalg/README.md +0 -0
  26. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linalg/__init__.py +0 -0
  27. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linalg/collect_nans_chol.py +0 -0
  28. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linalg/marginal_sqrt_cov.py +0 -0
  29. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linalg/symmetric_inv_sqrt.py +0 -0
  30. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linalg/tria.py +0 -0
  31. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linearize/README.md +0 -0
  32. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linearize/__init__.py +0 -0
  33. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linearize/log_density.py +0 -0
  34. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/linearize/taylor.py +0 -0
  35. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/quadrature/README.md +0 -0
  36. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/quadrature/__init__.py +0 -0
  37. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/quadrature/common.py +0 -0
  38. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/quadrature/cubature.py +0 -0
  39. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/quadrature/gauss_hermite.py +0 -0
  40. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/quadrature/linearize.py +0 -0
  41. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/quadrature/unscented.py +0 -0
  42. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/quadrature/utils.py +0 -0
  43. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/README.md +0 -0
  44. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/__init__.py +0 -0
  45. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/adaptive.py +0 -0
  46. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/autodiff.py +0 -0
  47. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/killing.py +0 -0
  48. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/multinomial.py +0 -0
  49. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/no_resampling.py +0 -0
  50. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/systematic.py +0 -0
  51. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/resampling/utils.py +0 -0
  52. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/smc/README.md +0 -0
  53. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/smc/__init__.py +0 -0
  54. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/smc/ess.py +0 -0
  55. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/smc/smoothing/__init__.py +0 -0
  56. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/smc/smoothing/exact_sampling.py +0 -0
  57. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/smc/smoothing/mcmc.py +0 -0
  58. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/smc/smoothing/protocols.py +0 -0
  59. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/smc/smoothing/tracing.py +0 -0
  60. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/stats/README.md +0 -0
  61. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/stats/__init__.py +0 -0
  62. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/stats/multivariate_normal.py +0 -0
  63. {cuthbertlib-0.0.13 → cuthbertlib-0.0.15}/cuthbertlib/types.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: cuthbertlib
3
- Version: 0.0.13
3
+ Version: 0.0.15
4
4
  Summary: Atomic building blocks for state-space model inference with JAX
5
5
  Author-email: Sam Duffield <s@mduffield.com>, Sahel Iqbal <sahel13miqbal@proton.me>, Adrien Corenflos <adrien.corenflos.stats@gmail.com>
6
6
  License: Apache-2.0
@@ -5,7 +5,7 @@ del version
5
5
 
6
6
  from cuthbertlib import (
7
7
  discrete,
8
- enkf,
8
+ ensemble_kalman,
9
9
  kalman,
10
10
  linalg,
11
11
  linearize,
@@ -0,0 +1,42 @@
1
+ # Ensemble Kalman
2
+
3
+ The high-level interfaces built on these functions are in
4
+ [`cuthbert.ensemble_kalman.ensemble_kalman_filter`](../../cuthbert/ensemble_kalman/ensemble_kalman_filter.py)
5
+ and
6
+ [`cuthbert.ensemble_kalman.ensemble_rts_smoother`](../../cuthbert/ensemble_kalman/ensemble_rts_smoother.py).
7
+
8
+ <!-- --8<-- [start:overview] -->
9
+ This sub-repository provides modular functions for ensemble Kalman methods, including the ensemble Kalman filter (EnKF) and ensemble RTS smoother (EnRTS smoother).
10
+
11
+ The core functions are:
12
+
13
+ - `predict`: Propagate ensemble members through nonlinear dynamics with additive Gaussian noise.
14
+ - `filter_update`: Update ensemble members with an observation using the EnKF update equation.
15
+ - `smoother_update`: Apply one Ensemble Rauch-Tung-Striebel smoother update.
16
+ - `construct_tapered_chol_innovation_covariance`: Construct a tapered innovation covariance factor without forming the covariance densely.
17
+ - `gaspari_cohn`: Construct covariance tapers with the Gaspari-Cohn correlation function.
18
+ - `gaussian`: Construct smooth, non-compact covariance tapers with the Gaussian correlation function.
19
+ <!-- --8<-- [end:overview] -->
20
+
21
+ ## Ensemble Kalman filtering
22
+
23
+ <!-- --8<-- [start:filtering] -->
24
+ Together, `predict` and `filter_update` can be used to perform an online EnKF filtering step.
25
+
26
+ The EnKF uses an ensemble of particles with a Kalman-style measurement update based on empirical covariances. Unlike the EKF, it does not require Jacobians, while naturally handling nonlinear dynamics.
27
+ <!-- --8<-- [end:filtering] -->
28
+
29
+ ## Ensemble Rauch-Tung-Striebel smoothing
30
+
31
+ <!-- --8<-- [start:smoothing] -->
32
+ The EnRTS algorithm provides a smoothing counterpart to the EnKF, based on the RTS smoother. It makes an empirical approximation of the RTS smoother gain, which is applied to the ensemble in a backwards pass to obtain a smoothing distribution. Note that, based on the outputs of the EnKF, the EnRTS step is entirely deterministic.
33
+ <!-- --8<-- [end:smoothing] -->
34
+
35
+ ## Covariance localization
36
+
37
+ <!-- --8<-- [start:localization] -->
38
+ `filter_update` accepts two independent low-level hooks. `cross_covariance_modifier` receives one empirical state-observation cross-covariance and returns its modified value; it defaults to `no_covariance_modifier`. For observation-space covariance localization, one may optionally use `construct_chol_innovation_covariance`, which receives normalized observation deviations `Y` and a Cholesky factor of the observation noise covariance, `chol_R`, and returns a generalized Cholesky factor of the localized innovation covariance $C_{yy} + R$. All localization happens before handling of missing data, so localization happens in the original coordinates.
39
+
40
+ The most common form of localization is covariance tapering; cuthbertlib provides `gaspari_cohn` and `gaussian` correlation functions for this purpose. The Gaspari-Cohn correlation function is more classical, but has compact support which may cause difficulties in optimizing localization hyperparameters. The `gaussian` correlation function has infinite support, and may therefore have better gradient properties. For obsservation-space tapering, `cuthbertlib` provides a convenience `construct_tapered_chol_innovation_covariance`, which uses a Cholesky factor of the taper to construct a factor of the tapered innovation covariance. This comes at slightly higher cost, due to a larger QR solve.
41
+
42
+ <!-- --8<-- [end:localization] -->
@@ -0,0 +1,15 @@
1
+ from cuthbertlib.ensemble_kalman.filtering import (
2
+ ConstructCholInnovationCovariance,
3
+ CrossCovarianceModifier,
4
+ no_covariance_modifier,
5
+ predict,
6
+ )
7
+ from cuthbertlib.ensemble_kalman.filtering import (
8
+ update as filter_update,
9
+ )
10
+ from cuthbertlib.ensemble_kalman.localization import (
11
+ construct_tapered_chol_innovation_covariance,
12
+ gaspari_cohn,
13
+ gaussian,
14
+ )
15
+ from cuthbertlib.ensemble_kalman.smoothing import update as smoother_update
@@ -17,6 +17,23 @@ from cuthbertlib.types import Array, KeyArray, ScalarArray
17
17
 
18
18
  ObservationFn = Callable[[Array], Array]
19
19
  DynamicsFn = Callable[[Array, KeyArray], Array]
20
+ CrossCovarianceModifier = Callable[[Array], Array]
21
+ ConstructCholInnovationCovariance = Callable[[Array, Array], Array]
22
+
23
+
24
+ def no_covariance_modifier(covariance: Array) -> Array:
25
+ """Return an empirical covariance unchanged.
26
+
27
+ The identity covariance modifier, used as the default when no modification
28
+ (e.g. localization) is requested.
29
+
30
+ Args:
31
+ covariance: Empirical covariance matrix.
32
+
33
+ Returns:
34
+ The covariance matrix, unchanged.
35
+ """
36
+ return covariance
20
37
 
21
38
 
22
39
  def predict(
@@ -56,6 +73,9 @@ def update(
56
73
  chol_R: Array,
57
74
  y: Array,
58
75
  perturbed_obs: bool = True,
76
+ cross_covariance_modifier: CrossCovarianceModifier = no_covariance_modifier,
77
+ construct_chol_innovation_covariance: ConstructCholInnovationCovariance
78
+ | None = None,
59
79
  ) -> tuple[Array, ScalarArray]:
60
80
  """Update ensemble members with an observation using the EnKF update.
61
81
 
@@ -71,6 +91,16 @@ def update(
71
91
  y: Observation vector, shape (y_dim,). NaNs indicate missing dimensions.
72
92
  perturbed_obs: If True, use perturbed observations (stochastic EnKF).
73
93
  If False, use deterministic update.
94
+ cross_covariance_modifier: Function that modifies the empirical
95
+ state-observation cross-covariance, shape (x_dim, y_dim), and returns
96
+ an array with the same shape. Defaults to the identity.
97
+ construct_chol_innovation_covariance: Optional function that
98
+ receives normalized observation deviations with shape (y_dim, N) and
99
+ ``chol_R`` with shape (y_dim, y_dim). It must return a generalized
100
+ Cholesky factor of the complete innovation covariance with shape
101
+ (y_dim, y_dim). The deviations have already been divided by
102
+ ``sqrt(N - 1)``. Both inputs use the original observation order.
103
+ ``None`` uses the standard, unlocalized square-root construction.
74
104
 
75
105
  Returns:
76
106
  Tuple of (updated_ensemble, log_likelihood).
@@ -79,27 +109,46 @@ def update(
79
109
 
80
110
  # Map ensemble to observation space
81
111
  y_pred = jax.vmap(observation_fn, (0,))(predicted_ensemble)
112
+ x_mean = jnp.mean(predicted_ensemble, axis=0)
113
+ x_dev = predicted_ensemble - x_mean
114
+
115
+ missing = jnp.isnan(y)
116
+
117
+ # Modify or construct covariances before reordering due to NaNs.
118
+ argsort = jnp.argsort(missing, stable=True)
119
+ original_y_dev = y_pred - jnp.mean(y_pred, axis=0)
120
+ normalized_original_y_dev = original_y_dev.T / jnp.sqrt(N - 1)
121
+
122
+ C_xy = x_dev.T @ original_y_dev / (N - 1)
123
+ C_xy = cross_covariance_modifier(C_xy)
124
+
125
+ if construct_chol_innovation_covariance is not None:
126
+ original_chol_S = construct_chol_innovation_covariance(
127
+ normalized_original_y_dev, chol_R
128
+ )
82
129
 
83
130
  # Handle partially-missing observations by reordering and zeroing missing dims.
84
131
  # Use y_pred.T because y_pred is (N, y_dim) and we want to reorder along axis 0.
85
- flag = jnp.isnan(y)
86
- flag, chol_R, y, y_pred = collect_nans_chol(flag, chol_R, y, y_pred.T)
132
+ flag, chol_R, y, y_pred = collect_nans_chol(missing, chol_R, y, y_pred.T)
87
133
  y_pred = y_pred.T
88
134
  y_dim = y.shape[0]
89
135
 
90
- # Ensemble means
91
- x_mean = jnp.mean(predicted_ensemble, axis=0)
92
136
  y_mean = jnp.mean(y_pred, axis=0)
93
-
94
- # Deviations from ensemble mean
95
- x_dev = predicted_ensemble - x_mean
96
137
  y_dev = y_pred - y_mean
138
+ C_xy = C_xy[:, argsort]
139
+ C_xy = jnp.where(flag[None, :], 0.0, C_xy)
97
140
 
98
- # Square-root innovation covariance via tria
99
- chol_S = tria(jnp.concatenate([y_dev.T / jnp.sqrt(N - 1), chol_R], axis=1))
100
-
101
- # Cross-covariance
102
- C_xy = x_dev.T @ y_dev / (N - 1)
141
+ if construct_chol_innovation_covariance is None:
142
+ chol_S = tria(jnp.concatenate([y_dev.T / jnp.sqrt(N - 1), chol_R], axis=1))
143
+ else:
144
+ # The constructor sees the original indexing. Only collect and refactor its
145
+ # result when dimensions are missing; otherwise preserve its returned factor.
146
+ chol_S = jax.lax.cond(
147
+ jnp.any(missing),
148
+ lambda chol: collect_nans_chol(missing, chol)[1],
149
+ lambda chol: chol,
150
+ original_chol_S,
151
+ )
103
152
 
104
153
  # Kalman gain: K = C_xy @ S^{-1} = C_xy @ cho_solve(chol_S, I)
105
154
  K = cho_solve((chol_S, True), C_xy.T).T
@@ -0,0 +1,89 @@
1
+ """Covariance localization utilities for ensemble Kalman methods."""
2
+
3
+ import jax.numpy as jnp
4
+
5
+ from cuthbertlib.linalg import tria
6
+ from cuthbertlib.types import Array, ArrayLike, ScalarArrayLike
7
+
8
+
9
+ def construct_tapered_chol_innovation_covariance(
10
+ Y: Array,
11
+ chol_taper: Array,
12
+ chol_R: Array,
13
+ ) -> Array:
14
+ """Construct a tapered innovation covariance factor without forming it densely.
15
+
16
+ If ``taper = chol_taper @ chol_taper.T`` and ``Y`` denotes the normalized
17
+ observation deviations, the returned generalized Cholesky factor ``chol_S``
18
+ satisfies ``chol_S @ chol_S.T = taper * (Y @ Y.T) + R``.
19
+
20
+ Args:
21
+ Y: Observation deviations transposed and divided by the square root of one
22
+ less than the ensemble size, shape (y_dim, n_particles).
23
+ chol_taper: Factor of a positive-semidefinite observation-space taper,
24
+ shape (y_dim, y_dim).
25
+ chol_R: Cholesky factor of the observation noise covariance, shape
26
+ (y_dim, y_dim).
27
+
28
+ Returns:
29
+ Generalized Cholesky factor of the complete tapered innovation covariance,
30
+ shape (y_dim, y_dim).
31
+ """
32
+ y_dim = Y.shape[0]
33
+ Y_tilde = (chol_taper[:, :, None] * Y[:, None, :]).reshape(y_dim, -1)
34
+ return tria(jnp.concatenate([Y_tilde, chol_R], axis=1))
35
+
36
+
37
+ def gaussian(
38
+ distances: ArrayLike,
39
+ length_scale: ScalarArrayLike,
40
+ ) -> Array:
41
+ r"""Evaluates a Gaussian covariance taper.
42
+
43
+ The taper is the squared-exponential correlation function
44
+ $\rho(d; \ell) = \exp(-\frac{1}{2}(d / \ell)^2)$. This taper
45
+ has infinite support and is differentiable with respect
46
+ to the length scale.
47
+
48
+ Args:
49
+ distances: Distances at which to evaluate the taper.
50
+ length_scale: Positive characteristic distance of the taper.
51
+
52
+ Returns:
53
+ Taper values with the broadcast shape of the inputs.
54
+ """
55
+ scaled_distances = jnp.asarray(distances) / length_scale
56
+ return jnp.exp(-0.5 * jnp.square(scaled_distances))
57
+
58
+
59
+ def gaspari_cohn(
60
+ distances: ArrayLike,
61
+ support_radius: ScalarArrayLike,
62
+ ) -> Array:
63
+ """Evaluates the compactly supported fifth-order Gaspari-Cohn taper.
64
+
65
+ This implements Eq. (4.10) of Gaspari and Cohn (1999),
66
+ https://doi.org/10.1002/qj.49712555417.
67
+
68
+ ``support_radius`` is the full support radius: taper values are exactly zero
69
+ where the absolute distance is greater than or equal to it.
70
+
71
+ Args:
72
+ distances: Distances at which to evaluate the taper.
73
+ support_radius: Positive distance at which the taper reaches zero.
74
+
75
+ Returns:
76
+ Taper values with the broadcast shape of the inputs.
77
+ """
78
+ distances = jnp.abs(distances)
79
+ q = 2 * distances / support_radius
80
+
81
+ inner = 1 - 5 / 3 * q**2 + 5 / 8 * q**3 + 1 / 2 * q**4 - 1 / 4 * q**5
82
+
83
+ # Avoid NaNs in computing the outer branch of the polynomial
84
+ safe_q = jnp.where(q > 0, q, 1)
85
+ # Factored version of Eq. (4.10) for numerical stability
86
+ outer = (2 - q) ** 4 * (2 * q**2 + 4 * q - 1) / (24 * safe_q)
87
+
88
+ within_support = jnp.where(q <= 1, inner, outer)
89
+ return jnp.where(distances < support_radius, within_support, 0)
@@ -0,0 +1,36 @@
1
+ """Implements the Ensemble Rauch-Tung-Striebel (EnRTS) smoother update.
2
+
3
+ Cf. [Raanes (2016)](https://doi.org/10.1002/qj.2728).
4
+ """
5
+
6
+ import jax.numpy as jnp
7
+
8
+ from cuthbertlib.types import Array
9
+
10
+
11
+ def update(
12
+ filtered_ensemble: Array,
13
+ predicted_ensemble: Array,
14
+ next_smoothed_ensemble: Array,
15
+ ) -> tuple[Array, Array]:
16
+ """Applies one EnRTS smoother update step.
17
+
18
+ Args:
19
+ filtered_ensemble: Filtered ensemble at time t, shape (N, x_dim).
20
+ predicted_ensemble: Paired forecast ensemble at time t + 1,
21
+ shape (N, next_x_dim).
22
+ next_smoothed_ensemble: Smoothed ensemble at time t + 1,
23
+ shape (N, next_x_dim).
24
+
25
+ Returns:
26
+ Tuple of the smoothed ensemble at time t and the EnRTS gain.
27
+ """
28
+ filtered_dev = filtered_ensemble - jnp.mean(filtered_ensemble, axis=0)
29
+ predicted_dev = predicted_ensemble - jnp.mean(predicted_ensemble, axis=0)
30
+
31
+ gain = filtered_dev.T @ jnp.linalg.pinv(predicted_dev.T)
32
+
33
+ smoothed_ensemble = (
34
+ filtered_ensemble + (next_smoothed_ensemble - predicted_ensemble) @ gain.T
35
+ )
36
+ return smoothed_ensemble, gain
@@ -63,14 +63,14 @@ def linearize_moments(
63
63
  - [sqrt-parallel-smoothers](https://github.com/EEA-sensors/sqrt-parallel-smoothers/blob/main/parsmooth/linearization/_extended.py)
64
64
  """
65
65
  if has_aux:
66
- mean_and_chol_cov_function = cast(
66
+ mean_and_chol_cov_function_with_aux = cast(
67
67
  MeanAndCholCovFuncAux, mean_and_chol_cov_function
68
68
  )
69
69
 
70
70
  def mean_and_chol_cov_function_wrapper_aux(
71
71
  x: ArrayLike,
72
72
  ) -> tuple[Array, tuple[Array, Array, ArrayTree]]:
73
- mean, chol_cov, aux = mean_and_chol_cov_function(x)
73
+ mean, chol_cov, aux = mean_and_chol_cov_function_with_aux(x)
74
74
  return mean, (mean, chol_cov, aux)
75
75
 
76
76
  F, (m, *extra) = jax.jacfwd(
@@ -1,3 +1,6 @@
1
+ # ty: ignore[invalid-return-type]
2
+ # f-strings are not supported docstrings and ty is confused about this
3
+ # See https://github.com/state-space-models/cuthbert/issues/261
1
4
  """Shared protocols for resampling algorithms."""
2
5
 
3
6
  from typing import Protocol, runtime_checkable
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cuthbertlib"
7
- version = "0.0.13"
7
+ version = "0.0.15"
8
8
  description = "Atomic building blocks for state-space model inference with JAX"
9
9
  requires-python = ">=3.10"
10
10
  readme = "README.md"
@@ -1,14 +0,0 @@
1
- # Ensemble Kalman Filter (EnKF)
2
-
3
- This sub-repository provides modular functions for the Ensemble Kalman Filter.
4
-
5
- The core functions are:
6
-
7
- - `predict`: Propagate ensemble members through nonlinear dynamics with additive Gaussian noise.
8
- - `update`: Update ensemble members with an observation using the EnKF update equation.
9
-
10
- Together, `predict` and `update` can be used to perform an online EnKF filtering step.
11
-
12
- The EnKF uses an ensemble of particles with a Kalman-style measurement update based on
13
- empirical covariances. Unlike the EKF, it does not require Jacobians, while naturally
14
- handling nonlinear dynamics.
@@ -1 +0,0 @@
1
- from cuthbertlib.enkf.filtering import predict, update
File without changes
File without changes