nrl-tracker 0.22.5__py3-none-any.whl → 1.8.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-0.22.5.dist-info → nrl_tracker-1.8.0.dist-info}/METADATA +57 -10
- {nrl_tracker-0.22.5.dist-info → nrl_tracker-1.8.0.dist-info}/RECORD +86 -69
- pytcl/__init__.py +4 -3
- pytcl/assignment_algorithms/__init__.py +28 -0
- pytcl/assignment_algorithms/dijkstra_min_cost.py +184 -0
- pytcl/assignment_algorithms/gating.py +10 -10
- pytcl/assignment_algorithms/jpda.py +40 -40
- pytcl/assignment_algorithms/nd_assignment.py +379 -0
- pytcl/assignment_algorithms/network_flow.py +464 -0
- pytcl/assignment_algorithms/network_simplex.py +167 -0
- pytcl/assignment_algorithms/three_dimensional/assignment.py +3 -3
- pytcl/astronomical/__init__.py +104 -3
- pytcl/astronomical/ephemerides.py +14 -11
- pytcl/astronomical/reference_frames.py +865 -56
- pytcl/astronomical/relativity.py +6 -5
- pytcl/astronomical/sgp4.py +710 -0
- pytcl/astronomical/special_orbits.py +532 -0
- pytcl/astronomical/tle.py +558 -0
- pytcl/atmosphere/__init__.py +43 -1
- pytcl/atmosphere/ionosphere.py +512 -0
- pytcl/atmosphere/nrlmsise00.py +809 -0
- pytcl/clustering/dbscan.py +2 -2
- pytcl/clustering/gaussian_mixture.py +3 -3
- pytcl/clustering/hierarchical.py +15 -15
- pytcl/clustering/kmeans.py +4 -4
- pytcl/containers/__init__.py +24 -0
- pytcl/containers/base.py +219 -0
- pytcl/containers/cluster_set.py +12 -2
- pytcl/containers/covertree.py +26 -29
- pytcl/containers/kd_tree.py +94 -29
- pytcl/containers/rtree.py +200 -1
- pytcl/containers/vptree.py +21 -28
- pytcl/coordinate_systems/conversions/geodetic.py +272 -5
- pytcl/coordinate_systems/jacobians/jacobians.py +2 -2
- pytcl/coordinate_systems/projections/__init__.py +1 -1
- pytcl/coordinate_systems/projections/projections.py +2 -2
- pytcl/coordinate_systems/rotations/rotations.py +10 -6
- pytcl/core/__init__.py +18 -0
- pytcl/core/validation.py +333 -2
- pytcl/dynamic_estimation/__init__.py +26 -0
- pytcl/dynamic_estimation/gaussian_sum_filter.py +434 -0
- pytcl/dynamic_estimation/imm.py +14 -14
- pytcl/dynamic_estimation/kalman/__init__.py +30 -0
- pytcl/dynamic_estimation/kalman/constrained.py +382 -0
- pytcl/dynamic_estimation/kalman/extended.py +8 -8
- pytcl/dynamic_estimation/kalman/h_infinity.py +613 -0
- pytcl/dynamic_estimation/kalman/square_root.py +60 -573
- pytcl/dynamic_estimation/kalman/sr_ukf.py +302 -0
- pytcl/dynamic_estimation/kalman/ud_filter.py +410 -0
- pytcl/dynamic_estimation/kalman/unscented.py +8 -6
- pytcl/dynamic_estimation/particle_filters/bootstrap.py +15 -15
- pytcl/dynamic_estimation/rbpf.py +589 -0
- pytcl/gravity/egm.py +13 -0
- pytcl/gravity/spherical_harmonics.py +98 -37
- pytcl/gravity/tides.py +6 -6
- pytcl/logging_config.py +328 -0
- pytcl/magnetism/__init__.py +7 -0
- pytcl/magnetism/emm.py +10 -3
- pytcl/magnetism/wmm.py +260 -23
- pytcl/mathematical_functions/combinatorics/combinatorics.py +5 -5
- pytcl/mathematical_functions/geometry/geometry.py +5 -5
- pytcl/mathematical_functions/numerical_integration/quadrature.py +6 -6
- pytcl/mathematical_functions/signal_processing/detection.py +24 -24
- pytcl/mathematical_functions/signal_processing/filters.py +14 -14
- pytcl/mathematical_functions/signal_processing/matched_filter.py +12 -12
- pytcl/mathematical_functions/special_functions/bessel.py +15 -3
- pytcl/mathematical_functions/special_functions/debye.py +136 -26
- pytcl/mathematical_functions/special_functions/error_functions.py +3 -1
- pytcl/mathematical_functions/special_functions/gamma_functions.py +4 -4
- pytcl/mathematical_functions/special_functions/hypergeometric.py +81 -15
- pytcl/mathematical_functions/transforms/fourier.py +8 -8
- pytcl/mathematical_functions/transforms/stft.py +12 -12
- pytcl/mathematical_functions/transforms/wavelets.py +9 -9
- pytcl/navigation/geodesy.py +246 -160
- pytcl/navigation/great_circle.py +101 -19
- pytcl/plotting/coordinates.py +7 -7
- pytcl/plotting/tracks.py +2 -2
- pytcl/static_estimation/maximum_likelihood.py +16 -14
- pytcl/static_estimation/robust.py +5 -5
- pytcl/terrain/loaders.py +5 -5
- pytcl/trackers/hypothesis.py +1 -1
- pytcl/trackers/mht.py +9 -9
- pytcl/trackers/multi_target.py +1 -1
- {nrl_tracker-0.22.5.dist-info → nrl_tracker-1.8.0.dist-info}/LICENSE +0 -0
- {nrl_tracker-0.22.5.dist-info → nrl_tracker-1.8.0.dist-info}/WHEEL +0 -0
- {nrl_tracker-0.22.5.dist-info → nrl_tracker-1.8.0.dist-info}/top_level.txt +0 -0
pytcl/magnetism/wmm.py
CHANGED
|
@@ -12,13 +12,30 @@ References
|
|
|
12
12
|
.. [2] https://www.ngdc.noaa.gov/geomag/WMM/
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
from
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
from typing import Any, NamedTuple, Optional, Tuple
|
|
16
17
|
|
|
17
18
|
import numpy as np
|
|
18
19
|
from numpy.typing import NDArray
|
|
19
20
|
|
|
20
21
|
from pytcl.gravity.spherical_harmonics import associated_legendre
|
|
21
22
|
|
|
23
|
+
# =============================================================================
|
|
24
|
+
# Cache Configuration
|
|
25
|
+
# =============================================================================
|
|
26
|
+
|
|
27
|
+
# Default cache size (number of unique location/time combinations to cache)
|
|
28
|
+
_DEFAULT_CACHE_SIZE = 1024
|
|
29
|
+
|
|
30
|
+
# Precision for rounding inputs (radians for lat/lon, km for radius, years for time)
|
|
31
|
+
# These control how aggressively similar inputs are grouped
|
|
32
|
+
_CACHE_PRECISION = {
|
|
33
|
+
"lat": 6, # ~0.1 meter precision at Earth surface
|
|
34
|
+
"lon": 6,
|
|
35
|
+
"r": 3, # 1 meter precision
|
|
36
|
+
"year": 2, # ~4 day precision
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
|
|
23
40
|
class MagneticResult(NamedTuple):
|
|
24
41
|
"""Result of magnetic field computation.
|
|
@@ -404,37 +421,90 @@ def create_wmm2020_coefficients() -> MagneticCoefficients:
|
|
|
404
421
|
WMM2020 = create_wmm2020_coefficients()
|
|
405
422
|
|
|
406
423
|
|
|
407
|
-
|
|
424
|
+
# =============================================================================
|
|
425
|
+
# Cached Computation Core
|
|
426
|
+
# =============================================================================
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _quantize_inputs(
|
|
430
|
+
lat: float, lon: float, r: float, year: float
|
|
431
|
+
) -> Tuple[float, float, float, float]:
|
|
432
|
+
"""Round inputs to cache precision for consistent cache hits."""
|
|
433
|
+
return (
|
|
434
|
+
round(lat, _CACHE_PRECISION["lat"]),
|
|
435
|
+
round(lon, _CACHE_PRECISION["lon"]),
|
|
436
|
+
round(r, _CACHE_PRECISION["r"]),
|
|
437
|
+
round(year, _CACHE_PRECISION["year"]),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@lru_cache(maxsize=_DEFAULT_CACHE_SIZE)
|
|
442
|
+
def _magnetic_field_spherical_cached(
|
|
408
443
|
lat: float,
|
|
409
444
|
lon: float,
|
|
410
445
|
r: float,
|
|
411
446
|
year: float,
|
|
412
|
-
|
|
447
|
+
n_max: int,
|
|
448
|
+
coeff_id: int,
|
|
413
449
|
) -> Tuple[float, float, float]:
|
|
414
450
|
"""
|
|
415
|
-
|
|
451
|
+
Cached core computation of magnetic field in spherical coordinates.
|
|
452
|
+
|
|
453
|
+
This is the internal cached version. The coefficient arrays are identified
|
|
454
|
+
by their id() since NamedTuples with numpy arrays aren't hashable.
|
|
416
455
|
|
|
417
456
|
Parameters
|
|
418
457
|
----------
|
|
419
458
|
lat : float
|
|
420
|
-
Geocentric latitude in radians.
|
|
459
|
+
Geocentric latitude in radians (quantized).
|
|
421
460
|
lon : float
|
|
422
|
-
Longitude in radians.
|
|
461
|
+
Longitude in radians (quantized).
|
|
423
462
|
r : float
|
|
424
|
-
Radial distance
|
|
463
|
+
Radial distance in km (quantized).
|
|
425
464
|
year : float
|
|
426
|
-
Decimal year (
|
|
427
|
-
|
|
428
|
-
|
|
465
|
+
Decimal year (quantized).
|
|
466
|
+
n_max : int
|
|
467
|
+
Maximum spherical harmonic degree.
|
|
468
|
+
coeff_id : int
|
|
469
|
+
Unique identifier for the coefficient set.
|
|
429
470
|
|
|
430
471
|
Returns
|
|
431
472
|
-------
|
|
432
|
-
B_r : float
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
473
|
+
B_r, B_theta, B_phi : tuple of float
|
|
474
|
+
Magnetic field components in spherical coordinates (nT).
|
|
475
|
+
"""
|
|
476
|
+
# Retrieve coefficients from registry
|
|
477
|
+
coeffs = _coefficient_registry.get(coeff_id)
|
|
478
|
+
if coeffs is None:
|
|
479
|
+
raise ValueError(f"Coefficient set {coeff_id} not found in registry")
|
|
480
|
+
|
|
481
|
+
return _compute_magnetic_field_spherical_impl(lat, lon, r, year, coeffs)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
# Registry to hold coefficient sets by id
|
|
485
|
+
_coefficient_registry: dict[str, Any] = {}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _register_coefficients(coeffs: "MagneticCoefficients") -> int:
|
|
489
|
+
"""Register a coefficient set and return its unique ID."""
|
|
490
|
+
coeff_id = id(coeffs)
|
|
491
|
+
if coeff_id not in _coefficient_registry:
|
|
492
|
+
_coefficient_registry[coeff_id] = coeffs
|
|
493
|
+
return coeff_id
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _compute_magnetic_field_spherical_impl(
|
|
497
|
+
lat: float,
|
|
498
|
+
lon: float,
|
|
499
|
+
r: float,
|
|
500
|
+
year: float,
|
|
501
|
+
coeffs: "MagneticCoefficients",
|
|
502
|
+
) -> Tuple[float, float, float]:
|
|
503
|
+
"""
|
|
504
|
+
Core implementation of magnetic field computation.
|
|
505
|
+
|
|
506
|
+
This contains the actual spherical harmonic expansion logic,
|
|
507
|
+
separated for clarity and to support caching.
|
|
438
508
|
"""
|
|
439
509
|
n_max = coeffs.n_max
|
|
440
510
|
a = 6371.2 # Reference radius in km (WMM convention)
|
|
@@ -452,11 +522,9 @@ def magnetic_field_spherical(
|
|
|
452
522
|
sin_theta = np.sin(theta)
|
|
453
523
|
|
|
454
524
|
# Compute associated Legendre functions (Schmidt semi-normalized)
|
|
455
|
-
# WMM uses Schmidt semi-normalization
|
|
456
525
|
P = associated_legendre(n_max, n_max, cos_theta, normalized=True)
|
|
457
526
|
|
|
458
|
-
# Compute dP/dtheta
|
|
459
|
-
# For efficiency, use recurrence relation
|
|
527
|
+
# Compute dP/dtheta
|
|
460
528
|
dP = np.zeros((n_max + 1, n_max + 1))
|
|
461
529
|
if abs(sin_theta) > 1e-10:
|
|
462
530
|
for n in range(1, n_max + 1):
|
|
@@ -464,7 +532,6 @@ def magnetic_field_spherical(
|
|
|
464
532
|
if m == n:
|
|
465
533
|
dP[n, m] = n * cos_theta / sin_theta * P[n, m]
|
|
466
534
|
elif n > m:
|
|
467
|
-
# Recurrence relation for derivative
|
|
468
535
|
factor = np.sqrt((n - m) * (n + m + 1))
|
|
469
536
|
if m + 1 <= n:
|
|
470
537
|
dP[n, m] = (
|
|
@@ -489,13 +556,10 @@ def magnetic_field_spherical(
|
|
|
489
556
|
cos_m_lon = np.cos(m * lon)
|
|
490
557
|
sin_m_lon = np.sin(m * lon)
|
|
491
558
|
|
|
492
|
-
# Gauss coefficients
|
|
493
559
|
gnm = g[n, m]
|
|
494
560
|
hnm = h[n, m]
|
|
495
561
|
|
|
496
|
-
# Field contributions
|
|
497
562
|
B_r += (n + 1) * r_power * P[n, m] * (gnm * cos_m_lon + hnm * sin_m_lon)
|
|
498
|
-
|
|
499
563
|
B_theta += -r_power * dP[n, m] * (gnm * cos_m_lon + hnm * sin_m_lon)
|
|
500
564
|
|
|
501
565
|
if abs(sin_theta) > 1e-10:
|
|
@@ -510,6 +574,175 @@ def magnetic_field_spherical(
|
|
|
510
574
|
return B_r, B_theta, B_phi
|
|
511
575
|
|
|
512
576
|
|
|
577
|
+
# =============================================================================
|
|
578
|
+
# Cache Management
|
|
579
|
+
# =============================================================================
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def get_magnetic_cache_info() -> dict[str, Any]:
|
|
583
|
+
"""
|
|
584
|
+
Get information about the magnetic field computation cache.
|
|
585
|
+
|
|
586
|
+
Returns
|
|
587
|
+
-------
|
|
588
|
+
info : dict
|
|
589
|
+
Dictionary containing cache statistics:
|
|
590
|
+
- hits: Number of cache hits
|
|
591
|
+
- misses: Number of cache misses
|
|
592
|
+
- maxsize: Maximum cache size
|
|
593
|
+
- currsize: Current number of cached entries
|
|
594
|
+
- hit_rate: Ratio of hits to total calls (0-1)
|
|
595
|
+
|
|
596
|
+
Examples
|
|
597
|
+
--------
|
|
598
|
+
>>> from pytcl.magnetism import get_magnetic_cache_info
|
|
599
|
+
>>> info = get_magnetic_cache_info()
|
|
600
|
+
>>> print(f"Cache hit rate: {info['hit_rate']:.1%}")
|
|
601
|
+
"""
|
|
602
|
+
cache_info = _magnetic_field_spherical_cached.cache_info()
|
|
603
|
+
total = cache_info.hits + cache_info.misses
|
|
604
|
+
hit_rate = cache_info.hits / total if total > 0 else 0.0
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
"hits": cache_info.hits,
|
|
608
|
+
"misses": cache_info.misses,
|
|
609
|
+
"maxsize": cache_info.maxsize,
|
|
610
|
+
"currsize": cache_info.currsize,
|
|
611
|
+
"hit_rate": hit_rate,
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def clear_magnetic_cache() -> None:
|
|
616
|
+
"""
|
|
617
|
+
Clear the magnetic field computation cache.
|
|
618
|
+
|
|
619
|
+
This can be useful when memory is constrained or when switching
|
|
620
|
+
between different coefficient sets.
|
|
621
|
+
|
|
622
|
+
Examples
|
|
623
|
+
--------
|
|
624
|
+
>>> from pytcl.magnetism import clear_magnetic_cache
|
|
625
|
+
>>> clear_magnetic_cache() # Free cached computations
|
|
626
|
+
"""
|
|
627
|
+
_magnetic_field_spherical_cached.cache_clear()
|
|
628
|
+
_coefficient_registry.clear()
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def configure_magnetic_cache(
|
|
632
|
+
maxsize: Optional[int] = None,
|
|
633
|
+
precision: Optional[dict[str, Any]] = None,
|
|
634
|
+
) -> None:
|
|
635
|
+
"""
|
|
636
|
+
Configure the magnetic field computation cache.
|
|
637
|
+
|
|
638
|
+
Parameters
|
|
639
|
+
----------
|
|
640
|
+
maxsize : int, optional
|
|
641
|
+
Maximum number of entries in the cache. If None, keeps current.
|
|
642
|
+
Set to 0 to disable caching.
|
|
643
|
+
precision : dict, optional
|
|
644
|
+
Dictionary with keys 'lat', 'lon', 'r', 'year' specifying
|
|
645
|
+
decimal places for rounding. Higher values = more precision
|
|
646
|
+
but fewer cache hits.
|
|
647
|
+
|
|
648
|
+
Notes
|
|
649
|
+
-----
|
|
650
|
+
Changing cache configuration clears the existing cache.
|
|
651
|
+
|
|
652
|
+
Examples
|
|
653
|
+
--------
|
|
654
|
+
>>> from pytcl.magnetism import configure_magnetic_cache
|
|
655
|
+
>>> # Increase cache size for batch processing
|
|
656
|
+
>>> configure_magnetic_cache(maxsize=4096)
|
|
657
|
+
>>> # Reduce precision for more cache hits
|
|
658
|
+
>>> configure_magnetic_cache(precision={'lat': 4, 'lon': 4, 'r': 2, 'year': 1})
|
|
659
|
+
"""
|
|
660
|
+
global _magnetic_field_spherical_cached
|
|
661
|
+
|
|
662
|
+
if precision is not None:
|
|
663
|
+
for key in ["lat", "lon", "r", "year"]:
|
|
664
|
+
if key in precision:
|
|
665
|
+
_CACHE_PRECISION[key] = precision[key]
|
|
666
|
+
|
|
667
|
+
if maxsize is not None:
|
|
668
|
+
# Recreate the cached function with new maxsize
|
|
669
|
+
clear_magnetic_cache()
|
|
670
|
+
|
|
671
|
+
@lru_cache(maxsize=maxsize)
|
|
672
|
+
def new_cached(
|
|
673
|
+
lat: float,
|
|
674
|
+
lon: float,
|
|
675
|
+
r: float,
|
|
676
|
+
year: float,
|
|
677
|
+
n_max: int,
|
|
678
|
+
coeff_id: int,
|
|
679
|
+
) -> Tuple[float, float, float]:
|
|
680
|
+
coeffs = _coefficient_registry.get(coeff_id)
|
|
681
|
+
if coeffs is None:
|
|
682
|
+
raise ValueError(f"Coefficient set {coeff_id} not found")
|
|
683
|
+
return _compute_magnetic_field_spherical_impl(lat, lon, r, year, coeffs)
|
|
684
|
+
|
|
685
|
+
_magnetic_field_spherical_cached = new_cached
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def magnetic_field_spherical(
|
|
689
|
+
lat: float,
|
|
690
|
+
lon: float,
|
|
691
|
+
r: float,
|
|
692
|
+
year: float,
|
|
693
|
+
coeffs: MagneticCoefficients = WMM2020,
|
|
694
|
+
use_cache: bool = True,
|
|
695
|
+
) -> Tuple[float, float, float]:
|
|
696
|
+
"""
|
|
697
|
+
Compute magnetic field in spherical coordinates.
|
|
698
|
+
|
|
699
|
+
Parameters
|
|
700
|
+
----------
|
|
701
|
+
lat : float
|
|
702
|
+
Geocentric latitude in radians.
|
|
703
|
+
lon : float
|
|
704
|
+
Longitude in radians.
|
|
705
|
+
r : float
|
|
706
|
+
Radial distance from Earth's center in km.
|
|
707
|
+
year : float
|
|
708
|
+
Decimal year (e.g., 2023.5 for mid-2023).
|
|
709
|
+
coeffs : MagneticCoefficients, optional
|
|
710
|
+
Model coefficients. Default WMM2020.
|
|
711
|
+
use_cache : bool, optional
|
|
712
|
+
Whether to use LRU caching for repeated queries. Default True.
|
|
713
|
+
Set to False for single-use queries or when memory is constrained.
|
|
714
|
+
|
|
715
|
+
Returns
|
|
716
|
+
-------
|
|
717
|
+
B_r : float
|
|
718
|
+
Radial component (positive outward) in nT.
|
|
719
|
+
B_theta : float
|
|
720
|
+
Colatitude component (positive southward) in nT.
|
|
721
|
+
B_phi : float
|
|
722
|
+
Longitude component (positive eastward) in nT.
|
|
723
|
+
|
|
724
|
+
Notes
|
|
725
|
+
-----
|
|
726
|
+
Results are cached by default using LRU caching. Inputs are quantized
|
|
727
|
+
to a configurable precision before caching to improve hit rates for
|
|
728
|
+
nearby queries. Use `get_magnetic_cache_info()` to check cache
|
|
729
|
+
statistics and `clear_magnetic_cache()` to free memory.
|
|
730
|
+
"""
|
|
731
|
+
if use_cache:
|
|
732
|
+
# Quantize inputs for cache key
|
|
733
|
+
q_lat, q_lon, q_r, q_year = _quantize_inputs(lat, lon, r, year)
|
|
734
|
+
|
|
735
|
+
# Register coefficients and get ID
|
|
736
|
+
coeff_id = _register_coefficients(coeffs)
|
|
737
|
+
|
|
738
|
+
return _magnetic_field_spherical_cached(
|
|
739
|
+
q_lat, q_lon, q_r, q_year, coeffs.n_max, coeff_id
|
|
740
|
+
)
|
|
741
|
+
else:
|
|
742
|
+
# Direct computation without caching
|
|
743
|
+
return _compute_magnetic_field_spherical_impl(lat, lon, r, year, coeffs)
|
|
744
|
+
|
|
745
|
+
|
|
513
746
|
def wmm(
|
|
514
747
|
lat: float,
|
|
515
748
|
lon: float,
|
|
@@ -706,4 +939,8 @@ __all__ = [
|
|
|
706
939
|
"magnetic_declination",
|
|
707
940
|
"magnetic_inclination",
|
|
708
941
|
"magnetic_field_intensity",
|
|
942
|
+
# Cache management
|
|
943
|
+
"get_magnetic_cache_info",
|
|
944
|
+
"clear_magnetic_cache",
|
|
945
|
+
"configure_magnetic_cache",
|
|
709
946
|
]
|
|
@@ -7,7 +7,7 @@ related operations commonly used in assignment problems and data association.
|
|
|
7
7
|
|
|
8
8
|
import itertools
|
|
9
9
|
from functools import lru_cache
|
|
10
|
-
from typing import Iterator, List, Optional, Tuple
|
|
10
|
+
from typing import Any, Iterator, List, Optional, Tuple
|
|
11
11
|
|
|
12
12
|
from numpy.typing import ArrayLike
|
|
13
13
|
|
|
@@ -108,7 +108,7 @@ def n_permute_k(n: int, k: int) -> int:
|
|
|
108
108
|
def permutations(
|
|
109
109
|
items: ArrayLike,
|
|
110
110
|
k: Optional[int] = None,
|
|
111
|
-
) -> Iterator[
|
|
111
|
+
) -> Iterator[tuple[Any, ...]]:
|
|
112
112
|
"""
|
|
113
113
|
Generate all k-permutations of items.
|
|
114
114
|
|
|
@@ -136,7 +136,7 @@ def permutations(
|
|
|
136
136
|
def combinations(
|
|
137
137
|
items: ArrayLike,
|
|
138
138
|
k: int,
|
|
139
|
-
) -> Iterator[
|
|
139
|
+
) -> Iterator[tuple[Any, ...]]:
|
|
140
140
|
"""
|
|
141
141
|
Generate all k-combinations of items.
|
|
142
142
|
|
|
@@ -164,7 +164,7 @@ def combinations(
|
|
|
164
164
|
def combinations_with_replacement(
|
|
165
165
|
items: ArrayLike,
|
|
166
166
|
k: int,
|
|
167
|
-
) -> Iterator[
|
|
167
|
+
) -> Iterator[tuple[Any, ...]]:
|
|
168
168
|
"""
|
|
169
169
|
Generate all k-combinations with replacement.
|
|
170
170
|
|
|
@@ -263,7 +263,7 @@ def permutation_unrank(rank: int, n: int) -> List[int]:
|
|
|
263
263
|
return perm
|
|
264
264
|
|
|
265
265
|
|
|
266
|
-
def next_permutation(perm: ArrayLike) -> Optional[List]:
|
|
266
|
+
def next_permutation(perm: ArrayLike) -> Optional[List[Any]]:
|
|
267
267
|
"""
|
|
268
268
|
Generate the next permutation in lexicographic order.
|
|
269
269
|
|
|
@@ -5,7 +5,7 @@ This module provides geometric functions for points, lines, planes,
|
|
|
5
5
|
polygons, and related operations used in tracking applications.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import Optional, Tuple
|
|
8
|
+
from typing import Any, Optional, Tuple
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
from numpy.typing import ArrayLike, NDArray
|
|
@@ -527,12 +527,12 @@ def minimum_bounding_circle(
|
|
|
527
527
|
"""
|
|
528
528
|
points = np.asarray(points, dtype=np.float64)
|
|
529
529
|
|
|
530
|
-
def circle_from_two_points(p1, p2):
|
|
530
|
+
def circle_from_two_points(p1: Any, p2: Any) -> tuple[Any, Any]:
|
|
531
531
|
center = (p1 + p2) / 2
|
|
532
532
|
radius = np.linalg.norm(p1 - center)
|
|
533
533
|
return center, radius
|
|
534
534
|
|
|
535
|
-
def circle_from_three_points(p1, p2, p3):
|
|
535
|
+
def circle_from_three_points(p1: Any, p2: Any, p3: Any) -> tuple[Any, Any]:
|
|
536
536
|
ax, ay = p1
|
|
537
537
|
bx, by = p2
|
|
538
538
|
cx, cy = p3
|
|
@@ -565,10 +565,10 @@ def minimum_bounding_circle(
|
|
|
565
565
|
radius = np.linalg.norm(p1 - center)
|
|
566
566
|
return center, radius
|
|
567
567
|
|
|
568
|
-
def is_inside(c, r, p):
|
|
568
|
+
def is_inside(c: Any, r: Any, p: Any) -> Any:
|
|
569
569
|
return np.linalg.norm(p - c) <= r + 1e-10
|
|
570
570
|
|
|
571
|
-
def welzl(P, R):
|
|
571
|
+
def welzl(P: Any, R: Any) -> tuple[Any, Any]:
|
|
572
572
|
if len(P) == 0 or len(R) == 3:
|
|
573
573
|
if len(R) == 0:
|
|
574
574
|
return np.array([0.0, 0.0]), 0.0
|
|
@@ -5,7 +5,7 @@ This module provides Gaussian quadrature rules and numerical integration
|
|
|
5
5
|
functions commonly used in state estimation and filtering.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import Callable, Literal, Optional, Tuple
|
|
8
|
+
from typing import Any, Callable, Literal, Optional, Tuple
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
import scipy.integrate as integrate
|
|
@@ -158,7 +158,7 @@ def quad(
|
|
|
158
158
|
f: Callable[[float], float],
|
|
159
159
|
a: float,
|
|
160
160
|
b: float,
|
|
161
|
-
**kwargs,
|
|
161
|
+
**kwargs: Any,
|
|
162
162
|
) -> Tuple[float, float]:
|
|
163
163
|
"""
|
|
164
164
|
Adaptive quadrature integration.
|
|
@@ -203,7 +203,7 @@ def dblquad(
|
|
|
203
203
|
b: float,
|
|
204
204
|
gfun: Callable[[float], float],
|
|
205
205
|
hfun: Callable[[float], float],
|
|
206
|
-
**kwargs,
|
|
206
|
+
**kwargs: Any,
|
|
207
207
|
) -> Tuple[float, float]:
|
|
208
208
|
"""
|
|
209
209
|
Double integration.
|
|
@@ -248,7 +248,7 @@ def tplquad(
|
|
|
248
248
|
hfun: Callable[[float], float],
|
|
249
249
|
qfun: Callable[[float, float], float],
|
|
250
250
|
rfun: Callable[[float, float], float],
|
|
251
|
-
**kwargs,
|
|
251
|
+
**kwargs: Any,
|
|
252
252
|
) -> Tuple[float, float]:
|
|
253
253
|
"""
|
|
254
254
|
Triple integration.
|
|
@@ -290,11 +290,11 @@ def tplquad(
|
|
|
290
290
|
|
|
291
291
|
|
|
292
292
|
def fixed_quad(
|
|
293
|
-
f: Callable[[
|
|
293
|
+
f: Callable[[np.ndarray[Any, Any]], np.ndarray[Any, Any]],
|
|
294
294
|
a: float,
|
|
295
295
|
b: float,
|
|
296
296
|
n: int = 5,
|
|
297
|
-
) ->
|
|
297
|
+
) -> tuple[float, None]:
|
|
298
298
|
"""
|
|
299
299
|
Fixed-order Gaussian quadrature.
|
|
300
300
|
|
|
@@ -24,7 +24,7 @@ References
|
|
|
24
24
|
Systems, 19(4), 608-621.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
-
from typing import NamedTuple, Optional
|
|
27
|
+
from typing import Any, NamedTuple, Optional
|
|
28
28
|
|
|
29
29
|
import numpy as np
|
|
30
30
|
from numba import njit, prange
|
|
@@ -214,12 +214,12 @@ def detection_probability(
|
|
|
214
214
|
|
|
215
215
|
@njit(cache=True, fastmath=True)
|
|
216
216
|
def _cfar_ca_kernel(
|
|
217
|
-
signal: np.ndarray,
|
|
217
|
+
signal: np.ndarray[Any, Any],
|
|
218
218
|
guard_cells: int,
|
|
219
219
|
ref_cells: int,
|
|
220
220
|
alpha: float,
|
|
221
|
-
noise_estimate: np.ndarray,
|
|
222
|
-
threshold: np.ndarray,
|
|
221
|
+
noise_estimate: np.ndarray[Any, Any],
|
|
222
|
+
threshold: np.ndarray[Any, Any],
|
|
223
223
|
) -> None:
|
|
224
224
|
"""JIT-compiled CA-CFAR kernel."""
|
|
225
225
|
n = len(signal)
|
|
@@ -252,12 +252,12 @@ def _cfar_ca_kernel(
|
|
|
252
252
|
|
|
253
253
|
@njit(cache=True, fastmath=True)
|
|
254
254
|
def _cfar_go_kernel(
|
|
255
|
-
signal: np.ndarray,
|
|
255
|
+
signal: np.ndarray[Any, Any],
|
|
256
256
|
guard_cells: int,
|
|
257
257
|
ref_cells: int,
|
|
258
258
|
alpha: float,
|
|
259
|
-
noise_estimate: np.ndarray,
|
|
260
|
-
threshold: np.ndarray,
|
|
259
|
+
noise_estimate: np.ndarray[Any, Any],
|
|
260
|
+
threshold: np.ndarray[Any, Any],
|
|
261
261
|
) -> None:
|
|
262
262
|
"""JIT-compiled GO-CFAR kernel."""
|
|
263
263
|
n = len(signal)
|
|
@@ -290,12 +290,12 @@ def _cfar_go_kernel(
|
|
|
290
290
|
|
|
291
291
|
@njit(cache=True, fastmath=True)
|
|
292
292
|
def _cfar_so_kernel(
|
|
293
|
-
signal: np.ndarray,
|
|
293
|
+
signal: np.ndarray[Any, Any],
|
|
294
294
|
guard_cells: int,
|
|
295
295
|
ref_cells: int,
|
|
296
296
|
alpha: float,
|
|
297
|
-
noise_estimate: np.ndarray,
|
|
298
|
-
threshold: np.ndarray,
|
|
297
|
+
noise_estimate: np.ndarray[Any, Any],
|
|
298
|
+
threshold: np.ndarray[Any, Any],
|
|
299
299
|
) -> None:
|
|
300
300
|
"""JIT-compiled SO-CFAR kernel."""
|
|
301
301
|
n = len(signal)
|
|
@@ -331,13 +331,13 @@ def _cfar_so_kernel(
|
|
|
331
331
|
|
|
332
332
|
@njit(cache=True, fastmath=True)
|
|
333
333
|
def _cfar_os_kernel(
|
|
334
|
-
signal: np.ndarray,
|
|
334
|
+
signal: np.ndarray[Any, Any],
|
|
335
335
|
guard_cells: int,
|
|
336
336
|
ref_cells: int,
|
|
337
337
|
k: int,
|
|
338
338
|
alpha: float,
|
|
339
|
-
noise_estimate: np.ndarray,
|
|
340
|
-
threshold: np.ndarray,
|
|
339
|
+
noise_estimate: np.ndarray[Any, Any],
|
|
340
|
+
threshold: np.ndarray[Any, Any],
|
|
341
341
|
) -> None:
|
|
342
342
|
"""JIT-compiled OS-CFAR kernel."""
|
|
343
343
|
n = len(signal)
|
|
@@ -378,14 +378,14 @@ def _cfar_os_kernel(
|
|
|
378
378
|
|
|
379
379
|
@njit(cache=True, fastmath=True, parallel=True)
|
|
380
380
|
def _cfar_2d_ca_kernel(
|
|
381
|
-
image: np.ndarray,
|
|
381
|
+
image: np.ndarray[Any, Any],
|
|
382
382
|
guard_rows: int,
|
|
383
383
|
guard_cols: int,
|
|
384
384
|
ref_rows: int,
|
|
385
385
|
ref_cols: int,
|
|
386
386
|
alpha: float,
|
|
387
|
-
noise_estimate: np.ndarray,
|
|
388
|
-
threshold: np.ndarray,
|
|
387
|
+
noise_estimate: np.ndarray[Any, Any],
|
|
388
|
+
threshold: np.ndarray[Any, Any],
|
|
389
389
|
) -> None:
|
|
390
390
|
"""JIT-compiled 2D CA-CFAR kernel with parallel execution."""
|
|
391
391
|
n_rows, n_cols = image.shape
|
|
@@ -426,14 +426,14 @@ def _cfar_2d_ca_kernel(
|
|
|
426
426
|
|
|
427
427
|
@njit(cache=True, fastmath=True, parallel=True)
|
|
428
428
|
def _cfar_2d_go_kernel(
|
|
429
|
-
image: np.ndarray,
|
|
429
|
+
image: np.ndarray[Any, Any],
|
|
430
430
|
guard_rows: int,
|
|
431
431
|
guard_cols: int,
|
|
432
432
|
ref_rows: int,
|
|
433
433
|
ref_cols: int,
|
|
434
434
|
alpha: float,
|
|
435
|
-
noise_estimate: np.ndarray,
|
|
436
|
-
threshold: np.ndarray,
|
|
435
|
+
noise_estimate: np.ndarray[Any, Any],
|
|
436
|
+
threshold: np.ndarray[Any, Any],
|
|
437
437
|
) -> None:
|
|
438
438
|
"""JIT-compiled 2D GO-CFAR kernel with parallel execution."""
|
|
439
439
|
n_rows, n_cols = image.shape
|
|
@@ -478,14 +478,14 @@ def _cfar_2d_go_kernel(
|
|
|
478
478
|
|
|
479
479
|
@njit(cache=True, fastmath=True, parallel=True)
|
|
480
480
|
def _cfar_2d_so_kernel(
|
|
481
|
-
image: np.ndarray,
|
|
481
|
+
image: np.ndarray[Any, Any],
|
|
482
482
|
guard_rows: int,
|
|
483
483
|
guard_cols: int,
|
|
484
484
|
ref_rows: int,
|
|
485
485
|
ref_cols: int,
|
|
486
486
|
alpha: float,
|
|
487
|
-
noise_estimate: np.ndarray,
|
|
488
|
-
threshold: np.ndarray,
|
|
487
|
+
noise_estimate: np.ndarray[Any, Any],
|
|
488
|
+
threshold: np.ndarray[Any, Any],
|
|
489
489
|
) -> None:
|
|
490
490
|
"""JIT-compiled 2D SO-CFAR kernel with parallel execution."""
|
|
491
491
|
n_rows, n_cols = image.shape
|
|
@@ -830,8 +830,8 @@ def cfar_os(
|
|
|
830
830
|
|
|
831
831
|
def cfar_2d(
|
|
832
832
|
image: ArrayLike,
|
|
833
|
-
guard_cells: tuple,
|
|
834
|
-
ref_cells: tuple,
|
|
833
|
+
guard_cells: tuple[int, int],
|
|
834
|
+
ref_cells: tuple[int, int],
|
|
835
835
|
pfa: float = 1e-6,
|
|
836
836
|
method: str = "ca",
|
|
837
837
|
alpha: Optional[float] = None,
|