nrl-tracker 1.9.1__py3-none-any.whl → 1.10.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.
- {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/METADATA +49 -4
- {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/RECORD +68 -60
- pytcl/__init__.py +2 -2
- pytcl/assignment_algorithms/gating.py +18 -0
- pytcl/assignment_algorithms/jpda.py +56 -0
- pytcl/assignment_algorithms/nd_assignment.py +65 -0
- pytcl/assignment_algorithms/network_flow.py +40 -0
- pytcl/astronomical/ephemerides.py +18 -0
- pytcl/astronomical/orbital_mechanics.py +131 -0
- pytcl/atmosphere/ionosphere.py +44 -0
- pytcl/atmosphere/models.py +29 -0
- pytcl/clustering/dbscan.py +9 -0
- pytcl/clustering/gaussian_mixture.py +20 -0
- pytcl/clustering/hierarchical.py +29 -0
- pytcl/clustering/kmeans.py +9 -0
- pytcl/coordinate_systems/conversions/geodetic.py +46 -0
- pytcl/coordinate_systems/conversions/spherical.py +35 -0
- pytcl/coordinate_systems/rotations/rotations.py +147 -0
- pytcl/core/__init__.py +16 -0
- pytcl/core/maturity.py +346 -0
- pytcl/core/optional_deps.py +20 -0
- pytcl/dynamic_estimation/gaussian_sum_filter.py +55 -0
- pytcl/dynamic_estimation/imm.py +29 -0
- pytcl/dynamic_estimation/information_filter.py +64 -0
- pytcl/dynamic_estimation/kalman/extended.py +56 -0
- pytcl/dynamic_estimation/kalman/linear.py +69 -0
- pytcl/dynamic_estimation/kalman/unscented.py +81 -0
- pytcl/dynamic_estimation/particle_filters/bootstrap.py +146 -0
- pytcl/dynamic_estimation/rbpf.py +51 -0
- pytcl/dynamic_estimation/smoothers.py +58 -0
- pytcl/dynamic_models/continuous_time/dynamics.py +104 -0
- pytcl/dynamic_models/discrete_time/coordinated_turn.py +6 -0
- pytcl/dynamic_models/discrete_time/singer.py +12 -0
- pytcl/dynamic_models/process_noise/coordinated_turn.py +46 -0
- pytcl/dynamic_models/process_noise/polynomial.py +6 -0
- pytcl/dynamic_models/process_noise/singer.py +52 -0
- pytcl/gpu/__init__.py +153 -0
- pytcl/gpu/ekf.py +425 -0
- pytcl/gpu/kalman.py +543 -0
- pytcl/gpu/matrix_utils.py +486 -0
- pytcl/gpu/particle_filter.py +568 -0
- pytcl/gpu/ukf.py +476 -0
- pytcl/gpu/utils.py +582 -0
- pytcl/gravity/clenshaw.py +60 -0
- pytcl/gravity/egm.py +47 -0
- pytcl/gravity/models.py +34 -0
- pytcl/gravity/spherical_harmonics.py +73 -0
- pytcl/gravity/tides.py +34 -0
- pytcl/mathematical_functions/numerical_integration/quadrature.py +85 -0
- pytcl/mathematical_functions/special_functions/bessel.py +55 -0
- pytcl/mathematical_functions/special_functions/elliptic.py +42 -0
- pytcl/mathematical_functions/special_functions/error_functions.py +49 -0
- pytcl/mathematical_functions/special_functions/gamma_functions.py +43 -0
- pytcl/mathematical_functions/special_functions/lambert_w.py +5 -0
- pytcl/mathematical_functions/special_functions/marcum_q.py +16 -0
- pytcl/navigation/geodesy.py +101 -2
- pytcl/navigation/great_circle.py +71 -0
- pytcl/navigation/rhumb.py +74 -0
- pytcl/performance_evaluation/estimation_metrics.py +70 -0
- pytcl/performance_evaluation/track_metrics.py +30 -0
- pytcl/static_estimation/maximum_likelihood.py +54 -0
- pytcl/static_estimation/robust.py +57 -0
- pytcl/terrain/dem.py +69 -0
- pytcl/terrain/visibility.py +65 -0
- pytcl/trackers/hypothesis.py +65 -0
- {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/LICENSE +0 -0
- {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/WHEEL +0 -0
- {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/top_level.txt +0 -0
pytcl/gpu/kalman.py
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GPU-accelerated Linear Kalman Filter using CuPy.
|
|
3
|
+
|
|
4
|
+
This module provides GPU-accelerated implementations of the linear Kalman filter
|
|
5
|
+
for batch processing of multiple tracks. The implementations achieve 5-10x
|
|
6
|
+
speedup compared to CPU for batch sizes > 100.
|
|
7
|
+
|
|
8
|
+
Key Features
|
|
9
|
+
------------
|
|
10
|
+
- Batch processing of multiple tracks in parallel
|
|
11
|
+
- Memory-efficient operations using CuPy's memory pool
|
|
12
|
+
- Compatible API with CPU implementations
|
|
13
|
+
- Automatic fallback to CPU if GPU unavailable
|
|
14
|
+
|
|
15
|
+
Examples
|
|
16
|
+
--------
|
|
17
|
+
Batch predict for 1000 tracks:
|
|
18
|
+
|
|
19
|
+
>>> from pytcl.gpu.kalman import batch_kf_predict
|
|
20
|
+
>>> import numpy as np
|
|
21
|
+
>>> n_tracks = 1000
|
|
22
|
+
>>> state_dim = 4
|
|
23
|
+
>>> x = np.random.randn(n_tracks, state_dim)
|
|
24
|
+
>>> P = np.tile(np.eye(state_dim), (n_tracks, 1, 1))
|
|
25
|
+
>>> F = np.array([[1, 1, 0, 0], [0, 1, 0, 0], [0, 0, 1, 1], [0, 0, 0, 1]])
|
|
26
|
+
>>> Q = np.eye(state_dim) * 0.1
|
|
27
|
+
>>> x_pred, P_pred = batch_kf_predict(x, P, F, Q)
|
|
28
|
+
|
|
29
|
+
See Also
|
|
30
|
+
--------
|
|
31
|
+
pytcl.dynamic_estimation.kalman.linear : CPU Kalman filter
|
|
32
|
+
pytcl.gpu.ekf : GPU Extended Kalman filter
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from typing import NamedTuple, Optional, Tuple
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
from numpy.typing import ArrayLike, NDArray
|
|
39
|
+
|
|
40
|
+
from pytcl.core.optional_deps import import_optional, requires
|
|
41
|
+
from pytcl.gpu.utils import ensure_gpu_array
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BatchKalmanPrediction(NamedTuple):
|
|
45
|
+
"""Result of batch Kalman filter prediction.
|
|
46
|
+
|
|
47
|
+
Attributes
|
|
48
|
+
----------
|
|
49
|
+
x : ndarray
|
|
50
|
+
Predicted state estimates, shape (n_tracks, state_dim).
|
|
51
|
+
P : ndarray
|
|
52
|
+
Predicted covariances, shape (n_tracks, state_dim, state_dim).
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
x: NDArray[np.floating]
|
|
56
|
+
P: NDArray[np.floating]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class BatchKalmanUpdate(NamedTuple):
|
|
60
|
+
"""Result of batch Kalman filter update.
|
|
61
|
+
|
|
62
|
+
Attributes
|
|
63
|
+
----------
|
|
64
|
+
x : ndarray
|
|
65
|
+
Updated state estimates, shape (n_tracks, state_dim).
|
|
66
|
+
P : ndarray
|
|
67
|
+
Updated covariances, shape (n_tracks, state_dim, state_dim).
|
|
68
|
+
y : ndarray
|
|
69
|
+
Innovations, shape (n_tracks, meas_dim).
|
|
70
|
+
S : ndarray
|
|
71
|
+
Innovation covariances, shape (n_tracks, meas_dim, meas_dim).
|
|
72
|
+
K : ndarray
|
|
73
|
+
Kalman gains, shape (n_tracks, state_dim, meas_dim).
|
|
74
|
+
likelihood : ndarray
|
|
75
|
+
Measurement likelihoods, shape (n_tracks,).
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
x: NDArray[np.floating]
|
|
79
|
+
P: NDArray[np.floating]
|
|
80
|
+
y: NDArray[np.floating]
|
|
81
|
+
S: NDArray[np.floating]
|
|
82
|
+
K: NDArray[np.floating]
|
|
83
|
+
likelihood: NDArray[np.floating]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@requires("cupy", extra="gpu", feature="GPU Kalman filter")
|
|
87
|
+
def batch_kf_predict(
|
|
88
|
+
x: ArrayLike,
|
|
89
|
+
P: ArrayLike,
|
|
90
|
+
F: ArrayLike,
|
|
91
|
+
Q: ArrayLike,
|
|
92
|
+
B: Optional[ArrayLike] = None,
|
|
93
|
+
u: Optional[ArrayLike] = None,
|
|
94
|
+
) -> BatchKalmanPrediction:
|
|
95
|
+
"""
|
|
96
|
+
Batch Kalman filter prediction for multiple tracks.
|
|
97
|
+
|
|
98
|
+
Performs the prediction step for N tracks in parallel on GPU:
|
|
99
|
+
x_pred[i] = F @ x[i] + B @ u[i] (if B, u provided)
|
|
100
|
+
P_pred[i] = F @ P[i] @ F' + Q
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
x : array_like
|
|
105
|
+
Current state estimates, shape (n_tracks, state_dim).
|
|
106
|
+
P : array_like
|
|
107
|
+
Current covariances, shape (n_tracks, state_dim, state_dim).
|
|
108
|
+
F : array_like
|
|
109
|
+
State transition matrix, shape (state_dim, state_dim).
|
|
110
|
+
Can also be (n_tracks, state_dim, state_dim) for track-specific matrices.
|
|
111
|
+
Q : array_like
|
|
112
|
+
Process noise covariance, shape (state_dim, state_dim).
|
|
113
|
+
Can also be (n_tracks, state_dim, state_dim) for track-specific noise.
|
|
114
|
+
B : array_like, optional
|
|
115
|
+
Control input matrix, shape (state_dim, control_dim).
|
|
116
|
+
u : array_like, optional
|
|
117
|
+
Control inputs, shape (n_tracks, control_dim).
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
result : BatchKalmanPrediction
|
|
122
|
+
Named tuple with predicted states and covariances.
|
|
123
|
+
|
|
124
|
+
Examples
|
|
125
|
+
--------
|
|
126
|
+
>>> import numpy as np
|
|
127
|
+
>>> from pytcl.gpu.kalman import batch_kf_predict
|
|
128
|
+
>>> n_tracks = 100
|
|
129
|
+
>>> x = np.random.randn(n_tracks, 4)
|
|
130
|
+
>>> P = np.tile(np.eye(4) * 0.1, (n_tracks, 1, 1))
|
|
131
|
+
>>> F = np.array([[1, 1, 0, 0], [0, 1, 0, 0],
|
|
132
|
+
... [0, 0, 1, 1], [0, 0, 0, 1]])
|
|
133
|
+
>>> Q = np.eye(4) * 0.01
|
|
134
|
+
>>> pred = batch_kf_predict(x, P, F, Q)
|
|
135
|
+
>>> pred.x.shape
|
|
136
|
+
(100, 4)
|
|
137
|
+
"""
|
|
138
|
+
cp = import_optional("cupy", extra="gpu", feature="GPU Kalman filter")
|
|
139
|
+
|
|
140
|
+
# Move arrays to GPU
|
|
141
|
+
x_gpu = ensure_gpu_array(x, dtype=cp.float64)
|
|
142
|
+
P_gpu = ensure_gpu_array(P, dtype=cp.float64)
|
|
143
|
+
F_gpu = ensure_gpu_array(F, dtype=cp.float64)
|
|
144
|
+
Q_gpu = ensure_gpu_array(Q, dtype=cp.float64)
|
|
145
|
+
|
|
146
|
+
n_tracks = x_gpu.shape[0]
|
|
147
|
+
state_dim = x_gpu.shape[1]
|
|
148
|
+
|
|
149
|
+
# Handle F matrix dimensions
|
|
150
|
+
if F_gpu.ndim == 2:
|
|
151
|
+
# Broadcast F to all tracks: (n, n) -> (n_tracks, n, n)
|
|
152
|
+
F_batch = cp.broadcast_to(F_gpu, (n_tracks, state_dim, state_dim))
|
|
153
|
+
else:
|
|
154
|
+
F_batch = F_gpu
|
|
155
|
+
|
|
156
|
+
# Handle Q matrix dimensions
|
|
157
|
+
if Q_gpu.ndim == 2:
|
|
158
|
+
Q_batch = cp.broadcast_to(Q_gpu, (n_tracks, state_dim, state_dim))
|
|
159
|
+
else:
|
|
160
|
+
Q_batch = Q_gpu
|
|
161
|
+
|
|
162
|
+
# Batch prediction: x_pred = F @ x
|
|
163
|
+
# Use einsum for batched matrix-vector multiplication
|
|
164
|
+
x_pred = cp.einsum("nij,nj->ni", F_batch, x_gpu)
|
|
165
|
+
|
|
166
|
+
# Add control input if provided
|
|
167
|
+
if B is not None and u is not None:
|
|
168
|
+
B_gpu = ensure_gpu_array(B, dtype=cp.float64)
|
|
169
|
+
u_gpu = ensure_gpu_array(u, dtype=cp.float64)
|
|
170
|
+
if B_gpu.ndim == 2:
|
|
171
|
+
# Broadcast B
|
|
172
|
+
x_pred += cp.einsum("ij,nj->ni", B_gpu, u_gpu)
|
|
173
|
+
else:
|
|
174
|
+
x_pred += cp.einsum("nij,nj->ni", B_gpu, u_gpu)
|
|
175
|
+
|
|
176
|
+
# Batch covariance prediction: P_pred = F @ P @ F' + Q
|
|
177
|
+
# Step 1: FP = F @ P
|
|
178
|
+
FP = cp.einsum("nij,njk->nik", F_batch, P_gpu)
|
|
179
|
+
# Step 2: P_pred = FP @ F' + Q
|
|
180
|
+
P_pred = cp.einsum("nij,nkj->nik", FP, F_batch) + Q_batch
|
|
181
|
+
|
|
182
|
+
# Ensure symmetry
|
|
183
|
+
P_pred = (P_pred + cp.swapaxes(P_pred, -2, -1)) / 2
|
|
184
|
+
|
|
185
|
+
return BatchKalmanPrediction(x=x_pred, P=P_pred)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@requires("cupy", extra="gpu", feature="GPU Kalman filter")
|
|
189
|
+
def batch_kf_update(
|
|
190
|
+
x: ArrayLike,
|
|
191
|
+
P: ArrayLike,
|
|
192
|
+
z: ArrayLike,
|
|
193
|
+
H: ArrayLike,
|
|
194
|
+
R: ArrayLike,
|
|
195
|
+
) -> BatchKalmanUpdate:
|
|
196
|
+
"""
|
|
197
|
+
Batch Kalman filter update for multiple tracks.
|
|
198
|
+
|
|
199
|
+
Performs the update step for N tracks in parallel on GPU:
|
|
200
|
+
y[i] = z[i] - H @ x[i] (innovation)
|
|
201
|
+
S[i] = H @ P[i] @ H' + R (innovation covariance)
|
|
202
|
+
K[i] = P[i] @ H' @ S[i]^{-1} (Kalman gain)
|
|
203
|
+
x_upd[i] = x[i] + K[i] @ y[i] (updated state)
|
|
204
|
+
P_upd[i] = (I - K[i] @ H) @ P[i] (updated covariance)
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
x : array_like
|
|
209
|
+
Predicted state estimates, shape (n_tracks, state_dim).
|
|
210
|
+
P : array_like
|
|
211
|
+
Predicted covariances, shape (n_tracks, state_dim, state_dim).
|
|
212
|
+
z : array_like
|
|
213
|
+
Measurements, shape (n_tracks, meas_dim).
|
|
214
|
+
H : array_like
|
|
215
|
+
Measurement matrix, shape (meas_dim, state_dim).
|
|
216
|
+
Can also be (n_tracks, meas_dim, state_dim).
|
|
217
|
+
R : array_like
|
|
218
|
+
Measurement noise covariance, shape (meas_dim, meas_dim).
|
|
219
|
+
Can also be (n_tracks, meas_dim, meas_dim).
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
result : BatchKalmanUpdate
|
|
224
|
+
Named tuple with updated states, covariances, and statistics.
|
|
225
|
+
|
|
226
|
+
Examples
|
|
227
|
+
--------
|
|
228
|
+
>>> import numpy as np
|
|
229
|
+
>>> from pytcl.gpu.kalman import batch_kf_update
|
|
230
|
+
>>> n_tracks = 100
|
|
231
|
+
>>> x = np.random.randn(n_tracks, 4)
|
|
232
|
+
>>> P = np.tile(np.eye(4) * 0.1, (n_tracks, 1, 1))
|
|
233
|
+
>>> z = np.random.randn(n_tracks, 2) # position measurements
|
|
234
|
+
>>> H = np.array([[1, 0, 0, 0], [0, 0, 1, 0]])
|
|
235
|
+
>>> R = np.eye(2) * 0.5
|
|
236
|
+
>>> upd = batch_kf_update(x, P, z, H, R)
|
|
237
|
+
>>> upd.x.shape
|
|
238
|
+
(100, 4)
|
|
239
|
+
"""
|
|
240
|
+
cp = import_optional("cupy", extra="gpu", feature="GPU Kalman filter")
|
|
241
|
+
|
|
242
|
+
# Move arrays to GPU
|
|
243
|
+
x_gpu = ensure_gpu_array(x, dtype=cp.float64)
|
|
244
|
+
P_gpu = ensure_gpu_array(P, dtype=cp.float64)
|
|
245
|
+
z_gpu = ensure_gpu_array(z, dtype=cp.float64)
|
|
246
|
+
H_gpu = ensure_gpu_array(H, dtype=cp.float64)
|
|
247
|
+
R_gpu = ensure_gpu_array(R, dtype=cp.float64)
|
|
248
|
+
|
|
249
|
+
n_tracks = x_gpu.shape[0]
|
|
250
|
+
state_dim = x_gpu.shape[1]
|
|
251
|
+
meas_dim = z_gpu.shape[1]
|
|
252
|
+
|
|
253
|
+
# Handle H matrix dimensions
|
|
254
|
+
if H_gpu.ndim == 2:
|
|
255
|
+
H_batch = cp.broadcast_to(H_gpu, (n_tracks, meas_dim, state_dim))
|
|
256
|
+
else:
|
|
257
|
+
H_batch = H_gpu
|
|
258
|
+
|
|
259
|
+
# Handle R matrix dimensions
|
|
260
|
+
if R_gpu.ndim == 2:
|
|
261
|
+
R_batch = cp.broadcast_to(R_gpu, (n_tracks, meas_dim, meas_dim))
|
|
262
|
+
else:
|
|
263
|
+
R_batch = R_gpu
|
|
264
|
+
|
|
265
|
+
# Innovation: y = z - H @ x
|
|
266
|
+
z_pred = cp.einsum("nij,nj->ni", H_batch, x_gpu)
|
|
267
|
+
y = z_gpu - z_pred
|
|
268
|
+
|
|
269
|
+
# Innovation covariance: S = H @ P @ H' + R
|
|
270
|
+
HP = cp.einsum("nij,njk->nik", H_batch, P_gpu)
|
|
271
|
+
S = cp.einsum("nij,nkj->nik", HP, H_batch) + R_batch
|
|
272
|
+
|
|
273
|
+
# Kalman gain: K = P @ H' @ S^{-1}
|
|
274
|
+
# First compute P @ H'
|
|
275
|
+
PHT = cp.einsum("nij,nkj->nik", P_gpu, H_batch)
|
|
276
|
+
|
|
277
|
+
# Batch matrix inverse using batched solve
|
|
278
|
+
# K = PHT @ S^{-1} is equivalent to solving S @ K' = PHT' for K
|
|
279
|
+
# But for efficiency, we solve S @ X = I for S^{-1}, then compute K = PHT @ S^{-1}
|
|
280
|
+
S_inv = cp.linalg.inv(S)
|
|
281
|
+
K = cp.einsum("nij,njk->nik", PHT, S_inv)
|
|
282
|
+
|
|
283
|
+
# Updated state: x_upd = x + K @ y
|
|
284
|
+
x_upd = x_gpu + cp.einsum("nij,nj->ni", K, y)
|
|
285
|
+
|
|
286
|
+
# Updated covariance using Joseph form: P_upd = (I - K @ H) @ P @ (I - K @ H)' + K @ R @ K'
|
|
287
|
+
eye = cp.eye(state_dim, dtype=cp.float64)
|
|
288
|
+
I_KH = eye - cp.einsum("nij,njk->nik", K, H_batch)
|
|
289
|
+
|
|
290
|
+
# Joseph form for numerical stability
|
|
291
|
+
P_upd = cp.einsum("nij,njk->nik", I_KH, P_gpu)
|
|
292
|
+
P_upd = cp.einsum("nij,nkj->nik", P_upd, I_KH)
|
|
293
|
+
KRK = cp.einsum("nij,njk,nlk->nil", K, R_batch, K)
|
|
294
|
+
P_upd = P_upd + KRK
|
|
295
|
+
|
|
296
|
+
# Ensure symmetry
|
|
297
|
+
P_upd = (P_upd + cp.swapaxes(P_upd, -2, -1)) / 2
|
|
298
|
+
|
|
299
|
+
# Compute likelihoods
|
|
300
|
+
# log(L) = -0.5 * (y' @ S^{-1} @ y + log(det(S)) + m*log(2*pi))
|
|
301
|
+
mahal_sq = cp.einsum("ni,nij,nj->n", y, S_inv, y)
|
|
302
|
+
sign, logdet = cp.linalg.slogdet(S)
|
|
303
|
+
log_likelihood = -0.5 * (mahal_sq + logdet + meas_dim * np.log(2 * np.pi))
|
|
304
|
+
likelihood = cp.exp(log_likelihood)
|
|
305
|
+
|
|
306
|
+
return BatchKalmanUpdate(
|
|
307
|
+
x=x_upd,
|
|
308
|
+
P=P_upd,
|
|
309
|
+
y=y,
|
|
310
|
+
S=S,
|
|
311
|
+
K=K,
|
|
312
|
+
likelihood=likelihood,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@requires("cupy", extra="gpu", feature="GPU Kalman filter")
|
|
317
|
+
def batch_kf_predict_update(
|
|
318
|
+
x: ArrayLike,
|
|
319
|
+
P: ArrayLike,
|
|
320
|
+
z: ArrayLike,
|
|
321
|
+
F: ArrayLike,
|
|
322
|
+
Q: ArrayLike,
|
|
323
|
+
H: ArrayLike,
|
|
324
|
+
R: ArrayLike,
|
|
325
|
+
B: Optional[ArrayLike] = None,
|
|
326
|
+
u: Optional[ArrayLike] = None,
|
|
327
|
+
) -> BatchKalmanUpdate:
|
|
328
|
+
"""
|
|
329
|
+
Combined batch Kalman filter prediction and update.
|
|
330
|
+
|
|
331
|
+
Parameters
|
|
332
|
+
----------
|
|
333
|
+
x : array_like
|
|
334
|
+
Current state estimates, shape (n_tracks, state_dim).
|
|
335
|
+
P : array_like
|
|
336
|
+
Current covariances, shape (n_tracks, state_dim, state_dim).
|
|
337
|
+
z : array_like
|
|
338
|
+
Measurements, shape (n_tracks, meas_dim).
|
|
339
|
+
F : array_like
|
|
340
|
+
State transition matrix.
|
|
341
|
+
Q : array_like
|
|
342
|
+
Process noise covariance.
|
|
343
|
+
H : array_like
|
|
344
|
+
Measurement matrix.
|
|
345
|
+
R : array_like
|
|
346
|
+
Measurement noise covariance.
|
|
347
|
+
B : array_like, optional
|
|
348
|
+
Control input matrix.
|
|
349
|
+
u : array_like, optional
|
|
350
|
+
Control inputs.
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
result : BatchKalmanUpdate
|
|
355
|
+
Named tuple with updated states, covariances, and statistics.
|
|
356
|
+
"""
|
|
357
|
+
pred = batch_kf_predict(x, P, F, Q, B, u)
|
|
358
|
+
return batch_kf_update(pred.x, pred.P, z, H, R)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class CuPyKalmanFilter:
|
|
362
|
+
"""
|
|
363
|
+
GPU-accelerated Linear Kalman Filter for batch processing.
|
|
364
|
+
|
|
365
|
+
This class provides a stateful interface for processing multiple tracks
|
|
366
|
+
in parallel on the GPU. It maintains the filter matrices and provides
|
|
367
|
+
methods for prediction and update.
|
|
368
|
+
|
|
369
|
+
Parameters
|
|
370
|
+
----------
|
|
371
|
+
state_dim : int
|
|
372
|
+
Dimension of the state vector.
|
|
373
|
+
meas_dim : int
|
|
374
|
+
Dimension of the measurement vector.
|
|
375
|
+
F : array_like, optional
|
|
376
|
+
State transition matrix. If None, uses identity.
|
|
377
|
+
H : array_like, optional
|
|
378
|
+
Measurement matrix. If None, measures first meas_dim states.
|
|
379
|
+
Q : array_like, optional
|
|
380
|
+
Process noise covariance. If None, uses 0.01 * I.
|
|
381
|
+
R : array_like, optional
|
|
382
|
+
Measurement noise covariance. If None, uses 1.0 * I.
|
|
383
|
+
|
|
384
|
+
Examples
|
|
385
|
+
--------
|
|
386
|
+
>>> import numpy as np
|
|
387
|
+
>>> from pytcl.gpu.kalman import CuPyKalmanFilter
|
|
388
|
+
>>>
|
|
389
|
+
>>> # Create filter for 2D constant velocity model
|
|
390
|
+
>>> kf = CuPyKalmanFilter(
|
|
391
|
+
... state_dim=4, # [x, vx, y, vy]
|
|
392
|
+
... meas_dim=2, # [x, y]
|
|
393
|
+
... F=np.array([[1, 1, 0, 0], [0, 1, 0, 0],
|
|
394
|
+
... [0, 0, 1, 1], [0, 0, 0, 1]]),
|
|
395
|
+
... H=np.array([[1, 0, 0, 0], [0, 0, 1, 0]]),
|
|
396
|
+
... Q=np.eye(4) * 0.1,
|
|
397
|
+
... R=np.eye(2) * 1.0,
|
|
398
|
+
... )
|
|
399
|
+
>>>
|
|
400
|
+
>>> # Process batch of tracks
|
|
401
|
+
>>> n_tracks = 1000
|
|
402
|
+
>>> x = np.random.randn(n_tracks, 4)
|
|
403
|
+
>>> P = np.tile(np.eye(4), (n_tracks, 1, 1))
|
|
404
|
+
>>> z = np.random.randn(n_tracks, 2)
|
|
405
|
+
>>>
|
|
406
|
+
>>> # Predict and update
|
|
407
|
+
>>> x_pred, P_pred = kf.predict(x, P)
|
|
408
|
+
>>> result = kf.update(x_pred, P_pred, z)
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
@requires("cupy", extra="gpu", feature="GPU Kalman filter")
|
|
412
|
+
def __init__(
|
|
413
|
+
self,
|
|
414
|
+
state_dim: int,
|
|
415
|
+
meas_dim: int,
|
|
416
|
+
F: Optional[ArrayLike] = None,
|
|
417
|
+
H: Optional[ArrayLike] = None,
|
|
418
|
+
Q: Optional[ArrayLike] = None,
|
|
419
|
+
R: Optional[ArrayLike] = None,
|
|
420
|
+
):
|
|
421
|
+
cp = import_optional("cupy", extra="gpu", feature="GPU Kalman filter")
|
|
422
|
+
|
|
423
|
+
self.state_dim = state_dim
|
|
424
|
+
self.meas_dim = meas_dim
|
|
425
|
+
|
|
426
|
+
# Initialize matrices on GPU
|
|
427
|
+
if F is None:
|
|
428
|
+
self.F = cp.eye(state_dim, dtype=cp.float64)
|
|
429
|
+
else:
|
|
430
|
+
self.F = ensure_gpu_array(F, dtype=cp.float64)
|
|
431
|
+
|
|
432
|
+
if H is None:
|
|
433
|
+
self.H = cp.zeros((meas_dim, state_dim), dtype=cp.float64)
|
|
434
|
+
self.H[:meas_dim, :meas_dim] = cp.eye(meas_dim, dtype=cp.float64)
|
|
435
|
+
else:
|
|
436
|
+
self.H = ensure_gpu_array(H, dtype=cp.float64)
|
|
437
|
+
|
|
438
|
+
if Q is None:
|
|
439
|
+
self.Q = cp.eye(state_dim, dtype=cp.float64) * 0.01
|
|
440
|
+
else:
|
|
441
|
+
self.Q = ensure_gpu_array(Q, dtype=cp.float64)
|
|
442
|
+
|
|
443
|
+
if R is None:
|
|
444
|
+
self.R = cp.eye(meas_dim, dtype=cp.float64)
|
|
445
|
+
else:
|
|
446
|
+
self.R = ensure_gpu_array(R, dtype=cp.float64)
|
|
447
|
+
|
|
448
|
+
def predict(
|
|
449
|
+
self,
|
|
450
|
+
x: ArrayLike,
|
|
451
|
+
P: ArrayLike,
|
|
452
|
+
B: Optional[ArrayLike] = None,
|
|
453
|
+
u: Optional[ArrayLike] = None,
|
|
454
|
+
) -> Tuple[NDArray[np.floating], NDArray[np.floating]]:
|
|
455
|
+
"""
|
|
456
|
+
Perform batch prediction.
|
|
457
|
+
|
|
458
|
+
Parameters
|
|
459
|
+
----------
|
|
460
|
+
x : array_like
|
|
461
|
+
State estimates, shape (n_tracks, state_dim).
|
|
462
|
+
P : array_like
|
|
463
|
+
Covariances, shape (n_tracks, state_dim, state_dim).
|
|
464
|
+
B : array_like, optional
|
|
465
|
+
Control input matrix.
|
|
466
|
+
u : array_like, optional
|
|
467
|
+
Control inputs.
|
|
468
|
+
|
|
469
|
+
Returns
|
|
470
|
+
-------
|
|
471
|
+
x_pred : ndarray
|
|
472
|
+
Predicted states.
|
|
473
|
+
P_pred : ndarray
|
|
474
|
+
Predicted covariances.
|
|
475
|
+
"""
|
|
476
|
+
result = batch_kf_predict(x, P, self.F, self.Q, B, u)
|
|
477
|
+
return result.x, result.P
|
|
478
|
+
|
|
479
|
+
def update(
|
|
480
|
+
self,
|
|
481
|
+
x: ArrayLike,
|
|
482
|
+
P: ArrayLike,
|
|
483
|
+
z: ArrayLike,
|
|
484
|
+
) -> BatchKalmanUpdate:
|
|
485
|
+
"""
|
|
486
|
+
Perform batch update.
|
|
487
|
+
|
|
488
|
+
Parameters
|
|
489
|
+
----------
|
|
490
|
+
x : array_like
|
|
491
|
+
Predicted state estimates.
|
|
492
|
+
P : array_like
|
|
493
|
+
Predicted covariances.
|
|
494
|
+
z : array_like
|
|
495
|
+
Measurements.
|
|
496
|
+
|
|
497
|
+
Returns
|
|
498
|
+
-------
|
|
499
|
+
result : BatchKalmanUpdate
|
|
500
|
+
Update results including states, covariances, and statistics.
|
|
501
|
+
"""
|
|
502
|
+
return batch_kf_update(x, P, z, self.H, self.R)
|
|
503
|
+
|
|
504
|
+
def predict_update(
|
|
505
|
+
self,
|
|
506
|
+
x: ArrayLike,
|
|
507
|
+
P: ArrayLike,
|
|
508
|
+
z: ArrayLike,
|
|
509
|
+
B: Optional[ArrayLike] = None,
|
|
510
|
+
u: Optional[ArrayLike] = None,
|
|
511
|
+
) -> BatchKalmanUpdate:
|
|
512
|
+
"""
|
|
513
|
+
Combined batch prediction and update.
|
|
514
|
+
|
|
515
|
+
Parameters
|
|
516
|
+
----------
|
|
517
|
+
x : array_like
|
|
518
|
+
Current state estimates.
|
|
519
|
+
P : array_like
|
|
520
|
+
Current covariances.
|
|
521
|
+
z : array_like
|
|
522
|
+
Measurements.
|
|
523
|
+
B : array_like, optional
|
|
524
|
+
Control input matrix.
|
|
525
|
+
u : array_like, optional
|
|
526
|
+
Control inputs.
|
|
527
|
+
|
|
528
|
+
Returns
|
|
529
|
+
-------
|
|
530
|
+
result : BatchKalmanUpdate
|
|
531
|
+
Update results.
|
|
532
|
+
"""
|
|
533
|
+
return batch_kf_predict_update(x, P, z, self.F, self.Q, self.H, self.R, B, u)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
__all__ = [
|
|
537
|
+
"BatchKalmanPrediction",
|
|
538
|
+
"BatchKalmanUpdate",
|
|
539
|
+
"batch_kf_predict",
|
|
540
|
+
"batch_kf_update",
|
|
541
|
+
"batch_kf_predict_update",
|
|
542
|
+
"CuPyKalmanFilter",
|
|
543
|
+
]
|