nrl-tracker 1.9.0__py3-none-any.whl → 1.9.2__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.
Files changed (61) hide show
  1. {nrl_tracker-1.9.0.dist-info → nrl_tracker-1.9.2.dist-info}/METADATA +4 -4
  2. {nrl_tracker-1.9.0.dist-info → nrl_tracker-1.9.2.dist-info}/RECORD +61 -60
  3. pytcl/__init__.py +2 -2
  4. pytcl/assignment_algorithms/gating.py +18 -0
  5. pytcl/assignment_algorithms/jpda.py +56 -0
  6. pytcl/assignment_algorithms/nd_assignment.py +65 -0
  7. pytcl/assignment_algorithms/network_flow.py +40 -0
  8. pytcl/astronomical/ephemerides.py +18 -0
  9. pytcl/astronomical/orbital_mechanics.py +131 -0
  10. pytcl/atmosphere/ionosphere.py +44 -0
  11. pytcl/atmosphere/models.py +29 -0
  12. pytcl/clustering/dbscan.py +9 -0
  13. pytcl/clustering/gaussian_mixture.py +20 -0
  14. pytcl/clustering/hierarchical.py +29 -0
  15. pytcl/clustering/kmeans.py +9 -0
  16. pytcl/coordinate_systems/conversions/geodetic.py +46 -0
  17. pytcl/coordinate_systems/conversions/spherical.py +35 -0
  18. pytcl/coordinate_systems/rotations/rotations.py +147 -0
  19. pytcl/core/__init__.py +16 -0
  20. pytcl/core/maturity.py +346 -0
  21. pytcl/core/optional_deps.py +1 -1
  22. pytcl/dynamic_estimation/gaussian_sum_filter.py +55 -0
  23. pytcl/dynamic_estimation/imm.py +29 -0
  24. pytcl/dynamic_estimation/information_filter.py +64 -0
  25. pytcl/dynamic_estimation/kalman/extended.py +56 -0
  26. pytcl/dynamic_estimation/kalman/linear.py +69 -0
  27. pytcl/dynamic_estimation/kalman/unscented.py +81 -0
  28. pytcl/dynamic_estimation/particle_filters/bootstrap.py +146 -0
  29. pytcl/dynamic_estimation/rbpf.py +51 -0
  30. pytcl/dynamic_estimation/smoothers.py +58 -0
  31. pytcl/dynamic_models/continuous_time/dynamics.py +104 -0
  32. pytcl/dynamic_models/discrete_time/coordinated_turn.py +6 -0
  33. pytcl/dynamic_models/discrete_time/singer.py +12 -0
  34. pytcl/dynamic_models/process_noise/coordinated_turn.py +46 -0
  35. pytcl/dynamic_models/process_noise/polynomial.py +6 -0
  36. pytcl/dynamic_models/process_noise/singer.py +52 -0
  37. pytcl/gravity/clenshaw.py +60 -0
  38. pytcl/gravity/egm.py +47 -0
  39. pytcl/gravity/models.py +34 -0
  40. pytcl/gravity/spherical_harmonics.py +73 -0
  41. pytcl/gravity/tides.py +34 -0
  42. pytcl/mathematical_functions/numerical_integration/quadrature.py +85 -0
  43. pytcl/mathematical_functions/special_functions/bessel.py +55 -0
  44. pytcl/mathematical_functions/special_functions/elliptic.py +42 -0
  45. pytcl/mathematical_functions/special_functions/error_functions.py +49 -0
  46. pytcl/mathematical_functions/special_functions/gamma_functions.py +43 -0
  47. pytcl/mathematical_functions/special_functions/lambert_w.py +5 -0
  48. pytcl/mathematical_functions/special_functions/marcum_q.py +16 -0
  49. pytcl/navigation/geodesy.py +101 -2
  50. pytcl/navigation/great_circle.py +71 -0
  51. pytcl/navigation/rhumb.py +74 -0
  52. pytcl/performance_evaluation/estimation_metrics.py +70 -0
  53. pytcl/performance_evaluation/track_metrics.py +30 -0
  54. pytcl/static_estimation/maximum_likelihood.py +54 -0
  55. pytcl/static_estimation/robust.py +57 -0
  56. pytcl/terrain/dem.py +69 -0
  57. pytcl/terrain/visibility.py +65 -0
  58. pytcl/trackers/hypothesis.py +65 -0
  59. {nrl_tracker-1.9.0.dist-info → nrl_tracker-1.9.2.dist-info}/LICENSE +0 -0
  60. {nrl_tracker-1.9.0.dist-info → nrl_tracker-1.9.2.dist-info}/WHEEL +0 -0
  61. {nrl_tracker-1.9.0.dist-info → nrl_tracker-1.9.2.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)
@@ -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)