kkf 0.3.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.
KKF/__init__.py ADDED
@@ -0,0 +1,48 @@
1
+ """Kernel Koopman Kalman Filter (KKF) - A library for nonlinear state estimation.
2
+
3
+ This package provides a complete implementation of the Koopman-Kalman Filter,
4
+ combining Koopman operator theory with Kalman filtering for accurate state
5
+ estimation in nonlinear dynamical systems.
6
+
7
+ Main Components
8
+ ---------------
9
+ - systems : Dynamical system definitions
10
+ - koopman : Koopman operator approximation via kEDMD
11
+ - covariances : Covariance matrix computations
12
+ - solution : Filter solution data structure
13
+ - filter : Main filtering algorithm
14
+
15
+ Quick Start
16
+ -----------
17
+ >>> from KKF import DynamicalSystem, KoopmanOperator, apply_koopman_kalman_filter
18
+ >>> # or
19
+ >>> from KKF.filter import apply_koopman_kalman_filter
20
+ """
21
+
22
+ # Core classes and utilities
23
+ from .covariances import (
24
+ compute_dynamics_covariance,
25
+ compute_initial_covariance,
26
+ compute_observation_covariance,
27
+ )
28
+
29
+ # Main filtering function
30
+ from .filter import apply_koopman_kalman_filter
31
+ from .koopman import KoopmanOperator
32
+ from .solution import KoopmanKalmanFilterSolution
33
+ from .systems import DynamicalSystem, create_additive_system
34
+
35
+ __all__ = [
36
+ # Classes
37
+ "DynamicalSystem",
38
+ "KoopmanOperator",
39
+ "KoopmanKalmanFilterSolution",
40
+ # Functions
41
+ "create_additive_system",
42
+ "apply_koopman_kalman_filter",
43
+ "compute_initial_covariance",
44
+ "compute_dynamics_covariance",
45
+ "compute_observation_covariance",
46
+ ]
47
+
48
+ __version__ = "0.3.0"
KKF/covariances.py ADDED
@@ -0,0 +1,159 @@
1
+ from typing import Any, Callable, Union
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+ from scipy.stats import rv_continuous
6
+
7
+
8
+ def compute_initial_covariance(
9
+ x: NDArray[np.float64],
10
+ n_features: int,
11
+ initial_distribution: rv_continuous,
12
+ koopman_operator: Any,
13
+ n_samples: int,
14
+ ) -> NDArray[np.float64]:
15
+ """
16
+ Compute the covariance matrix for the initial distribution in feature space.
17
+
18
+ This function samples from an initial distribution and computes the covariance
19
+ matrix of the transformed samples using a Koopman operator's feature map.
20
+
21
+ Parameters
22
+ ----------
23
+ x : np.ndarray
24
+ Current state vector or reference point.
25
+ n_features : int
26
+ Number of features in the transformed space.
27
+ initial_distribution : scipy.stats.rv_continuous
28
+ Initial probability distribution to sample from.
29
+ koopman_operator : object
30
+ Object containing the feature map phi method for state transformation.
31
+ Must have a method phi(x) that maps states to feature space.
32
+ n_samples : int
33
+ Number of samples to use for covariance estimation.
34
+
35
+ Returns
36
+ -------
37
+ np.ndarray
38
+ Covariance matrix of size (n_features, n_features) in the transformed space.
39
+
40
+ Notes
41
+ -----
42
+ The function performs the following steps:
43
+ 1. Samples from the initial distribution
44
+ 2. Applies the Koopman operator's feature map to each sample
45
+ 3. Computes the covariance matrix of the transformed samples
46
+ """
47
+ # Sample from initial distribution
48
+ samples = initial_distribution.rvs(size=n_samples).reshape((n_samples, len(x)))
49
+
50
+ # Initialize array for transformed samples
51
+ transformed_samples = np.zeros((n_samples, n_features))
52
+
53
+ # Transform each sample using the feature map
54
+ for i in range(n_samples):
55
+ transformed_samples[i, :] = koopman_operator.phi(samples[i, :])
56
+
57
+ # Compute and return covariance matrix
58
+ return np.cov(transformed_samples, rowvar=False)
59
+
60
+
61
+ def compute_dynamics_covariance(
62
+ x: NDArray[np.float64], n_features: int, dynamics: Any, koopman_operator: Any, n_samples: int
63
+ ) -> NDArray[np.float64]:
64
+ """
65
+ Compute the covariance matrix for the system dynamics in feature space.
66
+
67
+ This function samples from the dynamics distribution, applies the system
68
+ dynamics, and computes the covariance matrix of the transformed results.
69
+
70
+ Parameters
71
+ ----------
72
+ x : np.ndarray
73
+ Current state vector or reference point.
74
+ n_features : int
75
+ Number of features in the transformed space.
76
+ dynamics : object
77
+ Dynamical system object containing:
78
+ - dist_dyn: scipy.stats.rv_continuous distribution for dynamics noise
79
+ - dynamics(x, w): method implementing the system dynamics
80
+ koopman_operator : object
81
+ Object containing the feature map phi method for state transformation.
82
+ Must have a method phi(x) that maps states to feature space.
83
+ n_samples : int
84
+ Number of samples to use for covariance estimation.
85
+
86
+ Returns
87
+ -------
88
+ np.ndarray
89
+ Covariance matrix of size (n_features, n_features) in the transformed space.
90
+
91
+ Notes
92
+ -----
93
+ The function performs the following steps:
94
+ 1. Samples from the dynamics noise distribution
95
+ 2. Applies the system dynamics to each sample
96
+ 3. Transforms the results using the Koopman operator's feature map
97
+ 4. Computes the covariance matrix of the transformed samples
98
+ """
99
+ # Sample from dynamics distribution
100
+ noise_samples = dynamics.dist_dyn.rvs(size=n_samples).reshape((n_samples, len(x)))
101
+
102
+ # Initialize array for transformed samples
103
+ transformed_samples = np.zeros((n_samples, n_features))
104
+
105
+ # Apply dynamics and transform each sample
106
+ for i in range(n_samples):
107
+ state_evolution = dynamics.dynamics(x, noise_samples[i, :])
108
+ transformed_samples[i, :] = koopman_operator.phi(state_evolution)
109
+
110
+ # Compute and return covariance matrix
111
+ return np.cov(transformed_samples, rowvar=False)
112
+
113
+
114
+ def compute_observation_covariance(
115
+ x: NDArray[np.float64], n_outputs: int, dynamics: Any, n_samples: int
116
+ ) -> NDArray[np.float64]:
117
+ """
118
+ Compute the covariance matrix for the observation/measurement process.
119
+
120
+ This function samples from the measurement noise distribution and computes
121
+ the covariance matrix of the measurement process.
122
+
123
+ Parameters
124
+ ----------
125
+ x : np.ndarray
126
+ Current state vector or reference point.
127
+ n_outputs : int
128
+ Number of measurement outputs.
129
+ dynamics : object
130
+ Dynamical system object containing:
131
+ - dist_obs: scipy.stats.rv_continuous distribution for measurement noise
132
+ - measurements(x, w): method implementing the measurement process
133
+ n_samples : int
134
+ Number of samples to use for covariance estimation.
135
+
136
+ Returns
137
+ -------
138
+ np.ndarray
139
+ Covariance matrix of size (n_outputs, n_outputs) for the measurement process.
140
+
141
+ Notes
142
+ -----
143
+ The function performs the following steps:
144
+ 1. Samples from the measurement noise distribution
145
+ 2. Applies the measurement function to each sample
146
+ 3. Computes the covariance matrix of the measurements
147
+ """
148
+ # Sample from measurement distribution
149
+ noise_samples = dynamics.dist_obs.rvs(size=n_samples).reshape((n_samples, n_outputs))
150
+
151
+ # Initialize array for measurements
152
+ measurement_samples = np.zeros((n_samples, n_outputs))
153
+
154
+ # Apply measurement function to each sample
155
+ for i in range(n_samples):
156
+ measurement_samples[i, :] = dynamics.measurements(x, noise_samples[i, :])
157
+
158
+ # Compute and return covariance matrix (ensure 2D even for single output)
159
+ return np.atleast_2d(np.cov(measurement_samples, rowvar=False))
KKF/filter.py ADDED
@@ -0,0 +1,250 @@
1
+ """Koopman Kalman Filter algorithm implementation.
2
+
3
+ Combines Koopman operator theory with Kalman filtering for nonlinear
4
+ state estimation.
5
+ """
6
+
7
+ from typing import Any, Callable, Tuple
8
+
9
+ import numpy as np
10
+ from numpy.linalg import pinv
11
+ from numpy.typing import NDArray
12
+
13
+ from .covariances import (
14
+ compute_dynamics_covariance,
15
+ compute_initial_covariance,
16
+ compute_observation_covariance,
17
+ )
18
+ from .solution import KoopmanKalmanFilterSolution
19
+
20
+
21
+ def apply_koopman_kalman_filter(
22
+ koopman_operator: Any,
23
+ observations: NDArray[np.float64],
24
+ initial_distribution: Any,
25
+ n_features: int,
26
+ optimize: bool = True,
27
+ n_restarts_optimizer: int = 10,
28
+ noise_samples: int = 100,
29
+ ) -> KoopmanKalmanFilterSolution:
30
+ """
31
+ Implementation of the Koopman-Kalman Filter algorithm.
32
+
33
+ This function combines Koopman operator theory with Kalman filtering to perform
34
+ state estimation for nonlinear dynamical systems. It operates by lifting the
35
+ state space to a higher-dimensional feature space where the dynamics are
36
+ approximately linear.
37
+
38
+ Parameters
39
+ ----------
40
+ koopman_operator : object
41
+ Object containing Koopman operator methods and attributes:
42
+ - compute_edmd(n_features): Extended Dynamic Mode Decomposition method
43
+ - dynamical_system: Dynamical system object
44
+ - U, B, C: Matrices for Koopman approximation
45
+ - phi: Feature map function
46
+ observations : np.ndarray
47
+ Array of shape (n_timesteps, n_outputs) containing system measurements.
48
+ initial_distribution : object
49
+ Distribution object for initial state with methods:
50
+ - mean: Returns mean of initial state
51
+ - rvs: Random sampling method
52
+ n_features : int
53
+ Number of features in the lifted space.
54
+ optimize : bool, optional
55
+ Whether to optimize the kernel parameters. Default is True.
56
+ n_restarts_optimizer : int, optional
57
+ Number of restarts for kernel optimization. Default is 10.
58
+ noise_samples : int, optional
59
+ Number of samples for noise covariance estimation. Default is 100.
60
+
61
+ Returns
62
+ -------
63
+ KoopmanKalmanFilterSolution
64
+ Object containing all filter estimates and covariances.
65
+
66
+ Notes
67
+ -----
68
+ The algorithm consists of three main phases:
69
+ 1. Initialization:
70
+ - Sets up initial states and covariances
71
+ - Computes Koopman approximation
72
+
73
+ 2. Prediction Step:
74
+ - Projects state forward using system dynamics
75
+ - Updates covariances using Koopman operator
76
+
77
+ 3. Update Step:
78
+ - Incorporates new measurements
79
+ - Updates state and covariance estimates
80
+
81
+ The filter operates in both the original state space (x) and the lifted
82
+ feature space (z), maintaining estimates and covariances in both spaces.
83
+ """
84
+ # Compute Koopman approximation
85
+ koopman_operator.compute_edmd(n_features, optimize, n_restarts_optimizer)
86
+ dynamical_system = koopman_operator.dynamical_system
87
+
88
+ # Extract system and Koopman components
89
+ state_dynamics = dynamical_system.f
90
+ measurement_model = dynamical_system.g
91
+ U, B, C = koopman_operator.U, koopman_operator.B, koopman_operator.C
92
+ phi = koopman_operator.phi
93
+
94
+ ### Initialization ###
95
+ mean_val = initial_distribution.mean # Initial state estimate
96
+ x0 = np.atleast_1d(np.asarray(mean_val() if callable(mean_val) else mean_val))
97
+ z0 = phi(x0) # Initial state in feature space
98
+
99
+ # Get problem dimensions
100
+ n_timesteps, n_states = observations.shape[0], len(x0)
101
+ n_outputs = observations.shape[1]
102
+
103
+ # Initialize state arrays
104
+ x_minus, x_plus = _initialize_state_arrays(n_timesteps, n_states)
105
+ z_minus, z_plus = _initialize_state_arrays(n_timesteps, n_features)
106
+
107
+ # Initialize covariance arrays
108
+ Px_minus, Px_plus = _initialize_covariance_arrays(n_timesteps, n_states)
109
+ Pz_minus, Pz_plus = _initialize_covariance_arrays(n_timesteps, n_features)
110
+
111
+ # Initialize filter matrices
112
+ S = np.zeros((n_timesteps, n_outputs, n_outputs)) # Innovation covariance
113
+ K = np.zeros((n_timesteps, n_features, n_outputs)) # Kalman gain
114
+
115
+ # Set initial conditions
116
+ x_minus[0, :], x_plus[0, :] = x0, x0
117
+ z_minus[0, :], z_plus[0, :] = z0, z0
118
+
119
+ # Initialize covariances
120
+ initial_covariance = compute_initial_covariance(
121
+ x_minus[0, :], n_features, initial_distribution, koopman_operator, noise_samples
122
+ )
123
+ Pz_minus[0, :, :], Pz_plus[0, :, :] = initial_covariance, initial_covariance
124
+
125
+ ### Main Filter Loop ###
126
+ for t in range(1, n_timesteps):
127
+ # Prediction Step
128
+ x_minus[t], z_minus[t], Pz_minus[t] = _prediction_step(
129
+ x_plus[t - 1],
130
+ z_plus[t - 1],
131
+ Pz_plus[t - 1],
132
+ state_dynamics,
133
+ phi,
134
+ U,
135
+ dynamical_system,
136
+ koopman_operator,
137
+ n_features,
138
+ noise_samples,
139
+ )
140
+
141
+ # Update Step
142
+ x_plus[t], z_plus[t], Pz_plus[t], S[t], K[t] = _update_step(
143
+ x_minus[t],
144
+ z_minus[t],
145
+ Pz_minus[t],
146
+ observations[t],
147
+ measurement_model,
148
+ C,
149
+ B,
150
+ dynamical_system,
151
+ noise_samples,
152
+ )
153
+
154
+ # Update state-space covariances
155
+ Px_minus[t], Px_plus[t] = _update_state_covariances(Pz_minus[t], Pz_plus[t], B)
156
+
157
+ return KoopmanKalmanFilterSolution(x_plus, x_minus, Pz_plus, Pz_minus, Px_plus, Px_minus, S, K)
158
+
159
+
160
+ def _initialize_state_arrays(
161
+ n_timesteps: int, n_dim: int
162
+ ) -> Tuple[NDArray[np.float64], NDArray[np.float64]]:
163
+ """Initialize arrays for state estimates."""
164
+ return (np.zeros((n_timesteps, n_dim)), np.zeros((n_timesteps, n_dim)))
165
+
166
+
167
+ def _initialize_covariance_arrays(
168
+ n_timesteps: int, n_dim: int
169
+ ) -> Tuple[NDArray[np.float64], NDArray[np.float64]]:
170
+ """Initialize arrays for covariance matrices."""
171
+ return (np.zeros((n_timesteps, n_dim, n_dim)), np.zeros((n_timesteps, n_dim, n_dim)))
172
+
173
+
174
+ def _prediction_step(
175
+ x_prev: NDArray[np.float64],
176
+ z_prev: NDArray[np.float64],
177
+ Pz_prev: NDArray[np.float64],
178
+ state_dynamics: Callable,
179
+ phi: Callable,
180
+ U: NDArray[np.float64],
181
+ dynamical_system: Any,
182
+ koopman_operator: Any,
183
+ n_features: int,
184
+ noise_samples: int,
185
+ ) -> Tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
186
+ """Perform the prediction step of the filter."""
187
+ # Compute dynamics covariance
188
+ Qz = compute_dynamics_covariance(
189
+ x_prev, n_features, dynamical_system, koopman_operator, noise_samples
190
+ )
191
+
192
+ # Predict states
193
+ x_pred = state_dynamics(
194
+ x_prev,
195
+ )
196
+ z_pred = phi(x_pred)
197
+
198
+ # Predict covariance
199
+ Pz_pred = U @ Pz_prev @ U.T + Qz
200
+
201
+ return x_pred, z_pred, Pz_pred
202
+
203
+
204
+ def _update_step(
205
+ x_minus: NDArray[np.float64],
206
+ z_minus: NDArray[np.float64],
207
+ Pz_minus: NDArray[np.float64],
208
+ observation: NDArray[np.float64],
209
+ measurement_model: Callable,
210
+ C: NDArray[np.float64],
211
+ B: NDArray[np.float64],
212
+ dynamical_system: Any,
213
+ noise_samples: int,
214
+ ) -> Tuple[
215
+ NDArray[np.float64],
216
+ NDArray[np.float64],
217
+ NDArray[np.float64],
218
+ NDArray[np.float64],
219
+ NDArray[np.float64],
220
+ ]:
221
+ """Perform the update step of the filter."""
222
+ # Compute measurement covariance
223
+ Rz = compute_observation_covariance(x_minus, len(observation), dynamical_system, noise_samples)
224
+
225
+ # Compute innovation
226
+ innovation = observation - measurement_model(x_minus)
227
+
228
+ # Compute innovation covariance
229
+ S = C @ Pz_minus @ C.T + Rz
230
+
231
+ # Compute Kalman gain
232
+ K = Pz_minus @ C.T @ pinv(S)
233
+
234
+ # Update states
235
+ z_plus = z_minus + K @ innovation
236
+ x_plus = B @ z_plus
237
+
238
+ # Update covariance
239
+ Pz_plus = (np.eye(len(z_minus)) - K @ C) @ Pz_minus
240
+
241
+ return x_plus, z_plus, Pz_plus, S, K
242
+
243
+
244
+ def _update_state_covariances(
245
+ Pz_minus: NDArray[np.float64], Pz_plus: NDArray[np.float64], B: NDArray[np.float64]
246
+ ) -> Tuple[NDArray[np.float64], NDArray[np.float64]]:
247
+ """Update covariances in the state space."""
248
+ Px_minus = B @ Pz_minus @ B.T
249
+ Px_plus = B @ Pz_plus @ B.T
250
+ return Px_minus, Px_plus
KKF/koopman.py ADDED
@@ -0,0 +1,155 @@
1
+ """Koopman operator approximation module.
2
+
3
+ Implements kernel-based Extended Dynamic Mode Decomposition (kEDMD)
4
+ for computing Koopman operator approximations.
5
+ """
6
+
7
+ from typing import Callable, Optional
8
+
9
+ import numpy as np
10
+ from numpy.typing import NDArray
11
+ from sklearn.gaussian_process import GaussianProcessRegressor
12
+ from sklearn.gaussian_process.kernels import Kernel
13
+
14
+ from .systems import DynamicalSystem, create_additive_system
15
+
16
+
17
+ class KoopmanOperator:
18
+ """
19
+ Implementation of Koopman operator approximation using kernel-based Extended
20
+ Dynamic Mode Decomposition (kEDMD).
21
+
22
+ This class provides methods to compute finite-dimensional approximations of the
23
+ Koopman operator for nonlinear dynamical systems.
24
+
25
+ Attributes
26
+ ----------
27
+ kernel_function : Callable
28
+ Kernel function for computing feature space mappings.
29
+ dynamical_system : DynamicalSystem
30
+ The underlying dynamical system.
31
+ X : Optional[np.ndarray]
32
+ Dictionary of states used for kernel computations.
33
+ phi : Optional[Callable]
34
+ Feature map function.
35
+ U : Optional[np.ndarray]
36
+ Koopman operator matrix.
37
+ G : Optional[np.ndarray]
38
+ Gram matrix.
39
+ C : Optional[np.ndarray]
40
+ Output matrix.
41
+ B : Optional[np.ndarray]
42
+ State-to-feature space transformation matrix.
43
+
44
+ Notes
45
+ -----
46
+ The Koopman operator framework lifts nonlinear dynamics to a linear setting
47
+ in a higher-dimensional feature space. This implementation uses kernel methods
48
+ to compute the necessary feature spaces and operators.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ kernel_function: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
54
+ dynamical_system: DynamicalSystem,
55
+ ) -> None:
56
+ self.kernel_function = kernel_function
57
+ self.dynamical_system = dynamical_system
58
+ self.X: Optional[NDArray[np.float64]] = None
59
+ self.phi: Optional[Callable] = None
60
+ self.U: Optional[NDArray[np.float64]] = None
61
+ self.G: Optional[NDArray[np.float64]] = None
62
+ self.C: Optional[NDArray[np.float64]] = None
63
+ self.B: Optional[NDArray[np.float64]] = None
64
+
65
+ def compute_edmd(
66
+ self, n_features: int, optimize: bool = True, n_restarts_optimizer: int = 10
67
+ ) -> None:
68
+ """
69
+ Compute the kernel-based Extended Dynamic Mode Decomposition (kEDMD).
70
+
71
+ This method constructs finite-dimensional approximations of the Koopman
72
+ operator and associated matrices using kernel methods.
73
+
74
+ Parameters
75
+ ----------
76
+ n_features : int
77
+ Number of features to use in the approximation.
78
+ optimize : bool
79
+ Whether to optimize the kernel function. If True, the method will
80
+ optimize the kernel function using Gaussian Process Regression. If False,
81
+ the provided kernel function will be used without optimization. Default is True.
82
+ n_restarts_optimizer : int
83
+ Number of restarts for the optimizer. If optimize is False, will be ignored. Default is 10.
84
+
85
+ Notes
86
+ -----
87
+ The method performs the following steps:
88
+ 1. Generates dictionary points using the state distribution
89
+ 2. Constructs the feature map using the kernel function
90
+ 3. Computes the Gram matrix and its inverse
91
+ 4. Constructs the Koopman operator approximation
92
+ 5. Computes output and state transformation matrices
93
+ """
94
+ # Extract system components
95
+ f, g = self.dynamical_system.f, self.dynamical_system.g
96
+
97
+ # Generate dictionary points
98
+ self.X = self.dynamical_system.sample_state(n_features)
99
+
100
+ # Optimize kernel function
101
+ if optimize:
102
+ self.opt_kernel(X=self.X, n_restarts_optimizer=n_restarts_optimizer)
103
+
104
+ # Define feature map
105
+ self.phi = lambda x: self.kernel_function(x, self.X)[0]
106
+
107
+ # Compute Gram matrix
108
+ self.G = self.kernel_function(self.X, self.X)
109
+ G_inv = np.linalg.inv(self.G)
110
+
111
+ # Compute Koopman operator approximation
112
+ next_states = f(self.X.T).T
113
+ self.U = self.kernel_function(self.X, next_states) @ G_inv
114
+
115
+ # Compute output and state transformation matrices
116
+ self.C = g(self.X.T) @ G_inv
117
+ self.B = self.X.T @ G_inv
118
+
119
+ def get_feature_dimension(self) -> Optional[int]:
120
+ """
121
+ Get the dimension of the feature space.
122
+
123
+ Returns
124
+ -------
125
+ Optional[int]
126
+ Dimension of the feature space, or None if EDMD hasn't been computed.
127
+ """
128
+ return self.X.shape[0] if self.X is not None else None
129
+
130
+ def opt_kernel(self, X: NDArray[np.float64], n_restarts_optimizer: int = 10) -> None:
131
+ """
132
+ Optimize the kernel function for the Koopman operator.
133
+
134
+ Parameters
135
+ ----------
136
+ X : np.ndarray
137
+ Set of points.
138
+ n_restarts_optimizer : int
139
+ Number of restarts for the optimizer. Default is 10.
140
+
141
+ Notes
142
+ -----
143
+ This method uses Gaussian Process Regression to optimize the kernel function.
144
+ """
145
+ # Compute the output of the dynamical system
146
+ y = self.dynamical_system.f(X.T).T
147
+
148
+ # Fit the Gaussian process
149
+ gp = GaussianProcessRegressor(
150
+ kernel=self.kernel_function, n_restarts_optimizer=n_restarts_optimizer
151
+ )
152
+ gp.fit(X.T, y.T)
153
+
154
+ # Update with optimal kernel
155
+ self.kernel_function = gp.kernel_
KKF/solution.py ADDED
@@ -0,0 +1,145 @@
1
+ """Solution data structure for Koopman Kalman Filter results.
2
+
3
+ Provides the KoopmanKalmanFilterSolution dataclass for storing and accessing
4
+ filter outputs.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from typing import Optional
9
+
10
+ import numpy as np
11
+ from numpy.typing import NDArray
12
+
13
+
14
+ @dataclass
15
+ class KoopmanKalmanFilterSolution:
16
+ """
17
+ A class to store the solution of a Koopman-Kalman Filter iteration.
18
+
19
+ This class maintains the state estimates, covariance matrices, and filter gains
20
+ for both the prior (minus) and posterior (plus) estimates in both state and
21
+ feature spaces.
22
+
23
+ Attributes
24
+ ----------
25
+ x_plus : np.ndarray
26
+ Posterior state estimate after measurement update.
27
+ x_minus : np.ndarray
28
+ Prior state estimate from prediction step.
29
+ Pz_plus : np.ndarray
30
+ Posterior covariance matrix in feature space after measurement update.
31
+ Pz_minus : np.ndarray
32
+ Prior covariance matrix in feature space from prediction step.
33
+ Px_plus : np.ndarray
34
+ Posterior covariance matrix in state space after measurement update.
35
+ Px_minus : np.ndarray
36
+ Prior covariance matrix in state space from prediction step.
37
+ S : np.ndarray
38
+ Innovation (residual) covariance matrix.
39
+ K : np.ndarray
40
+ Kalman gain matrix.
41
+
42
+ Notes
43
+ -----
44
+ The class uses the common Kalman filter notation where:
45
+ - (-) denotes prior estimates before measurement update
46
+ - (+) denotes posterior estimates after measurement update
47
+ - Pz refers to covariances in the feature/transformed space
48
+ - Px refers to covariances in the original state space
49
+
50
+ Examples
51
+ --------
52
+ >>> solution = KoopmanKalmanFilterSolution(
53
+ ... x_plus=np.array([1.0, 2.0]),
54
+ ... x_minus=np.array([0.9, 1.9]),
55
+ ... Pz_plus=np.eye(2),
56
+ ... Pz_minus=np.eye(2) * 1.1,
57
+ ... Px_plus=np.eye(2) * 0.9,
58
+ ... Px_minus=np.eye(2),
59
+ ... S=np.eye(2) * 0.5,
60
+ ... K=np.array([[0.1, 0], [0, 0.1]])
61
+ ... )
62
+ """
63
+
64
+ x_plus: NDArray[np.float64]
65
+ x_minus: NDArray[np.float64]
66
+ Pz_plus: NDArray[np.float64]
67
+ Pz_minus: NDArray[np.float64]
68
+ Px_plus: NDArray[np.float64]
69
+ Px_minus: NDArray[np.float64]
70
+ S: NDArray[np.float64]
71
+ K: NDArray[np.float64]
72
+
73
+ def __post_init__(self) -> None:
74
+ """Validate the dimensions of the input arrays."""
75
+ # Ensure all inputs are numpy arrays
76
+ for attr in ["x_plus", "x_minus", "Pz_plus", "Pz_minus", "Px_plus", "Px_minus", "S", "K"]:
77
+ value = getattr(self, attr)
78
+ if not isinstance(value, np.ndarray):
79
+ setattr(self, attr, np.array(value))
80
+
81
+ def get_state_dimension(self) -> int:
82
+ """
83
+ Get the dimension of the state vector.
84
+
85
+ Returns
86
+ -------
87
+ int
88
+ The dimension of the state vector.
89
+ """
90
+ return self.x_plus.shape[-1]
91
+
92
+ def get_feature_dimension(self) -> int:
93
+ """
94
+ Get the dimension of the feature space.
95
+
96
+ Returns
97
+ -------
98
+ int
99
+ The dimension of the feature space.
100
+ """
101
+ return self.Pz_plus.shape[-1]
102
+
103
+ def get_estimation_error(self) -> NDArray[np.float64]:
104
+ """
105
+ Calculate the difference between prior and posterior estimates.
106
+
107
+ Returns
108
+ -------
109
+ np.ndarray
110
+ The difference between posterior and prior state estimates.
111
+ """
112
+ return self.x_plus - self.x_minus
113
+
114
+ def get_trace_reduction(self) -> float:
115
+ """
116
+ Calculate the reduction in uncertainty as measured by trace of covariance.
117
+
118
+ Returns
119
+ -------
120
+ float
121
+ The relative reduction in trace of the state covariance matrix.
122
+ """
123
+ trace_minus = np.trace(self.Px_minus)
124
+ trace_plus = np.trace(self.Px_plus)
125
+ return (trace_minus - trace_plus) / trace_minus if trace_minus != 0 else 0.0
126
+
127
+ def to_dict(self) -> dict:
128
+ """
129
+ Convert the solution to a dictionary format.
130
+
131
+ Returns
132
+ -------
133
+ dict
134
+ Dictionary containing all solution components.
135
+ """
136
+ return {
137
+ "x_plus": self.x_plus,
138
+ "x_minus": self.x_minus,
139
+ "Pz_plus": self.Pz_plus,
140
+ "Pz_minus": self.Pz_minus,
141
+ "Px_plus": self.Px_plus,
142
+ "Px_minus": self.Px_minus,
143
+ "S": self.S,
144
+ "K": self.K,
145
+ }
KKF/systems.py ADDED
@@ -0,0 +1,200 @@
1
+ """Dynamical systems module.
2
+
3
+ Defines classes and utilities for representing general dynamical systems
4
+ with state transitions and measurement functions.
5
+ """
6
+
7
+ from typing import Any, Callable, Optional
8
+
9
+ import numpy as np
10
+ from numpy.typing import NDArray
11
+ from scipy.stats import rv_continuous
12
+
13
+
14
+ class DynamicalSystem:
15
+ """
16
+ A class representing a general dynamical system with state and measurement equations.
17
+
18
+ This class encapsulates the dynamics, measurements, and associated probability
19
+ distributions for both state and measurement noise. Can be considered as:
20
+
21
+ Discrete time:
22
+ Dynamics: x_{k+1} = f(x_{k}) + w_{k}
23
+ Measurements: y_{k} = g(x_{k}) + v_{k}
24
+
25
+ Continous time:
26
+ Dynamics: x'(t) = f(x(t)) + w(t)
27
+ Measurements: y(t) = g(x(t)) + v(t)
28
+
29
+ Attributes
30
+ ----------
31
+ nx : int
32
+ Dimension of the state space.
33
+ ny : int
34
+ Dimension of the measurement/output space.
35
+ f : Callable
36
+ State transition function (dynamics).
37
+ g : Callable
38
+ Measurement/output function.
39
+ dist_X : rv_continuous
40
+ Probability distribution for initial states.
41
+ dist_dyn : rv_continuous
42
+ Probability distribution for dynamics noise.
43
+ dist_obs : rv_continuous
44
+ Probability distribution for measurement noise.
45
+ discrete_time : bool
46
+ Indicates if the system is in discrete time, if False the system is in continous time.
47
+
48
+ Notes
49
+ -----
50
+ The functions f and g should have the following signatures:
51
+ - f(x: ndarray, w: ndarray) -> ndarray
52
+ - g(x: ndarray, v: ndarray) -> ndarray
53
+ where:
54
+ - x is the state vector
55
+ - w is the process noise
56
+ - v is the measurement noise
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ nx: int,
62
+ ny: int,
63
+ f: Callable[[NDArray[np.float64]], NDArray[np.float64]],
64
+ g: Callable[[NDArray[np.float64]], NDArray[np.float64]],
65
+ dist_X: rv_continuous,
66
+ dist_dyn: rv_continuous,
67
+ dist_obs: rv_continuous,
68
+ discrete_time: bool,
69
+ ) -> None:
70
+ if nx <= 0:
71
+ raise ValueError(f"State dimension nx must be positive, got {nx}")
72
+ self.nx = nx
73
+ self.ny = ny
74
+ self.f = f
75
+ self.g = g
76
+ self.dist_X = dist_X
77
+ self.dist_dyn = dist_dyn
78
+ self.dist_obs = dist_obs
79
+ self.discrete_time = discrete_time
80
+
81
+ def dynamics(self, x: NDArray[np.float64], w: NDArray[np.float64]) -> NDArray[np.float64]:
82
+ """
83
+ Apply the state transition function.
84
+
85
+ Parameters
86
+ ----------
87
+ x : np.ndarray
88
+ Current state vector.
89
+ w : np.ndarray
90
+ Process noise vector.
91
+
92
+ Returns
93
+ -------
94
+ np.ndarray
95
+ Next state vector.
96
+ """
97
+ return self.f(x) + w
98
+
99
+ def measurements(self, x: NDArray[np.float64], v: NDArray[np.float64]) -> NDArray[np.float64]:
100
+ """
101
+ Apply the measurement function.
102
+
103
+ Parameters
104
+ ----------
105
+ x : np.ndarray
106
+ Current state vector.
107
+ v : np.ndarray
108
+ Measurement noise vector.
109
+
110
+ Returns
111
+ -------
112
+ np.ndarray
113
+ Measurement/output vector.
114
+ """
115
+ return self.g(x) + v
116
+
117
+ def sample_state(self, size: int = 1) -> NDArray[np.float64]:
118
+ """
119
+ Sample from the state distribution.
120
+
121
+ Parameters
122
+ ----------
123
+ size : int, optional
124
+ Number of samples to draw. Default is 1.
125
+
126
+ Returns
127
+ -------
128
+ np.ndarray
129
+ Sampled state(s).
130
+ """
131
+ return self.dist_X.rvs(size=size).reshape((size, self.nx))
132
+
133
+
134
+ def create_additive_system(
135
+ nx: int,
136
+ ny: int,
137
+ f: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
138
+ g: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
139
+ dist_X: rv_continuous,
140
+ dist_dyn: rv_continuous,
141
+ dist_obs: rv_continuous,
142
+ N_samples: int,
143
+ discrete_time: bool = True,
144
+ ) -> DynamicalSystem:
145
+ """
146
+ Create an additive dynamical system where noise is added to the state and observation functions.
147
+
148
+ Parameters
149
+ ----------
150
+ nx : int
151
+ Dimension of the state space.
152
+ ny : int
153
+ Dimension of the measurement space.
154
+ f : Callable
155
+ State transition function (with noise).
156
+ g : Callable
157
+ Measurement function (with noise).
158
+ dist_X : rv_continuous
159
+ Initial state distribution.
160
+ dist_dyn : rv_continuous
161
+ Dynamics noise distribution.
162
+ dist_obs : rv_continuous
163
+ Measurement noise distribution.
164
+ N_samples: int
165
+ Number of samples to generate empirical mean of dynamics and observation.
166
+ discrete_time : bool, optional
167
+ Indicates if the system is in discrete time. Default is True.
168
+
169
+ Returns
170
+ -------
171
+ DynamicalSystem
172
+ New dynamical system instance with additive noise.
173
+
174
+ Notes
175
+ -----
176
+ The resulting system has the form:
177
+ x[k+1] = f(x[k]) + w[k]
178
+ y[k] = g(x[k]) + v[k]
179
+ where w and v are noise terms.
180
+ """
181
+ new_f = lambda x: np.mean([f(x.reshape((len(x), 1)), dist_dyn.rvs(N_samples))], axis=1)
182
+ new_g = lambda x: np.mean([g(x.reshape((len(x), 1)), dist_obs.rvs(N_samples))], axis=1)
183
+
184
+ class DynDist:
185
+ def __init__(self) -> None:
186
+ pass
187
+
188
+ def rvs(self, x: NDArray[np.float64], size: int = 1) -> NDArray[np.float64]:
189
+ return f(x, dist_dyn.rvs(size=size)) - new_f(x)
190
+
191
+ class ObsDist:
192
+ def __init__(self) -> None:
193
+ pass
194
+
195
+ def rvs(self, x: NDArray[np.float64], size: int = 1) -> NDArray[np.float64]:
196
+ return g(x, dist_obs.rvs(size=size)) - new_g(x)
197
+
198
+ return DynamicalSystem(
199
+ nx, ny, new_f, new_g, dist_X, DynDist(), ObsDist(), discrete_time=discrete_time
200
+ )
KKF/utils.py ADDED
File without changes
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 diegoolguinw
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,367 @@
1
+ Metadata-Version: 2.2
2
+ Name: kkf
3
+ Version: 0.3.0
4
+ Summary: KKF: Kernel Koopman Kalman Filter - A library for non-linear state estimation using kernel methods and Koopman operator theory
5
+ Author-email: Diego Olguin-Wende <dolguin@dim.uchile.cl>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/diegoolguinw/kkf
8
+ Project-URL: Documentation, https://kkf.readthedocs.io
9
+ Project-URL: Repository, https://github.com/diegoolguinw/kkf.git
10
+ Project-URL: Bug Tracker, https://github.com/diegoolguinw/kkf/issues
11
+ Keywords: kalman-filter,koopman-operator,kernel-methods,edmd,nonlinear-dynamics,state-estimation,filtering
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Scientific/Engineering
24
+ Classifier: Topic :: Scientific/Engineering :: Physics
25
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: numpy>=1.20.0
30
+ Requires-Dist: scipy>=1.7.0
31
+ Requires-Dist: scikit-learn>=1.0.0
32
+ Provides-Extra: viz
33
+ Requires-Dist: matplotlib>=3.5.0; extra == "viz"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
36
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
37
+ Requires-Dist: black>=22.0.0; extra == "dev"
38
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
39
+ Requires-Dist: mypy>=0.990; extra == "dev"
40
+ Requires-Dist: isort>=5.10.0; extra == "dev"
41
+ Requires-Dist: pylint>=2.13.0; extra == "dev"
42
+ Provides-Extra: docs
43
+ Requires-Dist: sphinx>=4.5.0; extra == "docs"
44
+ Requires-Dist: sphinx-rtd-theme>=1.0.0; extra == "docs"
45
+ Requires-Dist: nbsphinx>=0.8.8; extra == "docs"
46
+ Requires-Dist: ipython>=7.0.0; extra == "docs"
47
+ Requires-Dist: jupyter>=1.0.0; extra == "docs"
48
+ Provides-Extra: all
49
+ Requires-Dist: kkf[dev,docs,viz]; extra == "all"
50
+
51
+ # Kernel Koopman Kalman Filter (KKF)
52
+
53
+ [![Tests](https://github.com/diegoolguinw/kkf/workflows/Tests%20&%20Code%20Quality/badge.svg)](https://github.com/diegoolguinw/kkf/actions)
54
+ [![PyPI version](https://badge.fury.io/py/kkf.svg)](https://badge.fury.io/py/kkf)
55
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
56
+ [![Python 3.8+](https://img.shields.io/badge/Python-3.8+-blue.svg)](https://www.python.org/downloads/)
57
+
58
+ A Python library for non-linear state estimation using Kernel-based Extended Dynamic Mode Decomposition (kEDMD) and Koopman operator theory.
59
+
60
+ ## What is KKF?
61
+
62
+ KKF is a sophisticated filtering library that combines:
63
+
64
+ - **Koopman Operator Theory**: Lifts nonlinear dynamics to a linear setting in feature space
65
+ - **Kernel Methods**: Uses kernel functions for feature space approximation
66
+ - **Kalman Filtering**: Applies optimal filtering in the lifted feature space
67
+ - **Extended Dynamic Mode Decomposition**: Learns accurate linear approximations
68
+
69
+ This enables accurate state estimation for highly nonlinear dynamical systems where traditional Kalman filters struggle.
70
+
71
+ ## Features
72
+
73
+ ✨ **Core Capabilities**
74
+ - Kernel Extended Dynamic Mode Decomposition (kEDMD)
75
+ - Non-linear Kalman Filter implementation
76
+ - Support for arbitrary dynamical systems
77
+ - Multiple kernel function options (RBF, Matérn, etc.)
78
+ - Robust state estimation with uncertainty quantification
79
+ - Covariance propagation in both state and feature spaces
80
+
81
+ 📊 **High-Quality Implementation**
82
+ - Type hints throughout the codebase
83
+ - Comprehensive error handling
84
+ - Numerical stability optimizations
85
+ - Efficient NumPy/SciPy operations
86
+
87
+ 🧪 **Well-Tested**
88
+ - 95+ unit and integration tests
89
+ - Edge case coverage
90
+ - Multi-platform CI/CD
91
+ - Coverage reporting
92
+
93
+ 📚 **Comprehensive Documentation**
94
+ - Detailed docstrings
95
+ - 3 complete working examples
96
+ - Parameter optimization guide
97
+ - API documentation
98
+
99
+ ## Installation
100
+
101
+ ### Current Release
102
+
103
+ ```bash
104
+ pip install kkf
105
+ ```
106
+
107
+ ### Development Version
108
+
109
+ ```bash
110
+ git clone https://github.com/diegoolguinw/kkf.git
111
+ cd kkf
112
+ pip install -e ".[dev]"
113
+ ```
114
+
115
+ ### Optional Dependencies
116
+
117
+ ```bash
118
+ # For visualization examples
119
+ pip install kkf[viz]
120
+
121
+ # For development and testing
122
+ pip install kkf[dev]
123
+
124
+ # For building documentation
125
+ pip install kkf[docs]
126
+
127
+ # Install everything
128
+ pip install kkf[all]
129
+ ```
130
+
131
+ ## Quick Start
132
+
133
+ Here's a complete example of using KKF to estimate states in a SIR (Susceptible-Infected-Recovered) epidemiological model (see also [`examples/02_sir_epidemic_model.py`](examples/02_sir_epidemic_model.py)):
134
+
135
+ ```python
136
+ import numpy as np
137
+ from scipy import stats
138
+ from sklearn.gaussian_process.kernels import Matern
139
+
140
+ from KKF import systems, koopman, filter
141
+
142
+ # Define SIR dynamics
143
+ beta, gamma = 0.12, 0.04
144
+
145
+ def f(x): # State transition
146
+ return x + np.array([
147
+ -beta * x[0] * x[1],
148
+ beta * x[0] * x[1] - gamma * x[1],
149
+ gamma * x[1]
150
+ ])
151
+
152
+ def g(x): # Observation function (infected population)
153
+ return np.array([x[1]])
154
+
155
+ # Setup system
156
+ nx, ny, n_features = 3, 1, 50
157
+ X_dist = stats.dirichlet(alpha=np.ones(nx))
158
+ dyn_dist = stats.multivariate_normal(mean=np.zeros(nx), cov=1e-5 * np.eye(nx))
159
+ obs_dist = stats.multivariate_normal(mean=np.zeros(ny), cov=1e-3 * np.eye(ny))
160
+
161
+ system = DynamicalSystem(nx, ny, f, g, X_dist, dyn_dist, obs_dist, discrete_time=True)
162
+
163
+ # Generate synthetic observations
164
+ np.random.seed(42)
165
+ n_timesteps = 100
166
+ y_obs = np.random.randn(n_timesteps, ny) * 0.1 + 0.1
167
+
168
+ # Apply Koopman Kalman Filter
169
+ kernel = Matern(length_scale=1.0, nu=0.5)
170
+ koopman_op = KoopmanOperator(kernel, system)
171
+
172
+ x0_prior = np.array([0.8, 0.15, 0.05])
173
+ initial_prior = stats.multivariate_normal(mean=x0_prior, cov=0.1 * np.eye(nx))
174
+
175
+ solution = apply_koopman_kalman_filter(
176
+ koopman_operator=koopman_op,
177
+ observations=y_obs,
178
+ initial_distribution=initial_prior,
179
+ n_features=n_features,
180
+ optimize=False,
181
+ noise_samples=100
182
+ )
183
+
184
+ # Access results
185
+ print(f"State estimates shape: {solution.x_plus.shape}") # (n_timesteps, nx)
186
+ print(f"State covariances shape: {solution.Px_plus.shape}") # (n_timesteps, nx, nx)
187
+ print(f"Mean estimation error: {solution.get_estimation_error().mean():.6f}")
188
+ ```
189
+
190
+ ## Examples
191
+
192
+ The library includes three comprehensive examples:
193
+
194
+ ### 1. Basic Linear System (`examples/01_basic_linear_system.py`)
195
+
196
+ Introduces core concepts with a simple linear system:
197
+ - System definition
198
+ - Synthetic data generation
199
+ - Filter application
200
+ - Confidence interval visualization
201
+
202
+ **Run:** `python examples/01_basic_linear_system.py`
203
+
204
+ ### 2. SIR Epidemic Model (`examples/02_sir_epidemic_model.py`)
205
+
206
+ Real-world application to disease modeling:
207
+ - Nonlinear dynamics
208
+ - Partial state observation
209
+ - Population dynamics estimation
210
+ - Epidemic curve prediction
211
+
212
+ **Run:** `python examples/02_sir_epidemic_model.py`
213
+
214
+ ### 3. Parameter Exploration (`examples/03_advanced_parameter_exploration.py`)
215
+
216
+ Advanced hyperparameter tuning:
217
+ - Multiple kernel comparison
218
+ - Feature dimension optimization
219
+ - Performance analysis
220
+ - Systematic search strategy
221
+
222
+ **Run:** `python examples/03_advanced_parameter_exploration.py`
223
+
224
+ For more details, see [examples/README.md](examples/README.md)
225
+
226
+ ## Documentation
227
+
228
+ ### API Reference
229
+
230
+ ```python
231
+ # Core classes
232
+ from KKF import DynamicalSystem, KoopmanOperator, KoopmanKalmanFilterSolution
233
+ from KKF.filter import apply_koopman_kalman_filter
234
+
235
+ # Utility functions
236
+ from KKF import (
237
+ compute_initial_covariance,
238
+ compute_dynamics_covariance,
239
+ compute_observation_covariance
240
+ )
241
+ ```
242
+
243
+ ### Theory
244
+
245
+ The KKF algorithm works in three main steps:
246
+
247
+ 1. **Lifting**: Maps nonlinear states to a higher-dimensional feature space using kernel methods
248
+ 2. **Approximation**: Constructs a linear Koopman operator approximation via kEDMD
249
+ 3. **Filtering**: Applies Kalman filtering in the lifted space, then maps back to original coordinates
250
+
251
+ ### Key Parameters
252
+
253
+ | Parameter | Description | Default |
254
+ |-----------|-------------|---------|
255
+ | `n_features` | Dimension of lifted feature space | 50 |
256
+ | `kernel` | Kernel function (RBF, Matérn, etc.) | RBF(1.0) |
257
+ | `optimize` | Whether to optimize kernel parameters | False |
258
+ | `noise_samples` | Monte Carlo samples for covariance | 100 |
259
+
260
+ ## System Requirements
261
+
262
+ - Python 3.8 or higher
263
+ - NumPy ≥ 1.20.0
264
+ - SciPy ≥ 1.7.0
265
+ - scikit-learn ≥ 1.0.0
266
+ - Matplotlib ≥ 3.5.0 (optional, for visualization)
267
+
268
+ ## Advanced Usage
269
+
270
+ ### Custom Kernel Functions
271
+
272
+ ```python
273
+ from sklearn.gaussian_process.kernels import (
274
+ RBF, Matern, ExpSineSquared, DotProduct, ConstantKernel
275
+ )
276
+
277
+ # Different kernel choices
278
+ kernel1 = RBF(length_scale=1.0)
279
+ kernel2 = Matern(length_scale=1.0, nu=2.5)
280
+ kernel3 = ExpSineSquared(length_scale=1.0, periodicity=1.0)
281
+ kernel4 = ConstantKernel(constant_value=1.0) * Matern(nu=1.5)
282
+ ```
283
+
284
+ ### Manual Kernel Optimization
285
+
286
+ ```python
287
+ solution = apply_koopman_kalman_filter(
288
+ koopman_operator=koopman_op,
289
+ observations=y_obs,
290
+ initial_distribution=initial_prior,
291
+ n_features=50,
292
+ optimize=True, # Enable kernel optimization
293
+ n_restarts_optimizer=20 # Number of optimization restarts
294
+ )
295
+ ```
296
+
297
+ ### Extracting Uncertainty Estimates
298
+
299
+ ```python
300
+ # Posterior state covariance
301
+ Px_plus = solution.Px_plus # (n_timesteps, nx, nx)
302
+
303
+ # Compute confidence intervals
304
+ std_devs = np.sqrt(np.diagonal(Px_plus, axis1=1, axis2=2))
305
+ ci_lower = solution.x_plus - 1.96 * std_devs
306
+ ci_upper = solution.x_plus + 1.96 * std_devs
307
+ ```
308
+
309
+ ## Testing
310
+
311
+ Run the comprehensive test suite:
312
+
313
+ ```bash
314
+ # All tests
315
+ pytest tests/
316
+
317
+ # Verbose output
318
+ pytest tests/ -v
319
+
320
+ # Specific test file
321
+ pytest tests/test_core.py
322
+
323
+ # Skip slow tests
324
+ pytest tests/ -m "not slow"
325
+
326
+ # With coverage report
327
+ pytest tests/ --cov=KKF --cov-report=html
328
+ ```
329
+
330
+ ## Contributing
331
+
332
+ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to:
333
+ - Report bugs
334
+ - Suggest features
335
+ - Submit pull requests
336
+ - Improve documentation
337
+
338
+ ## Code of Conduct
339
+
340
+ Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating you agree to abide by its terms.
341
+
342
+ ## Citation
343
+
344
+ If you use KKF in your research, please cite:
345
+
346
+ ```bibtex
347
+ @software{kkf2024,
348
+ title={KKF: Kernel Koopman Kalman Filter},
349
+ author={Olguin-Wende, Diego},
350
+ year={2024},
351
+ url={https://github.com/diegoolguinw/kkf}
352
+ }
353
+ ```
354
+
355
+ ## License
356
+
357
+ This project is licensed under the MIT License - see [LICENSE](LICENSE) file for details.
358
+
359
+ ## Changelog
360
+
361
+ See [CHANGELOG.md](CHANGELOG.md) for version history and release notes.
362
+
363
+ ## Support & Contact
364
+
365
+ - **Issues & Bug Reports**: [GitHub Issues](https://github.com/diegoolguinw/kkf/issues)
366
+ - **Discussions**: [GitHub Discussions](https://github.com/diegoolguinw/kkf/discussions)
367
+ - **Email**: dolguin@dim.uchile.cl
@@ -0,0 +1,12 @@
1
+ KKF/__init__.py,sha256=3W3pPuvwh-uVF9eHZ6o6xzSRLRHQEwkfXQdv_4B4fI4,1412
2
+ KKF/covariances.py,sha256=7ET1-wvbDnYBIc8v_i_aO7k06wZwHXI7Cxj_HGBbIkQ,5735
3
+ KKF/filter.py,sha256=evtaZFj9mbOKie6wUKesC50rX1-Dpvkk-Pf4uwOd3m0,8095
4
+ KKF/koopman.py,sha256=KobfN1WPW-kxiCl_4-9v5NoGX4TTfChw8s8ngM0Evp0,5480
5
+ KKF/solution.py,sha256=AqglUKkr6XiwEXnfA8YCX4edTaF1cw3ceLGlDOAGEOk,4425
6
+ KKF/systems.py,sha256=FmKc5BuRVEPxKF94y1FAi_-uvmCteNFYuF3ps7NRL00,5798
7
+ KKF/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ kkf-0.3.0.dist-info/LICENSE,sha256=6WnhMjAMnLiDcClR0tHyWuaX8lEs0MSn5R51JqXL7Ts,1069
9
+ kkf-0.3.0.dist-info/METADATA,sha256=t_ZymB92I2o0Iz22L7WcOq0JaXAV3gy_xc1Hk7wBBVI,10888
10
+ kkf-0.3.0.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
11
+ kkf-0.3.0.dist-info/top_level.txt,sha256=yCY9tBJ5DJeTknZPyFujr9XvomHsm-7oP4ABMz348pw,4
12
+ kkf-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (76.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ KKF