kkf 0.11__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.
kkf-0.11/LICENSE ADDED
@@ -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.
kkf-0.11/PKG-INFO ADDED
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.1
2
+ Name: kkf
3
+ Version: 0.11
4
+ Summary: kkf: a library for Python implementation of Kernel-Koopman-Kalman Filter.
5
+ Home-page: https://github.com/diegoolguinw/kkf
6
+ Author: Diego Olguin-Wende
7
+ Author-email: dolguin@dim.uchile.cl
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+
15
+ # Kernel Koopman Kalman Filter
16
+
17
+ KKKF is a Python library that implements kernel Extended Dynamic Mode Decomposition (EDMD) of Koopman operators and provides a non-linear variant of the Kalman Filter. This library is particularly useful for state estimation in dynamical systems with non-linear behavior.
18
+
19
+ ## Installation
20
+
21
+ You can install KKKF using pip:
22
+
23
+ ```bash
24
+ pip install KKKF
25
+ ```
26
+
27
+ ## Features
28
+
29
+ - Kernel-based Extended Dynamic Mode Decomposition (EDMD)
30
+ - Non-linear Kalman Filter implementation
31
+ - Support for general dynamical systems
32
+ - Integration with various kernel functions (e.g., Matérn kernel)
33
+ - Robust state estimation with noise handling
34
+
35
+ ## Dependencies
36
+
37
+ - NumPy
38
+ - SciPy
39
+ - scikit-learn (for kernel functions)
40
+ - Matplotlib (for visualization)
41
+
42
+ ## Quick Start
43
+
44
+ Here's a complete example of using KKKF to estimate and visualize states in a SIR (Susceptible-Infected-Recovered) model:
45
+
46
+ ```python
47
+ # Dependencies
48
+ import numpy as np
49
+ import matplotlib.pyplot as plt
50
+ from scipy import stats
51
+ from sklearn.gaussian_process.kernels import Matern
52
+ from KKKF.DynamicalSystems import DynamicalSystem
53
+ from KKKF.kEDMD import KoopmanOperator
54
+ from KKKF.applyKKKF import apply_koopman_kalman_filter
55
+
56
+ # Define system parameters
57
+ beta, gamma = 0.12, 0.04
58
+
59
+ # Define system dynamics
60
+ def f(x):
61
+ return x + np.array([-beta*x[0]*x[1], beta*x[0]*x[1] - gamma*x[1], gamma*x[1]])
62
+
63
+ # Define system observations
64
+ def g(x):
65
+ return np.array([x[1]])
66
+
67
+ # Setup system dimensions and kernel
68
+ N = 300
69
+ nx, ny = 3, 1
70
+ k = Matern(length_scale=N**(-1/nx), nu=0.5)
71
+
72
+ # Setup distributions
73
+ X_dist = stats.dirichlet(alpha=np.ones(nx))
74
+ dyn_dist = stats.multivariate_normal(mean=np.zeros(nx), cov=1e-5*np.eye(3))
75
+ obs_dist = stats.multivariate_normal(mean=np.zeros(ny), cov=1e-3*np.eye(1))
76
+
77
+ # Create dynamical system
78
+ dyn = DynamicalSystem(nx, ny, f, g, X_dist, dyn_dist, obs_dist)
79
+
80
+ # Generate synthetic data
81
+ iters = 100
82
+ x0 = np.array([0.9, 0.1, 0.0])
83
+ x = np.zeros((iters, nx))
84
+ y = np.zeros((iters, ny))
85
+
86
+ x[0] = x0
87
+ y[0] = g(x[0]) + obs_dist.rvs()
88
+
89
+ for i in range(1, iters):
90
+ x[i] = f(x[i-1]) + dyn.dist_dyn.rvs()
91
+ y[i] = g(x[i]) + obs_dist.rvs()
92
+
93
+ # Initialize and apply Koopman Kalman Filter
94
+
95
+ # Prior for the initial condition
96
+ x0_prior = np.array([0.8, 0.15, 0.05])
97
+ d0 = stats.multivariate_normal(mean=x0_prior, cov=0.1*np.eye(3))
98
+
99
+ # Koopman operator
100
+ Koop = KoopmanOperator(k, dyn)
101
+
102
+ # Solution
103
+ sol = apply_koopman_kalman_filter(Koop, y, d0, N, noise_samples=100)
104
+
105
+ # Visualization with confidence intervals
106
+ conf = np.zeros((iters, nx))
107
+ for i in range(iters):
108
+ conf[i, :] = np.sqrt(np.diag(sol.Px_plus[i,:,:]))
109
+
110
+ # 95% confidence interval
111
+ err1 = sol.x_plus - 1.96*conf
112
+ err2 = sol.x_plus + 1.96*conf
113
+
114
+ # Plot elements
115
+ labels = ["S (True)", "I (True)", "R (True)"]
116
+ colors = ["blue", "red", "green"]
117
+
118
+ plt.plot(sol.x_plus, label=["S (KKF)", "I (KKF)", "R (KKF)"])
119
+
120
+ for i in range(nx):
121
+ plt.fill_between(np.arange(iters), err1[:,i], err2[:,i], alpha=0.6)
122
+ plt.scatter(np.arange(iters), x[:,i], label=labels[i], color=colors[i], s=1.4)
123
+
124
+ plt.xlabel("Days")
125
+ plt.ylabel("Propotion of population")
126
+ plt.title("KKKF Estimation")
127
+ plt.legend()
128
+ plt.show()
129
+ ```
130
+
131
+ ## API Reference
132
+
133
+ ### DynamicalSystem
134
+
135
+ ```python
136
+ DynamicalSystem(nx, ny, f, g, X_dist, dyn_dist, obs_dist)
137
+ ```
138
+ Creates a dynamical system with:
139
+ - `nx`: State dimension
140
+ - `ny`: Observation dimension
141
+ - `f`: State transition function
142
+ - `g`: Observation function
143
+ - `X_dist`: State distribution
144
+ - `dyn_dist`: Dynamic noise distribution
145
+ - `obs_dist`: Observation noise distribution
146
+
147
+ ### KoopmanOperator
148
+
149
+ ```python
150
+ KoopmanOperator(kernel, dynamical_system)
151
+ ```
152
+ Initializes a Koopman operator with:
153
+ - `kernel`: Kernel function (e.g., Matérn kernel)
154
+ - `dynamical_system`: Instance of DynamicalSystem
155
+
156
+ ### apply_koopman_kalman_filter
157
+
158
+ ```python
159
+ apply_koopman_kalman_filter(koopman, observations, initial_distribution, N, noise_samples=100)
160
+ ```
161
+ Applies the Koopman-based Kalman filter with:
162
+ - `koopman`: KoopmanOperator instance
163
+ - `observations`: Observation data
164
+ - `initial_distribution`: Initial state distribution
165
+ - `N`: Number of samples
166
+ - `noise_samples`: Number of noise samples for uncertainty estimation
167
+
168
+ Returns a solution object containing:
169
+ - `x_plus`: State estimates
170
+ - `Px_plus`: Covariance matrices
171
+ - Additional filter statistics
172
+
173
+ ## Visualization
174
+
175
+ The library supports visualization of results with confidence intervals. The example above demonstrates how to:
176
+ - Plot state estimates
177
+ - Add confidence intervals (shaded regions)
178
+ - Compare with real data (if available)
179
+ - Customize plot appearance
180
+
181
+ ## Contributing
182
+
183
+ Contributions are welcome! Please feel free to submit a Pull Request.
184
+
185
+ ## License
186
+
187
+ This project is licensed under the MIT License - see the LICENSE file for details.
188
+
189
+ ## Citation
190
+
191
+ If you use this library in your research, please cite:
192
+
193
+ ```bibtex
194
+ @software{kkkf,
195
+ title = {KKKF: Kernel Koopman Kalman Filter},
196
+ year = {2024},
197
+ author = {Diego Olguín-Wende},
198
+ url = {https://github.com/diegoolguinw/KKKF}
199
+ }
200
+ ```
kkf-0.11/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # Kernel Koopman Kalman Filter
2
+
3
+ KKKF is a Python library that implements kernel Extended Dynamic Mode Decomposition (EDMD) of Koopman operators and provides a non-linear variant of the Kalman Filter. This library is particularly useful for state estimation in dynamical systems with non-linear behavior.
4
+
5
+ ## Installation
6
+
7
+ You can install KKKF using pip:
8
+
9
+ ```bash
10
+ pip install KKKF
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - Kernel-based Extended Dynamic Mode Decomposition (EDMD)
16
+ - Non-linear Kalman Filter implementation
17
+ - Support for general dynamical systems
18
+ - Integration with various kernel functions (e.g., Matérn kernel)
19
+ - Robust state estimation with noise handling
20
+
21
+ ## Dependencies
22
+
23
+ - NumPy
24
+ - SciPy
25
+ - scikit-learn (for kernel functions)
26
+ - Matplotlib (for visualization)
27
+
28
+ ## Quick Start
29
+
30
+ Here's a complete example of using KKKF to estimate and visualize states in a SIR (Susceptible-Infected-Recovered) model:
31
+
32
+ ```python
33
+ # Dependencies
34
+ import numpy as np
35
+ import matplotlib.pyplot as plt
36
+ from scipy import stats
37
+ from sklearn.gaussian_process.kernels import Matern
38
+ from KKKF.DynamicalSystems import DynamicalSystem
39
+ from KKKF.kEDMD import KoopmanOperator
40
+ from KKKF.applyKKKF import apply_koopman_kalman_filter
41
+
42
+ # Define system parameters
43
+ beta, gamma = 0.12, 0.04
44
+
45
+ # Define system dynamics
46
+ def f(x):
47
+ return x + np.array([-beta*x[0]*x[1], beta*x[0]*x[1] - gamma*x[1], gamma*x[1]])
48
+
49
+ # Define system observations
50
+ def g(x):
51
+ return np.array([x[1]])
52
+
53
+ # Setup system dimensions and kernel
54
+ N = 300
55
+ nx, ny = 3, 1
56
+ k = Matern(length_scale=N**(-1/nx), nu=0.5)
57
+
58
+ # Setup distributions
59
+ X_dist = stats.dirichlet(alpha=np.ones(nx))
60
+ dyn_dist = stats.multivariate_normal(mean=np.zeros(nx), cov=1e-5*np.eye(3))
61
+ obs_dist = stats.multivariate_normal(mean=np.zeros(ny), cov=1e-3*np.eye(1))
62
+
63
+ # Create dynamical system
64
+ dyn = DynamicalSystem(nx, ny, f, g, X_dist, dyn_dist, obs_dist)
65
+
66
+ # Generate synthetic data
67
+ iters = 100
68
+ x0 = np.array([0.9, 0.1, 0.0])
69
+ x = np.zeros((iters, nx))
70
+ y = np.zeros((iters, ny))
71
+
72
+ x[0] = x0
73
+ y[0] = g(x[0]) + obs_dist.rvs()
74
+
75
+ for i in range(1, iters):
76
+ x[i] = f(x[i-1]) + dyn.dist_dyn.rvs()
77
+ y[i] = g(x[i]) + obs_dist.rvs()
78
+
79
+ # Initialize and apply Koopman Kalman Filter
80
+
81
+ # Prior for the initial condition
82
+ x0_prior = np.array([0.8, 0.15, 0.05])
83
+ d0 = stats.multivariate_normal(mean=x0_prior, cov=0.1*np.eye(3))
84
+
85
+ # Koopman operator
86
+ Koop = KoopmanOperator(k, dyn)
87
+
88
+ # Solution
89
+ sol = apply_koopman_kalman_filter(Koop, y, d0, N, noise_samples=100)
90
+
91
+ # Visualization with confidence intervals
92
+ conf = np.zeros((iters, nx))
93
+ for i in range(iters):
94
+ conf[i, :] = np.sqrt(np.diag(sol.Px_plus[i,:,:]))
95
+
96
+ # 95% confidence interval
97
+ err1 = sol.x_plus - 1.96*conf
98
+ err2 = sol.x_plus + 1.96*conf
99
+
100
+ # Plot elements
101
+ labels = ["S (True)", "I (True)", "R (True)"]
102
+ colors = ["blue", "red", "green"]
103
+
104
+ plt.plot(sol.x_plus, label=["S (KKF)", "I (KKF)", "R (KKF)"])
105
+
106
+ for i in range(nx):
107
+ plt.fill_between(np.arange(iters), err1[:,i], err2[:,i], alpha=0.6)
108
+ plt.scatter(np.arange(iters), x[:,i], label=labels[i], color=colors[i], s=1.4)
109
+
110
+ plt.xlabel("Days")
111
+ plt.ylabel("Propotion of population")
112
+ plt.title("KKKF Estimation")
113
+ plt.legend()
114
+ plt.show()
115
+ ```
116
+
117
+ ## API Reference
118
+
119
+ ### DynamicalSystem
120
+
121
+ ```python
122
+ DynamicalSystem(nx, ny, f, g, X_dist, dyn_dist, obs_dist)
123
+ ```
124
+ Creates a dynamical system with:
125
+ - `nx`: State dimension
126
+ - `ny`: Observation dimension
127
+ - `f`: State transition function
128
+ - `g`: Observation function
129
+ - `X_dist`: State distribution
130
+ - `dyn_dist`: Dynamic noise distribution
131
+ - `obs_dist`: Observation noise distribution
132
+
133
+ ### KoopmanOperator
134
+
135
+ ```python
136
+ KoopmanOperator(kernel, dynamical_system)
137
+ ```
138
+ Initializes a Koopman operator with:
139
+ - `kernel`: Kernel function (e.g., Matérn kernel)
140
+ - `dynamical_system`: Instance of DynamicalSystem
141
+
142
+ ### apply_koopman_kalman_filter
143
+
144
+ ```python
145
+ apply_koopman_kalman_filter(koopman, observations, initial_distribution, N, noise_samples=100)
146
+ ```
147
+ Applies the Koopman-based Kalman filter with:
148
+ - `koopman`: KoopmanOperator instance
149
+ - `observations`: Observation data
150
+ - `initial_distribution`: Initial state distribution
151
+ - `N`: Number of samples
152
+ - `noise_samples`: Number of noise samples for uncertainty estimation
153
+
154
+ Returns a solution object containing:
155
+ - `x_plus`: State estimates
156
+ - `Px_plus`: Covariance matrices
157
+ - Additional filter statistics
158
+
159
+ ## Visualization
160
+
161
+ The library supports visualization of results with confidence intervals. The example above demonstrates how to:
162
+ - Plot state estimates
163
+ - Add confidence intervals (shaded regions)
164
+ - Compare with real data (if available)
165
+ - Customize plot appearance
166
+
167
+ ## Contributing
168
+
169
+ Contributions are welcome! Please feel free to submit a Pull Request.
170
+
171
+ ## License
172
+
173
+ This project is licensed under the MIT License - see the LICENSE file for details.
174
+
175
+ ## Citation
176
+
177
+ If you use this library in your research, please cite:
178
+
179
+ ```bibtex
180
+ @software{kkkf,
181
+ title = {KKKF: Kernel Koopman Kalman Filter},
182
+ year = {2024},
183
+ author = {Diego Olguín-Wende},
184
+ url = {https://github.com/diegoolguinw/KKKF}
185
+ }
186
+ ```
@@ -0,0 +1,185 @@
1
+ from typing import Callable, Any, Optional, Union
2
+ import numpy as np
3
+ from numpy.typing import NDArray
4
+ from scipy.stats import rv_continuous
5
+
6
+ class DynamicalSystem:
7
+ """
8
+ A class representing a general dynamical system with state and measurement equations.
9
+
10
+ This class encapsulates the dynamics, measurements, and associated probability
11
+ distributions for both state and measurement noise. Can be considered as:
12
+
13
+ Discrete time:
14
+ Dynamics: x_{k+1} = f(x_{k}) + w_{k}
15
+ Measurements: y_{k} = g(x_{k}) + v_{k}
16
+
17
+ Continous time:
18
+ Dynamics: x'(t) = f(x(t)) + w(t)
19
+ Measurements: y(t) = g(x(t)) + v(t)
20
+
21
+ Attributes
22
+ ----------
23
+ nx : int
24
+ Dimension of the state space.
25
+ ny : int
26
+ Dimension of the measurement/output space.
27
+ f : Callable
28
+ State transition function (dynamics).
29
+ g : Callable
30
+ Measurement/output function.
31
+ dist_X : rv_continuous
32
+ Probability distribution for initial states.
33
+ dist_dyn : rv_continuous
34
+ Probability distribution for dynamics noise.
35
+ dist_obs : rv_continuous
36
+ Probability distribution for measurement noise.
37
+ discrete_time : bool
38
+ Indicates if the system is in discrete time, if False the system is in continous time.
39
+
40
+ Notes
41
+ -----
42
+ The functions f and g should have the following signatures:
43
+ - f(x: ndarray, w: ndarray) -> ndarray
44
+ - g(x: ndarray, v: ndarray) -> ndarray
45
+ where:
46
+ - x is the state vector
47
+ - w is the process noise
48
+ - v is the measurement noise
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ nx: int,
54
+ ny: int,
55
+ f: Callable[[NDArray[np.float64]], NDArray[np.float64]],
56
+ g: Callable[[NDArray[np.float64]], NDArray[np.float64]],
57
+ dist_X: rv_continuous,
58
+ dist_dyn: rv_continuous,
59
+ dist_obs: rv_continuous,
60
+ discrete_time: bool
61
+ ):
62
+ self.nx = nx
63
+ self.ny = ny
64
+ self.f = f
65
+ self.g = g
66
+ self.dist_X = dist_X
67
+ self.dist_dyn = dist_dyn
68
+ self.dist_obs = dist_obs
69
+ self.discrete_time = discrete_time
70
+
71
+ def dynamics(self, x: NDArray[np.float64], w: NDArray[np.float64]) -> NDArray[np.float64]:
72
+ """
73
+ Apply the state transition function.
74
+
75
+ Parameters
76
+ ----------
77
+ x : np.ndarray
78
+ Current state vector.
79
+ w : np.ndarray
80
+ Process noise vector.
81
+
82
+ Returns
83
+ -------
84
+ np.ndarray
85
+ Next state vector.
86
+ """
87
+ return self.f(x) + w
88
+
89
+ def measurements(self, x: NDArray[np.float64], v: NDArray[np.float64]) -> NDArray[np.float64]:
90
+ """
91
+ Apply the measurement function.
92
+
93
+ Parameters
94
+ ----------
95
+ x : np.ndarray
96
+ Current state vector.
97
+ v : np.ndarray
98
+ Measurement noise vector.
99
+
100
+ Returns
101
+ -------
102
+ np.ndarray
103
+ Measurement/output vector.
104
+ """
105
+ return self.g(x) + v
106
+
107
+ def sample_state(self, size: int = 1) -> NDArray[np.float64]:
108
+ """
109
+ Sample from the state distribution.
110
+
111
+ Parameters
112
+ ----------
113
+ size : int, optional
114
+ Number of samples to draw. Default is 1.
115
+
116
+ Returns
117
+ -------
118
+ np.ndarray
119
+ Sampled state(s).
120
+ """
121
+ return self.dist_X.rvs(size=size).reshape((size, self.nx))
122
+
123
+ def create_additive_system(
124
+ nx: int,
125
+ ny: int,
126
+ f: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
127
+ g: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
128
+ dist_X: rv_continuous,
129
+ dist_dyn: rv_continuous,
130
+ dist_obs: rv_continuous,
131
+ N_samples: int,
132
+ discrete_time: bool = True
133
+ ) -> DynamicalSystem:
134
+ """
135
+ Create an additive dynamical system where noise is added to the state and observation functions.
136
+
137
+ Parameters
138
+ ----------
139
+ nx : int
140
+ Dimension of the state space.
141
+ ny : int
142
+ Dimension of the measurement space.
143
+ f : Callable
144
+ State transition function (with noise).
145
+ g : Callable
146
+ Measurement function (with noise).
147
+ dist_X : rv_continuous
148
+ Initial state distribution.
149
+ dist_dyn : rv_continuous
150
+ Dynamics noise distribution.
151
+ dist_obs : rv_continuous
152
+ Measurement noise distribution.
153
+ N_samples: int
154
+ Number of samples to generate empirical mean of dynamics and observation.
155
+ discrete_time : bool, optional
156
+ Indicates if the system is in discrete time. Default is True.
157
+
158
+ Returns
159
+ -------
160
+ DynamicalSystem
161
+ New dynamical system instance with additive noise.
162
+
163
+ Notes
164
+ -----
165
+ The resulting system has the form:
166
+ x[k+1] = f(x[k]) + w[k]
167
+ y[k] = g(x[k]) + v[k]
168
+ where w and v are noise terms.
169
+ """
170
+ new_f = lambda x: np.mean([f(x.reshape((len(x),1)), dist_dyn.rvs(N_samples))], axis=1)
171
+ new_g = lambda x: np.mean([g(x.reshape((len(x),1)), dist_obs.rvs(N_samples))], axis=1)
172
+ class DynDist:
173
+ def __init__(self):
174
+ pass
175
+
176
+ def rvs(self, x, size=1):
177
+ return f(x, dist_dyn.rvs(size=size)) - new_f(x)
178
+
179
+ class ObsDist:
180
+ def __init__(self):
181
+ pass
182
+
183
+ def rvs(self, x, size=1):
184
+ return g(x, dist_obs.rvs(size=size)) - new_g(x)
185
+ return DynamicalSystem(nx, ny, new_f, new_g, dist_X, DynDist, ObsDist, discrete_time=discrete_time)