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
|
@@ -363,6 +363,33 @@ def gaussian_sum_filter_predict(
|
|
|
363
363
|
-------
|
|
364
364
|
components_new : list[GaussianComponent]
|
|
365
365
|
Predicted components.
|
|
366
|
+
|
|
367
|
+
Examples
|
|
368
|
+
--------
|
|
369
|
+
>>> import numpy as np
|
|
370
|
+
>>> from pytcl.dynamic_estimation.gaussian_sum_filter import GaussianComponent
|
|
371
|
+
>>> # Two-component mixture for position-velocity state
|
|
372
|
+
>>> comp1 = GaussianComponent(
|
|
373
|
+
... x=np.array([0.0, 1.0]), # moving right
|
|
374
|
+
... P=np.eye(2) * 0.5,
|
|
375
|
+
... w=0.5
|
|
376
|
+
... )
|
|
377
|
+
>>> comp2 = GaussianComponent(
|
|
378
|
+
... x=np.array([0.0, -1.0]), # moving left
|
|
379
|
+
... P=np.eye(2) * 0.5,
|
|
380
|
+
... w=0.5
|
|
381
|
+
... )
|
|
382
|
+
>>> components = [comp1, comp2]
|
|
383
|
+
>>> # Constant velocity dynamics
|
|
384
|
+
>>> dt = 0.1
|
|
385
|
+
>>> f = lambda x: np.array([x[0] + x[1] * dt, x[1]])
|
|
386
|
+
>>> F = np.array([[1, dt], [0, 1]])
|
|
387
|
+
>>> Q = np.eye(2) * 0.01
|
|
388
|
+
>>> predicted = gaussian_sum_filter_predict(components, f, F, Q)
|
|
389
|
+
>>> len(predicted)
|
|
390
|
+
2
|
|
391
|
+
>>> predicted[0].w # weights unchanged in prediction
|
|
392
|
+
0.5
|
|
366
393
|
"""
|
|
367
394
|
F = np.asarray(F, dtype=np.float64)
|
|
368
395
|
Q = np.asarray(Q, dtype=np.float64)
|
|
@@ -401,6 +428,34 @@ def gaussian_sum_filter_update(
|
|
|
401
428
|
-------
|
|
402
429
|
components_new : list[GaussianComponent]
|
|
403
430
|
Updated components with adapted weights.
|
|
431
|
+
|
|
432
|
+
Examples
|
|
433
|
+
--------
|
|
434
|
+
>>> import numpy as np
|
|
435
|
+
>>> from pytcl.dynamic_estimation.gaussian_sum_filter import GaussianComponent
|
|
436
|
+
>>> # Two-component mixture
|
|
437
|
+
>>> comp1 = GaussianComponent(
|
|
438
|
+
... x=np.array([1.0, 0.5]),
|
|
439
|
+
... P=np.eye(2) * 0.5,
|
|
440
|
+
... w=0.5
|
|
441
|
+
... )
|
|
442
|
+
>>> comp2 = GaussianComponent(
|
|
443
|
+
... x=np.array([3.0, 0.5]),
|
|
444
|
+
... P=np.eye(2) * 0.5,
|
|
445
|
+
... w=0.5
|
|
446
|
+
... )
|
|
447
|
+
>>> components = [comp1, comp2]
|
|
448
|
+
>>> # Position measurement near component 1
|
|
449
|
+
>>> z = np.array([1.1])
|
|
450
|
+
>>> h = lambda x: np.array([x[0]])
|
|
451
|
+
>>> H = np.array([[1, 0]])
|
|
452
|
+
>>> R = np.array([[0.1]])
|
|
453
|
+
>>> updated = gaussian_sum_filter_update(components, z, h, H, R)
|
|
454
|
+
>>> len(updated)
|
|
455
|
+
2
|
|
456
|
+
>>> # Component 1 should have higher weight (closer to measurement)
|
|
457
|
+
>>> updated[0].w > updated[1].w
|
|
458
|
+
True
|
|
404
459
|
"""
|
|
405
460
|
z = np.asarray(z, dtype=np.float64)
|
|
406
461
|
H = np.asarray(H, dtype=np.float64)
|
pytcl/dynamic_estimation/imm.py
CHANGED
|
@@ -468,6 +468,35 @@ def imm_predict_update(
|
|
|
468
468
|
-------
|
|
469
469
|
result : IMMUpdate
|
|
470
470
|
Updated states, covariances, and mode probabilities.
|
|
471
|
+
|
|
472
|
+
Examples
|
|
473
|
+
--------
|
|
474
|
+
Track a target with 2 motion modes (CV and CA):
|
|
475
|
+
|
|
476
|
+
>>> import numpy as np
|
|
477
|
+
>>> # Two modes: constant velocity and constant acceleration
|
|
478
|
+
>>> states = [np.array([0, 1, 0, 1]), np.array([0, 1, 0, 1])]
|
|
479
|
+
>>> covs = [np.eye(4) * 0.1, np.eye(4) * 0.1]
|
|
480
|
+
>>> probs = np.array([0.9, 0.1]) # likely CV mode
|
|
481
|
+
>>> # Mode transition matrix (90% stay, 10% switch)
|
|
482
|
+
>>> trans = np.array([[0.9, 0.1], [0.1, 0.9]])
|
|
483
|
+
>>> # Dynamics and measurement matrices for each mode
|
|
484
|
+
>>> F_cv = np.array([[1, 1, 0, 0], [0, 1, 0, 0], [0, 0, 1, 1], [0, 0, 0, 1]])
|
|
485
|
+
>>> F_ca = F_cv.copy() # simplified
|
|
486
|
+
>>> H = np.array([[1, 0, 0, 0], [0, 0, 1, 0]])
|
|
487
|
+
>>> Q = np.eye(4) * 0.01
|
|
488
|
+
>>> R = np.eye(2) * 0.1
|
|
489
|
+
>>> z = np.array([1.0, 1.0])
|
|
490
|
+
>>> result = imm_predict_update(states, covs, probs, trans, z,
|
|
491
|
+
... [F_cv, F_ca], [Q, Q], [H, H], [R, R])
|
|
492
|
+
>>> len(result.mode_probs)
|
|
493
|
+
2
|
|
494
|
+
|
|
495
|
+
See Also
|
|
496
|
+
--------
|
|
497
|
+
imm_predict : IMM prediction step only.
|
|
498
|
+
imm_update : IMM update step only.
|
|
499
|
+
IMMEstimator : Object-oriented interface.
|
|
471
500
|
"""
|
|
472
501
|
pred = imm_predict(
|
|
473
502
|
mode_states, mode_covs, mode_probs, transition_matrix, F_list, Q_list
|
|
@@ -119,6 +119,20 @@ def information_to_state(
|
|
|
119
119
|
State estimate.
|
|
120
120
|
P : ndarray
|
|
121
121
|
State covariance.
|
|
122
|
+
|
|
123
|
+
Examples
|
|
124
|
+
--------
|
|
125
|
+
>>> import numpy as np
|
|
126
|
+
>>> # Information form: Y = inv(P), y = Y @ x
|
|
127
|
+
>>> x_true = np.array([1.0, 2.0])
|
|
128
|
+
>>> P_true = np.eye(2) * 0.5
|
|
129
|
+
>>> Y = np.linalg.inv(P_true) # Information matrix
|
|
130
|
+
>>> y = Y @ x_true # Information vector
|
|
131
|
+
>>> x_recovered, P_recovered = information_to_state(y, Y)
|
|
132
|
+
>>> np.allclose(x_recovered, x_true)
|
|
133
|
+
True
|
|
134
|
+
>>> np.allclose(P_recovered, P_true)
|
|
135
|
+
True
|
|
122
136
|
"""
|
|
123
137
|
y = np.asarray(y, dtype=np.float64).flatten()
|
|
124
138
|
Y = np.asarray(Y, dtype=np.float64)
|
|
@@ -149,6 +163,18 @@ def state_to_information(
|
|
|
149
163
|
Information vector.
|
|
150
164
|
Y : ndarray
|
|
151
165
|
Information matrix.
|
|
166
|
+
|
|
167
|
+
Examples
|
|
168
|
+
--------
|
|
169
|
+
>>> import numpy as np
|
|
170
|
+
>>> x = np.array([1.0, 2.0])
|
|
171
|
+
>>> P = np.eye(2) * 0.5 # Covariance
|
|
172
|
+
>>> y, Y = state_to_information(x, P)
|
|
173
|
+
>>> Y # Should be inv(P) = 2*I
|
|
174
|
+
array([[2., 0.],
|
|
175
|
+
[0., 2.]])
|
|
176
|
+
>>> y # Should be Y @ x = [2, 4]
|
|
177
|
+
array([2., 4.])
|
|
152
178
|
"""
|
|
153
179
|
x = np.asarray(x, dtype=np.float64).flatten()
|
|
154
180
|
P = np.asarray(P, dtype=np.float64)
|
|
@@ -323,6 +349,24 @@ def srif_predict(
|
|
|
323
349
|
R_pred : ndarray
|
|
324
350
|
Predicted square root information matrix.
|
|
325
351
|
|
|
352
|
+
Examples
|
|
353
|
+
--------
|
|
354
|
+
>>> import numpy as np
|
|
355
|
+
>>> n = 2
|
|
356
|
+
>>> # Initialize with known state
|
|
357
|
+
>>> P0 = np.eye(n)
|
|
358
|
+
>>> R0 = np.linalg.inv(np.linalg.cholesky(P0)).T
|
|
359
|
+
>>> x0 = np.array([1.0, 0.5])
|
|
360
|
+
>>> r0 = R0 @ x0
|
|
361
|
+
>>> # Prediction step
|
|
362
|
+
>>> F = np.array([[1, 1], [0, 1]])
|
|
363
|
+
>>> Q = np.eye(2) * 0.1
|
|
364
|
+
>>> r_pred, R_pred = srif_predict(r0, R0, F, Q)
|
|
365
|
+
>>> r_pred.shape
|
|
366
|
+
(2,)
|
|
367
|
+
>>> R_pred.shape
|
|
368
|
+
(2, 2)
|
|
369
|
+
|
|
326
370
|
Notes
|
|
327
371
|
-----
|
|
328
372
|
The SRIF prediction uses:
|
|
@@ -400,6 +444,26 @@ def srif_update(
|
|
|
400
444
|
R_upd : ndarray
|
|
401
445
|
Updated square root information matrix.
|
|
402
446
|
|
|
447
|
+
Examples
|
|
448
|
+
--------
|
|
449
|
+
>>> import numpy as np
|
|
450
|
+
>>> n = 2
|
|
451
|
+
>>> # Prior in SRIF form
|
|
452
|
+
>>> P_prior = np.eye(n) * 2.0
|
|
453
|
+
>>> R_prior = np.linalg.inv(np.linalg.cholesky(P_prior)).T
|
|
454
|
+
>>> x_prior = np.array([1.0, 0.5])
|
|
455
|
+
>>> r_prior = R_prior @ x_prior
|
|
456
|
+
>>> # Measurement update
|
|
457
|
+
>>> z = np.array([1.2]) # Position measurement
|
|
458
|
+
>>> H = np.array([[1, 0]])
|
|
459
|
+
>>> R_meas = np.array([[0.5]]) # Measurement noise covariance
|
|
460
|
+
>>> r_upd, R_upd = srif_update(r_prior, R_prior, z, H, R_meas)
|
|
461
|
+
>>> r_upd.shape
|
|
462
|
+
(2,)
|
|
463
|
+
>>> # Updated information matrix has more information (higher values)
|
|
464
|
+
>>> np.linalg.det(R_upd.T @ R_upd) > np.linalg.det(R_prior.T @ R_prior)
|
|
465
|
+
True
|
|
466
|
+
|
|
403
467
|
Notes
|
|
404
468
|
-----
|
|
405
469
|
The update uses QR decomposition to combine the prior information
|
|
@@ -263,6 +263,23 @@ def ekf_predict_auto(
|
|
|
263
263
|
-------
|
|
264
264
|
result : KalmanPrediction
|
|
265
265
|
Predicted state and covariance.
|
|
266
|
+
|
|
267
|
+
Examples
|
|
268
|
+
--------
|
|
269
|
+
>>> import numpy as np
|
|
270
|
+
>>> # Simple nonlinear dynamics
|
|
271
|
+
>>> def f(x):
|
|
272
|
+
... return np.array([x[0] + x[1], 0.9 * x[1]])
|
|
273
|
+
>>> x = np.array([0.0, 1.0])
|
|
274
|
+
>>> P = np.eye(2) * 0.1
|
|
275
|
+
>>> Q = np.eye(2) * 0.01
|
|
276
|
+
>>> pred = ekf_predict_auto(x, P, f, Q)
|
|
277
|
+
>>> pred.x
|
|
278
|
+
array([1., 0.9])
|
|
279
|
+
|
|
280
|
+
See Also
|
|
281
|
+
--------
|
|
282
|
+
ekf_predict : EKF prediction with explicit Jacobian.
|
|
266
283
|
"""
|
|
267
284
|
x = np.asarray(x, dtype=np.float64).flatten()
|
|
268
285
|
F = numerical_jacobian(f, x, dx)
|
|
@@ -299,6 +316,24 @@ def ekf_update_auto(
|
|
|
299
316
|
-------
|
|
300
317
|
result : KalmanUpdate
|
|
301
318
|
Updated state and covariance.
|
|
319
|
+
|
|
320
|
+
Examples
|
|
321
|
+
--------
|
|
322
|
+
>>> import numpy as np
|
|
323
|
+
>>> # Range measurement h(x) = sqrt(x[0]^2 + x[1]^2)
|
|
324
|
+
>>> def h(x):
|
|
325
|
+
... return np.array([np.sqrt(x[0]**2 + x[1]**2)])
|
|
326
|
+
>>> x = np.array([3.0, 4.0]) # predicted position
|
|
327
|
+
>>> P = np.eye(2) * 0.5
|
|
328
|
+
>>> z = np.array([5.1]) # measured range
|
|
329
|
+
>>> R = np.array([[0.1]])
|
|
330
|
+
>>> upd = ekf_update_auto(x, P, z, h, R)
|
|
331
|
+
>>> np.linalg.norm(upd.x) # estimate moves toward measurement
|
|
332
|
+
5.0...
|
|
333
|
+
|
|
334
|
+
See Also
|
|
335
|
+
--------
|
|
336
|
+
ekf_update : EKF update with explicit Jacobian.
|
|
302
337
|
"""
|
|
303
338
|
x = np.asarray(x, dtype=np.float64).flatten()
|
|
304
339
|
H = numerical_jacobian(h, x, dx)
|
|
@@ -344,6 +379,27 @@ def iterated_ekf_update(
|
|
|
344
379
|
-------
|
|
345
380
|
result : KalmanUpdate
|
|
346
381
|
Updated state and covariance after convergence.
|
|
382
|
+
|
|
383
|
+
Examples
|
|
384
|
+
--------
|
|
385
|
+
>>> import numpy as np
|
|
386
|
+
>>> # Bearing-only measurement (highly nonlinear)
|
|
387
|
+
>>> def h(x):
|
|
388
|
+
... return np.array([np.arctan2(x[1], x[0])])
|
|
389
|
+
>>> def H_func(x):
|
|
390
|
+
... r2 = x[0]**2 + x[1]**2
|
|
391
|
+
... return np.array([[-x[1]/r2, x[0]/r2]])
|
|
392
|
+
>>> x = np.array([10.0, 10.0])
|
|
393
|
+
>>> P = np.eye(2) * 5.0
|
|
394
|
+
>>> z = np.array([np.radians(50)]) # measured bearing
|
|
395
|
+
>>> R = np.array([[0.01]])
|
|
396
|
+
>>> upd = iterated_ekf_update(x, P, z, h, H_func, R)
|
|
397
|
+
>>> upd.x.shape
|
|
398
|
+
(2,)
|
|
399
|
+
|
|
400
|
+
See Also
|
|
401
|
+
--------
|
|
402
|
+
ekf_update : Standard (non-iterated) EKF update.
|
|
347
403
|
"""
|
|
348
404
|
x = np.asarray(x, dtype=np.float64).flatten()
|
|
349
405
|
P = np.asarray(P, dtype=np.float64)
|
|
@@ -282,6 +282,22 @@ def kf_predict_update(
|
|
|
282
282
|
result : KalmanUpdate
|
|
283
283
|
Updated state and covariance with innovation statistics.
|
|
284
284
|
|
|
285
|
+
Examples
|
|
286
|
+
--------
|
|
287
|
+
Track a 1D constant-velocity target with position measurements:
|
|
288
|
+
|
|
289
|
+
>>> import numpy as np
|
|
290
|
+
>>> x = np.array([0.0, 1.0]) # [position, velocity]
|
|
291
|
+
>>> P = np.eye(2) * 0.1
|
|
292
|
+
>>> F = np.array([[1, 1], [0, 1]]) # CV model, T=1s
|
|
293
|
+
>>> Q = np.array([[0.25, 0.5], [0.5, 1.0]]) # process noise
|
|
294
|
+
>>> H = np.array([[1, 0]]) # measure position only
|
|
295
|
+
>>> R = np.array([[0.5]]) # measurement noise variance
|
|
296
|
+
>>> z = np.array([1.1]) # measured position
|
|
297
|
+
>>> result = kf_predict_update(x, P, z, F, Q, H, R)
|
|
298
|
+
>>> result.x # updated state
|
|
299
|
+
array([1.0..., 1.0...])
|
|
300
|
+
|
|
285
301
|
See Also
|
|
286
302
|
--------
|
|
287
303
|
kf_predict : Prediction step only.
|
|
@@ -336,6 +352,30 @@ def kf_smooth(
|
|
|
336
352
|
G = P_filt @ F' @ P_pred^{-1}
|
|
337
353
|
x_smooth = x_filt + G @ (x_smooth_next - x_pred)
|
|
338
354
|
P_smooth = P_filt + G @ (P_smooth_next - P_pred) @ G'
|
|
355
|
+
|
|
356
|
+
Examples
|
|
357
|
+
--------
|
|
358
|
+
Apply RTS smoothing to improve a filtered estimate:
|
|
359
|
+
|
|
360
|
+
>>> import numpy as np
|
|
361
|
+
>>> # Filtered estimate at time k
|
|
362
|
+
>>> x_filt = np.array([1.0, 0.9])
|
|
363
|
+
>>> P_filt = np.array([[0.1, 0.05], [0.05, 0.2]])
|
|
364
|
+
>>> # Predicted estimate at time k+1 (from forward pass)
|
|
365
|
+
>>> x_pred = np.array([1.9, 0.9])
|
|
366
|
+
>>> P_pred = np.array([[0.35, 0.25], [0.25, 0.4]])
|
|
367
|
+
>>> # Smoothed estimate at time k+1 (already computed)
|
|
368
|
+
>>> x_smooth_next = np.array([2.0, 1.0])
|
|
369
|
+
>>> P_smooth_next = np.array([[0.08, 0.03], [0.03, 0.15]])
|
|
370
|
+
>>> F = np.array([[1, 1], [0, 1]]) # CV model
|
|
371
|
+
>>> x_s, P_s = kf_smooth(x_filt, P_filt, x_pred, P_pred,
|
|
372
|
+
... x_smooth_next, P_smooth_next, F)
|
|
373
|
+
>>> x_s # smoothed state at time k
|
|
374
|
+
array([1.0..., 0.9...])
|
|
375
|
+
|
|
376
|
+
See Also
|
|
377
|
+
--------
|
|
378
|
+
kf_predict_update : Forward filtering step.
|
|
339
379
|
"""
|
|
340
380
|
x_filt = np.asarray(x_filt, dtype=np.float64).flatten()
|
|
341
381
|
P_filt = np.asarray(P_filt, dtype=np.float64)
|
|
@@ -390,6 +430,21 @@ def information_filter_predict(
|
|
|
390
430
|
Y_pred : ndarray
|
|
391
431
|
Predicted information matrix.
|
|
392
432
|
|
|
433
|
+
Examples
|
|
434
|
+
--------
|
|
435
|
+
>>> import numpy as np
|
|
436
|
+
>>> # Convert from state form to information form
|
|
437
|
+
>>> x = np.array([1.0, 0.5])
|
|
438
|
+
>>> P = np.eye(2) * 0.1
|
|
439
|
+
>>> Y = np.linalg.inv(P)
|
|
440
|
+
>>> y = Y @ x
|
|
441
|
+
>>> # Predict with constant velocity model
|
|
442
|
+
>>> F = np.array([[1, 1], [0, 1]])
|
|
443
|
+
>>> Q = np.eye(2) * 0.01
|
|
444
|
+
>>> y_pred, Y_pred = information_filter_predict(y, Y, F, Q)
|
|
445
|
+
>>> y_pred.shape
|
|
446
|
+
(2,)
|
|
447
|
+
|
|
393
448
|
Notes
|
|
394
449
|
-----
|
|
395
450
|
The prediction requires converting to state space, predicting,
|
|
@@ -445,6 +500,20 @@ def information_filter_update(
|
|
|
445
500
|
Y_upd : ndarray
|
|
446
501
|
Updated information matrix.
|
|
447
502
|
|
|
503
|
+
Examples
|
|
504
|
+
--------
|
|
505
|
+
>>> import numpy as np
|
|
506
|
+
>>> # Initial information form (from prior)
|
|
507
|
+
>>> Y = np.eye(2) * 10.0 # High prior information
|
|
508
|
+
>>> y = np.array([10.0, 5.0]) # Corresponds to state [1.0, 0.5]
|
|
509
|
+
>>> # Measurement z = H @ x + noise
|
|
510
|
+
>>> z = np.array([1.1])
|
|
511
|
+
>>> H = np.array([[1, 0]]) # Measure position only
|
|
512
|
+
>>> R = np.array([[0.1]])
|
|
513
|
+
>>> y_upd, Y_upd = information_filter_update(y, Y, z, H, R)
|
|
514
|
+
>>> Y_upd[0, 0] > Y[0, 0] # Information increased
|
|
515
|
+
True
|
|
516
|
+
|
|
448
517
|
Notes
|
|
449
518
|
-----
|
|
450
519
|
The update in information form is additive:
|
|
@@ -138,6 +138,17 @@ def sigma_points_julier(
|
|
|
138
138
|
result : SigmaPoints
|
|
139
139
|
Sigma points and weights.
|
|
140
140
|
|
|
141
|
+
Examples
|
|
142
|
+
--------
|
|
143
|
+
>>> import numpy as np
|
|
144
|
+
>>> x = np.array([1.0, 2.0])
|
|
145
|
+
>>> P = np.eye(2) * 0.1
|
|
146
|
+
>>> sp = sigma_points_julier(x, P, kappa=1.0)
|
|
147
|
+
>>> sp.points.shape # 2*n+1 = 5 points for n=2
|
|
148
|
+
(5, 2)
|
|
149
|
+
>>> np.allclose(sp.Wm.sum(), 1.0)
|
|
150
|
+
True
|
|
151
|
+
|
|
141
152
|
Notes
|
|
142
153
|
-----
|
|
143
154
|
Julier's method is a special case of Merwe's with alpha=1, beta=0.
|
|
@@ -196,6 +207,21 @@ def unscented_transform(
|
|
|
196
207
|
Weighted mean, shape (m,).
|
|
197
208
|
cov : ndarray
|
|
198
209
|
Weighted covariance, shape (m, m).
|
|
210
|
+
|
|
211
|
+
Examples
|
|
212
|
+
--------
|
|
213
|
+
>>> import numpy as np
|
|
214
|
+
>>> # Generate sigma points
|
|
215
|
+
>>> x = np.array([0.0, 0.0])
|
|
216
|
+
>>> P = np.eye(2)
|
|
217
|
+
>>> sp = sigma_points_merwe(x, P)
|
|
218
|
+
>>> # Pass through nonlinear function
|
|
219
|
+
>>> def f(x):
|
|
220
|
+
... return np.array([x[0]**2, x[1]])
|
|
221
|
+
>>> sigmas_f = np.array([f(sp.points[i]) for i in range(len(sp.points))])
|
|
222
|
+
>>> mean, cov = unscented_transform(sigmas_f, sp.Wm, sp.Wc)
|
|
223
|
+
>>> mean.shape
|
|
224
|
+
(2,)
|
|
199
225
|
"""
|
|
200
226
|
# Weighted mean
|
|
201
227
|
mean = np.sum(Wm[:, np.newaxis] * sigmas, axis=0)
|
|
@@ -325,6 +351,24 @@ def ukf_update(
|
|
|
325
351
|
result : KalmanUpdate
|
|
326
352
|
Updated state, covariance, and innovation statistics.
|
|
327
353
|
|
|
354
|
+
Examples
|
|
355
|
+
--------
|
|
356
|
+
Update with a range-bearing measurement:
|
|
357
|
+
|
|
358
|
+
>>> import numpy as np
|
|
359
|
+
>>> # State: [x, y], range-bearing measurement
|
|
360
|
+
>>> def h_rb(x):
|
|
361
|
+
... r = np.sqrt(x[0]**2 + x[1]**2)
|
|
362
|
+
... theta = np.arctan2(x[1], x[0])
|
|
363
|
+
... return np.array([r, theta])
|
|
364
|
+
>>> x = np.array([100.0, 50.0])
|
|
365
|
+
>>> P = np.eye(2) * 10.0
|
|
366
|
+
>>> z = np.array([112.0, 0.46]) # measured range and bearing
|
|
367
|
+
>>> R = np.diag([1.0, 0.01]) # measurement noise
|
|
368
|
+
>>> upd = ukf_update(x, P, z, h_rb, R)
|
|
369
|
+
>>> upd.x.shape
|
|
370
|
+
(2,)
|
|
371
|
+
|
|
328
372
|
See Also
|
|
329
373
|
--------
|
|
330
374
|
ukf_predict : UKF prediction step.
|
|
@@ -400,6 +444,15 @@ def ckf_spherical_cubature_points(
|
|
|
400
444
|
weights : ndarray
|
|
401
445
|
Cubature weights.
|
|
402
446
|
|
|
447
|
+
Examples
|
|
448
|
+
--------
|
|
449
|
+
>>> import numpy as np
|
|
450
|
+
>>> points, weights = ckf_spherical_cubature_points(3)
|
|
451
|
+
>>> points.shape # 2n = 6 points for n=3
|
|
452
|
+
(6, 3)
|
|
453
|
+
>>> np.allclose(weights.sum(), 1.0)
|
|
454
|
+
True
|
|
455
|
+
|
|
403
456
|
Notes
|
|
404
457
|
-----
|
|
405
458
|
The CKF uses a third-degree spherical-radial cubature rule with
|
|
@@ -445,6 +498,20 @@ def ckf_predict(
|
|
|
445
498
|
result : KalmanPrediction
|
|
446
499
|
Predicted state and covariance.
|
|
447
500
|
|
|
501
|
+
Examples
|
|
502
|
+
--------
|
|
503
|
+
>>> import numpy as np
|
|
504
|
+
>>> # Linear dynamics: x_k+1 = F @ x_k
|
|
505
|
+
>>> def f(x):
|
|
506
|
+
... F = np.array([[1, 1], [0, 1]])
|
|
507
|
+
... return F @ x
|
|
508
|
+
>>> x = np.array([0.0, 1.0])
|
|
509
|
+
>>> P = np.eye(2) * 0.1
|
|
510
|
+
>>> Q = np.eye(2) * 0.01
|
|
511
|
+
>>> pred = ckf_predict(x, P, f, Q)
|
|
512
|
+
>>> pred.x # Should be approximately [1, 1]
|
|
513
|
+
array([1., 1.])
|
|
514
|
+
|
|
448
515
|
References
|
|
449
516
|
----------
|
|
450
517
|
.. [1] Arasaratnam, I. and Haykin, S., "Cubature Kalman Filters",
|
|
@@ -512,6 +579,20 @@ def ckf_update(
|
|
|
512
579
|
-------
|
|
513
580
|
result : KalmanUpdate
|
|
514
581
|
Updated state and covariance.
|
|
582
|
+
|
|
583
|
+
Examples
|
|
584
|
+
--------
|
|
585
|
+
>>> import numpy as np
|
|
586
|
+
>>> # Position measurement h(x) = x[0]
|
|
587
|
+
>>> def h(x):
|
|
588
|
+
... return np.array([x[0]])
|
|
589
|
+
>>> x = np.array([3.0, 1.0]) # predicted [position, velocity]
|
|
590
|
+
>>> P = np.eye(2) * 0.5
|
|
591
|
+
>>> z = np.array([3.1]) # measurement
|
|
592
|
+
>>> R = np.array([[0.1]])
|
|
593
|
+
>>> upd = ckf_update(x, P, z, h, R)
|
|
594
|
+
>>> upd.x.shape
|
|
595
|
+
(2,)
|
|
515
596
|
"""
|
|
516
597
|
x = np.asarray(x, dtype=np.float64).flatten()
|
|
517
598
|
P = np.asarray(P, dtype=np.float64)
|