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 +21 -0
- kkf-0.11/PKG-INFO +200 -0
- kkf-0.11/README.md +186 -0
- kkf-0.11/kkf/DynamicalSystems.py +185 -0
- kkf-0.11/kkf/KKFsol.py +145 -0
- kkf-0.11/kkf/__init__.py +5 -0
- kkf-0.11/kkf/applyKKF.py +228 -0
- kkf-0.11/kkf/covariances.py +162 -0
- kkf-0.11/kkf/kEDMD.py +147 -0
- kkf-0.11/kkf/utils.py +0 -0
- kkf-0.11/kkf.egg-info/PKG-INFO +200 -0
- kkf-0.11/kkf.egg-info/SOURCES.txt +17 -0
- kkf-0.11/kkf.egg-info/dependency_links.txt +1 -0
- kkf-0.11/kkf.egg-info/requires.txt +3 -0
- kkf-0.11/kkf.egg-info/top_level.txt +1 -0
- kkf-0.11/setup.cfg +4 -0
- kkf-0.11/setup.py +24 -0
- kkf-0.11/tests/test_core.py +0 -0
- kkf-0.11/tests/test_utils.py +0 -0
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)
|