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.
Files changed (68) hide show
  1. {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/METADATA +49 -4
  2. {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/RECORD +68 -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 +20 -0
  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/gpu/__init__.py +153 -0
  38. pytcl/gpu/ekf.py +425 -0
  39. pytcl/gpu/kalman.py +543 -0
  40. pytcl/gpu/matrix_utils.py +486 -0
  41. pytcl/gpu/particle_filter.py +568 -0
  42. pytcl/gpu/ukf.py +476 -0
  43. pytcl/gpu/utils.py +582 -0
  44. pytcl/gravity/clenshaw.py +60 -0
  45. pytcl/gravity/egm.py +47 -0
  46. pytcl/gravity/models.py +34 -0
  47. pytcl/gravity/spherical_harmonics.py +73 -0
  48. pytcl/gravity/tides.py +34 -0
  49. pytcl/mathematical_functions/numerical_integration/quadrature.py +85 -0
  50. pytcl/mathematical_functions/special_functions/bessel.py +55 -0
  51. pytcl/mathematical_functions/special_functions/elliptic.py +42 -0
  52. pytcl/mathematical_functions/special_functions/error_functions.py +49 -0
  53. pytcl/mathematical_functions/special_functions/gamma_functions.py +43 -0
  54. pytcl/mathematical_functions/special_functions/lambert_w.py +5 -0
  55. pytcl/mathematical_functions/special_functions/marcum_q.py +16 -0
  56. pytcl/navigation/geodesy.py +101 -2
  57. pytcl/navigation/great_circle.py +71 -0
  58. pytcl/navigation/rhumb.py +74 -0
  59. pytcl/performance_evaluation/estimation_metrics.py +70 -0
  60. pytcl/performance_evaluation/track_metrics.py +30 -0
  61. pytcl/static_estimation/maximum_likelihood.py +54 -0
  62. pytcl/static_estimation/robust.py +57 -0
  63. pytcl/terrain/dem.py +69 -0
  64. pytcl/terrain/visibility.py +65 -0
  65. pytcl/trackers/hypothesis.py +65 -0
  66. {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/LICENSE +0 -0
  67. {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/WHEEL +0 -0
  68. {nrl_tracker-1.9.1.dist-info → nrl_tracker-1.10.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,486 @@
1
+ """
2
+ GPU matrix utilities for numerical linear algebra.
3
+
4
+ This module provides GPU-accelerated matrix operations commonly used in
5
+ tracking algorithms, including:
6
+ - Cholesky decomposition
7
+ - QR factorization
8
+ - Matrix inversion and solving
9
+ - Memory pool management
10
+
11
+ Examples
12
+ --------
13
+ >>> from pytcl.gpu.matrix_utils import gpu_cholesky, gpu_solve
14
+ >>> import numpy as np
15
+ >>>
16
+ >>> # Compute Cholesky decomposition on GPU
17
+ >>> A = np.eye(4) + np.random.randn(4, 4) * 0.1
18
+ >>> A = A @ A.T # Make positive definite
19
+ >>> L = gpu_cholesky(A)
20
+ >>>
21
+ >>> # Solve linear system
22
+ >>> b = np.random.randn(4)
23
+ >>> x = gpu_solve(A, b)
24
+ """
25
+
26
+ import logging
27
+ from contextlib import contextmanager
28
+ from typing import Generator, Optional, Tuple
29
+
30
+ from numpy.typing import ArrayLike, NDArray
31
+
32
+ from pytcl.core.optional_deps import import_optional, is_available, requires
33
+ from pytcl.gpu.utils import ensure_gpu_array
34
+
35
+ # Module logger
36
+ _logger = logging.getLogger("pytcl.gpu.matrix_utils")
37
+
38
+
39
+ @requires("cupy", extra="gpu", feature="GPU matrix utilities")
40
+ def gpu_cholesky(A: ArrayLike, lower: bool = True) -> NDArray:
41
+ """
42
+ GPU-accelerated Cholesky decomposition.
43
+
44
+ Computes L such that A = L @ L.T (lower=True) or A = U.T @ U (lower=False).
45
+
46
+ Parameters
47
+ ----------
48
+ A : array_like
49
+ Symmetric positive definite matrix, shape (n, n) or batch (k, n, n).
50
+ lower : bool
51
+ If True, return lower triangular. If False, return upper triangular.
52
+
53
+ Returns
54
+ -------
55
+ L : ndarray
56
+ Cholesky factor, same shape as A.
57
+
58
+ Raises
59
+ ------
60
+ numpy.linalg.LinAlgError
61
+ If matrix is not positive definite.
62
+
63
+ Examples
64
+ --------
65
+ >>> import numpy as np
66
+ >>> from pytcl.gpu.matrix_utils import gpu_cholesky
67
+ >>> A = np.array([[4, 2], [2, 3]])
68
+ >>> L = gpu_cholesky(A)
69
+ >>> np.allclose(L @ L.T, A)
70
+ True
71
+ """
72
+ cp = import_optional("cupy", extra="gpu", feature="GPU matrix utilities")
73
+
74
+ A_gpu = ensure_gpu_array(A, dtype=cp.float64)
75
+
76
+ L = cp.linalg.cholesky(A_gpu)
77
+
78
+ if not lower:
79
+ if A_gpu.ndim == 2:
80
+ L = L.T
81
+ else:
82
+ L = cp.swapaxes(L, -2, -1)
83
+
84
+ return L
85
+
86
+
87
+ @requires("cupy", extra="gpu", feature="GPU matrix utilities")
88
+ def gpu_cholesky_safe(
89
+ A: ArrayLike,
90
+ lower: bool = True,
91
+ regularization: float = 1e-10,
92
+ ) -> Tuple[NDArray, bool]:
93
+ """
94
+ GPU Cholesky decomposition with fallback for non-positive-definite matrices.
95
+
96
+ If standard Cholesky fails, adds regularization to diagonal and retries.
97
+
98
+ Parameters
99
+ ----------
100
+ A : array_like
101
+ Symmetric matrix, shape (n, n) or batch (k, n, n).
102
+ lower : bool
103
+ Return lower (True) or upper (False) triangular factor.
104
+ regularization : float
105
+ Amount to add to diagonal if matrix is not positive definite.
106
+
107
+ Returns
108
+ -------
109
+ L : ndarray
110
+ Cholesky factor.
111
+ success : bool
112
+ True if succeeded without regularization.
113
+
114
+ Examples
115
+ --------
116
+ >>> import numpy as np
117
+ >>> from pytcl.gpu.matrix_utils import gpu_cholesky_safe
118
+ >>> A = np.array([[1, 2], [2, 1]]) # Not positive definite
119
+ >>> L, success = gpu_cholesky_safe(A)
120
+ >>> success
121
+ False
122
+ """
123
+ cp = import_optional("cupy", extra="gpu", feature="GPU matrix utilities")
124
+
125
+ A_gpu = ensure_gpu_array(A, dtype=cp.float64)
126
+
127
+ try:
128
+ L = cp.linalg.cholesky(A_gpu)
129
+ success = True
130
+ except cp.linalg.LinAlgError:
131
+ # Add regularization
132
+ if A_gpu.ndim == 2:
133
+ A_reg = A_gpu + regularization * cp.eye(A_gpu.shape[0], dtype=cp.float64)
134
+ else:
135
+ # Batch case
136
+ n = A_gpu.shape[-1]
137
+ eye = cp.eye(n, dtype=cp.float64)
138
+ A_reg = A_gpu + regularization * eye
139
+
140
+ L = cp.linalg.cholesky(A_reg)
141
+ success = False
142
+ _logger.warning("Cholesky decomposition required regularization")
143
+
144
+ if not lower:
145
+ if A_gpu.ndim == 2:
146
+ L = L.T
147
+ else:
148
+ L = cp.swapaxes(L, -2, -1)
149
+
150
+ return L, success
151
+
152
+
153
+ @requires("cupy", extra="gpu", feature="GPU matrix utilities")
154
+ def gpu_qr(A: ArrayLike, mode: str = "reduced") -> Tuple[NDArray, NDArray]:
155
+ """
156
+ GPU-accelerated QR decomposition.
157
+
158
+ Computes A = Q @ R where Q is orthogonal and R is upper triangular.
159
+
160
+ Parameters
161
+ ----------
162
+ A : array_like
163
+ Matrix to decompose, shape (m, n) or batch (k, m, n).
164
+ mode : str
165
+ 'reduced' (default) or 'complete'.
166
+
167
+ Returns
168
+ -------
169
+ Q : ndarray
170
+ Orthogonal matrix.
171
+ R : ndarray
172
+ Upper triangular matrix.
173
+
174
+ Examples
175
+ --------
176
+ >>> import numpy as np
177
+ >>> from pytcl.gpu.matrix_utils import gpu_qr
178
+ >>> A = np.random.randn(4, 3)
179
+ >>> Q, R = gpu_qr(A)
180
+ >>> np.allclose(Q @ R, A)
181
+ True
182
+ """
183
+ cp = import_optional("cupy", extra="gpu", feature="GPU matrix utilities")
184
+
185
+ A_gpu = ensure_gpu_array(A, dtype=cp.float64)
186
+ Q, R = cp.linalg.qr(A_gpu, mode=mode)
187
+
188
+ return Q, R
189
+
190
+
191
+ @requires("cupy", extra="gpu", feature="GPU matrix utilities")
192
+ def gpu_solve(A: ArrayLike, b: ArrayLike) -> NDArray:
193
+ """
194
+ GPU-accelerated linear system solve.
195
+
196
+ Solves A @ x = b for x.
197
+
198
+ Parameters
199
+ ----------
200
+ A : array_like
201
+ Coefficient matrix, shape (n, n) or batch (k, n, n).
202
+ b : array_like
203
+ Right-hand side, shape (n,) or (n, m) or batch (k, n).
204
+
205
+ Returns
206
+ -------
207
+ x : ndarray
208
+ Solution vector/matrix.
209
+
210
+ Examples
211
+ --------
212
+ >>> import numpy as np
213
+ >>> from pytcl.gpu.matrix_utils import gpu_solve
214
+ >>> A = np.array([[3, 1], [1, 2]])
215
+ >>> b = np.array([9, 8])
216
+ >>> x = gpu_solve(A, b)
217
+ >>> np.allclose(A @ x, b)
218
+ True
219
+ """
220
+ cp = import_optional("cupy", extra="gpu", feature="GPU matrix utilities")
221
+
222
+ A_gpu = ensure_gpu_array(A, dtype=cp.float64)
223
+ b_gpu = ensure_gpu_array(b, dtype=cp.float64)
224
+
225
+ x = cp.linalg.solve(A_gpu, b_gpu)
226
+
227
+ return x
228
+
229
+
230
+ @requires("cupy", extra="gpu", feature="GPU matrix utilities")
231
+ def gpu_inv(A: ArrayLike) -> NDArray:
232
+ """
233
+ GPU-accelerated matrix inversion.
234
+
235
+ Parameters
236
+ ----------
237
+ A : array_like
238
+ Matrix to invert, shape (n, n) or batch (k, n, n).
239
+
240
+ Returns
241
+ -------
242
+ A_inv : ndarray
243
+ Inverse matrix.
244
+
245
+ Examples
246
+ --------
247
+ >>> import numpy as np
248
+ >>> from pytcl.gpu.matrix_utils import gpu_inv
249
+ >>> A = np.array([[1, 2], [3, 4]])
250
+ >>> A_inv = gpu_inv(A)
251
+ >>> np.allclose(A @ A_inv, np.eye(2))
252
+ True
253
+ """
254
+ cp = import_optional("cupy", extra="gpu", feature="GPU matrix utilities")
255
+
256
+ A_gpu = ensure_gpu_array(A, dtype=cp.float64)
257
+ A_inv = cp.linalg.inv(A_gpu)
258
+
259
+ return A_inv
260
+
261
+
262
+ @requires("cupy", extra="gpu", feature="GPU matrix utilities")
263
+ def gpu_eigh(A: ArrayLike) -> Tuple[NDArray, NDArray]:
264
+ """
265
+ GPU-accelerated eigendecomposition for symmetric matrices.
266
+
267
+ Computes eigenvalues and eigenvectors of symmetric matrix A.
268
+
269
+ Parameters
270
+ ----------
271
+ A : array_like
272
+ Symmetric matrix, shape (n, n) or batch (k, n, n).
273
+
274
+ Returns
275
+ -------
276
+ eigenvalues : ndarray
277
+ Eigenvalues in ascending order.
278
+ eigenvectors : ndarray
279
+ Corresponding eigenvectors as columns.
280
+
281
+ Examples
282
+ --------
283
+ >>> import numpy as np
284
+ >>> from pytcl.gpu.matrix_utils import gpu_eigh
285
+ >>> A = np.array([[2, 1], [1, 2]])
286
+ >>> eigvals, eigvecs = gpu_eigh(A)
287
+ >>> eigvals
288
+ array([1., 3.])
289
+ """
290
+ cp = import_optional("cupy", extra="gpu", feature="GPU matrix utilities")
291
+
292
+ A_gpu = ensure_gpu_array(A, dtype=cp.float64)
293
+ eigvals, eigvecs = cp.linalg.eigh(A_gpu)
294
+
295
+ return eigvals, eigvecs
296
+
297
+
298
+ @requires("cupy", extra="gpu", feature="GPU matrix utilities")
299
+ def gpu_matrix_sqrt(A: ArrayLike) -> NDArray:
300
+ """
301
+ GPU-accelerated matrix square root for positive definite matrices.
302
+
303
+ Computes S such that S @ S = A using eigendecomposition.
304
+
305
+ Parameters
306
+ ----------
307
+ A : array_like
308
+ Symmetric positive definite matrix.
309
+
310
+ Returns
311
+ -------
312
+ S : ndarray
313
+ Matrix square root.
314
+
315
+ Examples
316
+ --------
317
+ >>> import numpy as np
318
+ >>> from pytcl.gpu.matrix_utils import gpu_matrix_sqrt
319
+ >>> A = np.array([[4, 0], [0, 9]])
320
+ >>> S = gpu_matrix_sqrt(A)
321
+ >>> np.allclose(S @ S, A)
322
+ True
323
+ """
324
+ cp = import_optional("cupy", extra="gpu", feature="GPU matrix utilities")
325
+
326
+ A_gpu = ensure_gpu_array(A, dtype=cp.float64)
327
+
328
+ # Eigendecomposition
329
+ eigvals, eigvecs = cp.linalg.eigh(A_gpu)
330
+
331
+ # Ensure non-negative eigenvalues
332
+ eigvals = cp.maximum(eigvals, 0)
333
+
334
+ # Compute sqrt
335
+ sqrt_eigvals = cp.sqrt(eigvals)
336
+
337
+ # Reconstruct: S = V @ diag(sqrt(lambda)) @ V'
338
+ if A_gpu.ndim == 2:
339
+ S = eigvecs @ cp.diag(sqrt_eigvals) @ eigvecs.T
340
+ else:
341
+ # Batch case
342
+ S = cp.einsum("...ij,...j,...kj->...ik", eigvecs, sqrt_eigvals, eigvecs)
343
+
344
+ return S
345
+
346
+
347
+ class MemoryPool:
348
+ """
349
+ GPU memory pool manager for efficient memory allocation.
350
+
351
+ This class provides convenient access to CuPy's memory pool
352
+ with additional monitoring and management utilities.
353
+
354
+ Examples
355
+ --------
356
+ >>> from pytcl.gpu.matrix_utils import MemoryPool
357
+ >>> pool = MemoryPool()
358
+ >>> print(pool.get_stats())
359
+ {'used': 0, 'total': 0, 'free': ...}
360
+ >>>
361
+ >>> # Allocate some arrays
362
+ >>> import cupy as cp
363
+ >>> x = cp.zeros((1000, 1000))
364
+ >>> print(pool.get_stats())
365
+ {'used': 8000000, ...}
366
+ >>>
367
+ >>> # Free cached memory
368
+ >>> pool.free_all()
369
+ """
370
+
371
+ def __init__(self):
372
+ """Initialize memory pool manager."""
373
+ if not is_available("cupy"):
374
+ _logger.warning("CuPy not available, MemoryPool is a no-op")
375
+ self._pool = None
376
+ self._pinned_pool = None
377
+ else:
378
+ import cupy as cp
379
+
380
+ self._pool = cp.get_default_memory_pool()
381
+ self._pinned_pool = cp.get_default_pinned_memory_pool()
382
+
383
+ def get_stats(self) -> dict[str, int]:
384
+ """
385
+ Get memory pool statistics.
386
+
387
+ Returns
388
+ -------
389
+ stats : dict
390
+ Dictionary with 'used', 'total', and 'free' bytes.
391
+ """
392
+ if self._pool is None:
393
+ return {"used": 0, "total": 0, "free": 0}
394
+
395
+ import cupy as cp
396
+
397
+ free, total = cp.cuda.Device().mem_info
398
+
399
+ return {
400
+ "used": self._pool.used_bytes(),
401
+ "total": self._pool.total_bytes(),
402
+ "free": free,
403
+ "device_total": total,
404
+ }
405
+
406
+ def free_all(self) -> None:
407
+ """Free all cached memory blocks."""
408
+ if self._pool is not None:
409
+ self._pool.free_all_blocks()
410
+ if self._pinned_pool is not None:
411
+ self._pinned_pool.free_all_blocks()
412
+
413
+ def set_limit(self, limit: Optional[int] = None) -> None:
414
+ """
415
+ Set memory pool limit.
416
+
417
+ Parameters
418
+ ----------
419
+ limit : int or None
420
+ Maximum bytes to allocate. None for no limit.
421
+ """
422
+ if self._pool is not None:
423
+ if limit is None:
424
+ self._pool.set_limit(size=0) # 0 means no limit
425
+ else:
426
+ self._pool.set_limit(size=limit)
427
+
428
+ @contextmanager
429
+ def limit_memory(self, max_bytes: int) -> Generator[None, None, None]:
430
+ """
431
+ Context manager for temporary memory limit.
432
+
433
+ Parameters
434
+ ----------
435
+ max_bytes : int
436
+ Maximum bytes allowed during context.
437
+
438
+ Examples
439
+ --------
440
+ >>> pool = MemoryPool()
441
+ >>> with pool.limit_memory(1e9): # 1GB limit
442
+ ... # Operations here have limited memory
443
+ ... pass
444
+ """
445
+ if self._pool is None:
446
+ yield
447
+ return
448
+
449
+ old_limit = self._pool.get_limit()
450
+ self._pool.set_limit(size=max_bytes)
451
+ try:
452
+ yield
453
+ finally:
454
+ self._pool.set_limit(size=old_limit)
455
+
456
+
457
+ # Global memory pool instance
458
+ _memory_pool: Optional[MemoryPool] = None
459
+
460
+
461
+ def get_memory_pool() -> MemoryPool:
462
+ """
463
+ Get the global GPU memory pool manager.
464
+
465
+ Returns
466
+ -------
467
+ pool : MemoryPool
468
+ Global memory pool instance.
469
+ """
470
+ global _memory_pool
471
+ if _memory_pool is None:
472
+ _memory_pool = MemoryPool()
473
+ return _memory_pool
474
+
475
+
476
+ __all__ = [
477
+ "gpu_cholesky",
478
+ "gpu_cholesky_safe",
479
+ "gpu_qr",
480
+ "gpu_solve",
481
+ "gpu_inv",
482
+ "gpu_eigh",
483
+ "gpu_matrix_sqrt",
484
+ "MemoryPool",
485
+ "get_memory_pool",
486
+ ]