nrl-tracker 0.22.5__py3-none-any.whl → 1.7.5__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.7.5.dist-info}/METADATA +57 -10
- {nrl_tracker-0.22.5.dist-info → nrl_tracker-1.7.5.dist-info}/RECORD +84 -69
- pytcl/__init__.py +4 -3
- pytcl/assignment_algorithms/__init__.py +28 -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 +371 -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.7.5.dist-info}/LICENSE +0 -0
- {nrl_tracker-0.22.5.dist-info → nrl_tracker-1.7.5.dist-info}/WHEEL +0 -0
- {nrl_tracker-0.22.5.dist-info → nrl_tracker-1.7.5.dist-info}/top_level.txt +0 -0
|
@@ -165,11 +165,13 @@ def ecef2geodetic(
|
|
|
165
165
|
N = a / np.sqrt(1 - e2 * sin_lat**2)
|
|
166
166
|
|
|
167
167
|
# Altitude
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
# Use cos_lat when available, otherwise use sin_lat with guard against division by zero
|
|
169
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
170
|
+
alt = np.where(
|
|
171
|
+
np.abs(cos_lat) > 1e-10,
|
|
172
|
+
p / cos_lat - N,
|
|
173
|
+
np.abs(z) / np.abs(sin_lat) - N * (1 - e2),
|
|
174
|
+
)
|
|
173
175
|
else:
|
|
174
176
|
# Direct/closed-form method (simplified Vermeille)
|
|
175
177
|
zp = np.abs(z)
|
|
@@ -525,6 +527,267 @@ def ned2enu(ned: ArrayLike) -> NDArray[np.floating]:
|
|
|
525
527
|
return np.array([ned[1], ned[0], -ned[2]], dtype=np.float64)
|
|
526
528
|
|
|
527
529
|
|
|
530
|
+
def geodetic2sez(
|
|
531
|
+
lat: ArrayLike,
|
|
532
|
+
lon: ArrayLike,
|
|
533
|
+
alt: ArrayLike,
|
|
534
|
+
lat_ref: float,
|
|
535
|
+
lon_ref: float,
|
|
536
|
+
alt_ref: float,
|
|
537
|
+
a: float = WGS84.a,
|
|
538
|
+
f: float = WGS84.f,
|
|
539
|
+
) -> NDArray[np.floating]:
|
|
540
|
+
"""
|
|
541
|
+
Convert geodetic coordinates to local SEZ (South-East-Zenith) coordinates.
|
|
542
|
+
|
|
543
|
+
SEZ is a horizon-relative coordinate frame where:
|
|
544
|
+
- S (South) points in the southward direction
|
|
545
|
+
- E (East) points in the eastward direction
|
|
546
|
+
- Z (Zenith) points upward (away from Earth center)
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
lat : array_like
|
|
551
|
+
Geodetic latitude in radians.
|
|
552
|
+
lon : array_like
|
|
553
|
+
Geodetic longitude in radians.
|
|
554
|
+
alt : array_like
|
|
555
|
+
Altitude in meters.
|
|
556
|
+
lat_ref : float
|
|
557
|
+
Reference point latitude in radians.
|
|
558
|
+
lon_ref : float
|
|
559
|
+
Reference point longitude in radians.
|
|
560
|
+
alt_ref : float
|
|
561
|
+
Reference point altitude in meters.
|
|
562
|
+
a : float, optional
|
|
563
|
+
Semi-major axis of the reference ellipsoid.
|
|
564
|
+
f : float, optional
|
|
565
|
+
Flattening of the reference ellipsoid.
|
|
566
|
+
|
|
567
|
+
Returns
|
|
568
|
+
-------
|
|
569
|
+
sez : ndarray
|
|
570
|
+
Local SEZ coordinates [south, east, zenith] in meters.
|
|
571
|
+
|
|
572
|
+
See Also
|
|
573
|
+
--------
|
|
574
|
+
sez2geodetic : Inverse conversion.
|
|
575
|
+
ecef2sez : ECEF to SEZ conversion.
|
|
576
|
+
|
|
577
|
+
Notes
|
|
578
|
+
-----
|
|
579
|
+
SEZ is equivalent to NED when azimuth is measured from south.
|
|
580
|
+
Conversion: SEZ = [S, E, Z] = [NED[0], NED[1], -NED[2]]
|
|
581
|
+
|
|
582
|
+
Examples
|
|
583
|
+
--------
|
|
584
|
+
>>> sez = geodetic2sez(lat, lon, alt, lat_ref, lon_ref, alt_ref)
|
|
585
|
+
"""
|
|
586
|
+
# Convert both to ECEF
|
|
587
|
+
ecef = geodetic2ecef(lat, lon, alt, a, f)
|
|
588
|
+
ecef_ref = geodetic2ecef(lat_ref, lon_ref, alt_ref, a, f)
|
|
589
|
+
|
|
590
|
+
# Get SEZ from ECEF difference
|
|
591
|
+
return ecef2sez(ecef, lat_ref, lon_ref, ecef_ref)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def ecef2sez(
|
|
595
|
+
ecef: ArrayLike,
|
|
596
|
+
lat_ref: float,
|
|
597
|
+
lon_ref: float,
|
|
598
|
+
ecef_ref: Optional[ArrayLike] = None,
|
|
599
|
+
) -> NDArray[np.floating]:
|
|
600
|
+
"""
|
|
601
|
+
Convert ECEF coordinates to local SEZ coordinates.
|
|
602
|
+
|
|
603
|
+
Parameters
|
|
604
|
+
----------
|
|
605
|
+
ecef : array_like
|
|
606
|
+
ECEF coordinates [X, Y, Z] in meters, shape (3,) or (3, N).
|
|
607
|
+
lat_ref : float
|
|
608
|
+
Reference point latitude in radians.
|
|
609
|
+
lon_ref : float
|
|
610
|
+
Reference point longitude in radians.
|
|
611
|
+
ecef_ref : array_like, optional
|
|
612
|
+
Reference ECEF position. If None, the reference point is
|
|
613
|
+
at (lat_ref, lon_ref) with zero altitude.
|
|
614
|
+
|
|
615
|
+
Returns
|
|
616
|
+
-------
|
|
617
|
+
sez : ndarray
|
|
618
|
+
SEZ coordinates [south, east, zenith] in meters.
|
|
619
|
+
|
|
620
|
+
See Also
|
|
621
|
+
--------
|
|
622
|
+
sez2ecef : Inverse conversion.
|
|
623
|
+
"""
|
|
624
|
+
ecef = np.asarray(ecef, dtype=np.float64)
|
|
625
|
+
|
|
626
|
+
if ecef_ref is None:
|
|
627
|
+
ecef_ref = geodetic2ecef(lat_ref, lon_ref, 0.0)
|
|
628
|
+
else:
|
|
629
|
+
ecef_ref = np.asarray(ecef_ref, dtype=np.float64)
|
|
630
|
+
|
|
631
|
+
# Relative position in ECEF
|
|
632
|
+
if ecef.ndim == 1:
|
|
633
|
+
delta_ecef = ecef - ecef_ref
|
|
634
|
+
else:
|
|
635
|
+
if ecef.shape[0] != 3:
|
|
636
|
+
ecef = ecef.T
|
|
637
|
+
if ecef_ref.ndim == 1:
|
|
638
|
+
delta_ecef = ecef - ecef_ref[:, np.newaxis]
|
|
639
|
+
else:
|
|
640
|
+
delta_ecef = ecef - ecef_ref[:, np.newaxis]
|
|
641
|
+
|
|
642
|
+
# Rotation matrix from ECEF to SEZ
|
|
643
|
+
sin_lat = np.sin(lat_ref)
|
|
644
|
+
cos_lat = np.cos(lat_ref)
|
|
645
|
+
sin_lon = np.sin(lon_ref)
|
|
646
|
+
cos_lon = np.cos(lon_ref)
|
|
647
|
+
|
|
648
|
+
# SEZ rotation matrix (transforms ECEF delta to SEZ)
|
|
649
|
+
# S = -sin(lat)*cos(lon)*dX - sin(lat)*sin(lon)*dY + cos(lat)*dZ
|
|
650
|
+
# E = -sin(lon)*dX + cos(lon)*dY
|
|
651
|
+
# Z = cos(lat)*cos(lon)*dX + cos(lat)*sin(lon)*dY + sin(lat)*dZ
|
|
652
|
+
|
|
653
|
+
if delta_ecef.ndim == 1:
|
|
654
|
+
s = (
|
|
655
|
+
-sin_lat * cos_lon * delta_ecef[0]
|
|
656
|
+
- sin_lat * sin_lon * delta_ecef[1]
|
|
657
|
+
+ cos_lat * delta_ecef[2]
|
|
658
|
+
)
|
|
659
|
+
e = -sin_lon * delta_ecef[0] + cos_lon * delta_ecef[1]
|
|
660
|
+
z = (
|
|
661
|
+
cos_lat * cos_lon * delta_ecef[0]
|
|
662
|
+
+ cos_lat * sin_lon * delta_ecef[1]
|
|
663
|
+
+ sin_lat * delta_ecef[2]
|
|
664
|
+
)
|
|
665
|
+
return np.array([s, e, z], dtype=np.float64)
|
|
666
|
+
else:
|
|
667
|
+
s = (
|
|
668
|
+
-sin_lat * cos_lon * delta_ecef[0, :]
|
|
669
|
+
- sin_lat * sin_lon * delta_ecef[1, :]
|
|
670
|
+
+ cos_lat * delta_ecef[2, :]
|
|
671
|
+
)
|
|
672
|
+
e = -sin_lon * delta_ecef[0, :] + cos_lon * delta_ecef[1, :]
|
|
673
|
+
z = (
|
|
674
|
+
cos_lat * cos_lon * delta_ecef[0, :]
|
|
675
|
+
+ cos_lat * sin_lon * delta_ecef[1, :]
|
|
676
|
+
+ sin_lat * delta_ecef[2, :]
|
|
677
|
+
)
|
|
678
|
+
return np.array([s, e, z], dtype=np.float64)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def sez2ecef(
|
|
682
|
+
sez: ArrayLike,
|
|
683
|
+
lat_ref: float,
|
|
684
|
+
lon_ref: float,
|
|
685
|
+
ecef_ref: Optional[ArrayLike] = None,
|
|
686
|
+
) -> NDArray[np.floating]:
|
|
687
|
+
"""
|
|
688
|
+
Convert local SEZ coordinates to ECEF coordinates.
|
|
689
|
+
|
|
690
|
+
Parameters
|
|
691
|
+
----------
|
|
692
|
+
sez : array_like
|
|
693
|
+
SEZ coordinates [south, east, zenith] in meters, shape (3,) or (3, N).
|
|
694
|
+
lat_ref : float
|
|
695
|
+
Reference point latitude in radians.
|
|
696
|
+
lon_ref : float
|
|
697
|
+
Reference point longitude in radians.
|
|
698
|
+
ecef_ref : array_like, optional
|
|
699
|
+
Reference ECEF position. If None, the reference point is
|
|
700
|
+
at (lat_ref, lon_ref) with zero altitude.
|
|
701
|
+
|
|
702
|
+
Returns
|
|
703
|
+
-------
|
|
704
|
+
ecef : ndarray
|
|
705
|
+
ECEF coordinates [X, Y, Z] in meters.
|
|
706
|
+
|
|
707
|
+
See Also
|
|
708
|
+
--------
|
|
709
|
+
ecef2sez : Forward conversion.
|
|
710
|
+
"""
|
|
711
|
+
sez = np.asarray(sez, dtype=np.float64)
|
|
712
|
+
|
|
713
|
+
if ecef_ref is None:
|
|
714
|
+
ecef_ref = geodetic2ecef(lat_ref, lon_ref, 0.0)
|
|
715
|
+
else:
|
|
716
|
+
ecef_ref = np.asarray(ecef_ref, dtype=np.float64)
|
|
717
|
+
|
|
718
|
+
# Rotation matrix from SEZ to ECEF (transpose of ECEF to SEZ)
|
|
719
|
+
sin_lat = np.sin(lat_ref)
|
|
720
|
+
cos_lat = np.cos(lat_ref)
|
|
721
|
+
sin_lon = np.sin(lon_ref)
|
|
722
|
+
cos_lon = np.cos(lon_ref)
|
|
723
|
+
|
|
724
|
+
# Inverse rotation: ECEF = ECEF_ref + R_inv @ SEZ
|
|
725
|
+
if sez.ndim == 1:
|
|
726
|
+
dX = -sin_lat * cos_lon * sez[0] - sin_lon * sez[1] + cos_lat * cos_lon * sez[2]
|
|
727
|
+
dY = -sin_lat * sin_lon * sez[0] + cos_lon * sez[1] + cos_lat * sin_lon * sez[2]
|
|
728
|
+
dZ = cos_lat * sez[0] + sin_lat * sez[2]
|
|
729
|
+
return ecef_ref + np.array([dX, dY, dZ], dtype=np.float64)
|
|
730
|
+
else:
|
|
731
|
+
if sez.shape[0] != 3:
|
|
732
|
+
sez = sez.T
|
|
733
|
+
dX = (
|
|
734
|
+
-sin_lat * cos_lon * sez[0, :]
|
|
735
|
+
- sin_lon * sez[1, :]
|
|
736
|
+
+ cos_lat * cos_lon * sez[2, :]
|
|
737
|
+
)
|
|
738
|
+
dY = (
|
|
739
|
+
-sin_lat * sin_lon * sez[0, :]
|
|
740
|
+
+ cos_lon * sez[1, :]
|
|
741
|
+
+ cos_lat * sin_lon * sez[2, :]
|
|
742
|
+
)
|
|
743
|
+
dZ = cos_lat * sez[0, :] + sin_lat * sez[2, :]
|
|
744
|
+
return ecef_ref[:, np.newaxis] + np.array([dX, dY, dZ], dtype=np.float64)
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def sez2geodetic(
|
|
748
|
+
sez: ArrayLike,
|
|
749
|
+
lat_ref: float,
|
|
750
|
+
lon_ref: float,
|
|
751
|
+
alt_ref: float,
|
|
752
|
+
a: float = WGS84.a,
|
|
753
|
+
f: float = WGS84.f,
|
|
754
|
+
) -> Tuple[NDArray[np.floating], NDArray[np.floating], NDArray[np.floating]]:
|
|
755
|
+
"""
|
|
756
|
+
Convert local SEZ coordinates to geodetic coordinates.
|
|
757
|
+
|
|
758
|
+
Parameters
|
|
759
|
+
----------
|
|
760
|
+
sez : array_like
|
|
761
|
+
SEZ coordinates [south, east, zenith] in meters.
|
|
762
|
+
lat_ref : float
|
|
763
|
+
Reference point latitude in radians.
|
|
764
|
+
lon_ref : float
|
|
765
|
+
Reference point longitude in radians.
|
|
766
|
+
alt_ref : float
|
|
767
|
+
Reference point altitude in meters.
|
|
768
|
+
a : float, optional
|
|
769
|
+
Semi-major axis.
|
|
770
|
+
f : float, optional
|
|
771
|
+
Flattening.
|
|
772
|
+
|
|
773
|
+
Returns
|
|
774
|
+
-------
|
|
775
|
+
lat : ndarray
|
|
776
|
+
Geodetic latitude in radians.
|
|
777
|
+
lon : ndarray
|
|
778
|
+
Geodetic longitude in radians.
|
|
779
|
+
alt : ndarray
|
|
780
|
+
Altitude in meters.
|
|
781
|
+
|
|
782
|
+
See Also
|
|
783
|
+
--------
|
|
784
|
+
geodetic2sez : Forward conversion.
|
|
785
|
+
"""
|
|
786
|
+
ecef_ref = geodetic2ecef(lat_ref, lon_ref, alt_ref, a, f)
|
|
787
|
+
ecef = sez2ecef(sez, lat_ref, lon_ref, ecef_ref)
|
|
788
|
+
return ecef2geodetic(ecef, a, f)
|
|
789
|
+
|
|
790
|
+
|
|
528
791
|
def geocentric_radius(
|
|
529
792
|
lat: ArrayLike,
|
|
530
793
|
a: float = WGS84.a,
|
|
@@ -625,6 +888,10 @@ __all__ = [
|
|
|
625
888
|
"ned2ecef",
|
|
626
889
|
"enu2ned",
|
|
627
890
|
"ned2enu",
|
|
891
|
+
"geodetic2sez",
|
|
892
|
+
"ecef2sez",
|
|
893
|
+
"sez2ecef",
|
|
894
|
+
"sez2geodetic",
|
|
628
895
|
"geocentric_radius",
|
|
629
896
|
"prime_vertical_radius",
|
|
630
897
|
"meridional_radius",
|
|
@@ -6,7 +6,7 @@ coordinate transformations, essential for error propagation in tracking
|
|
|
6
6
|
filters (e.g., converting measurement covariances between coordinate systems).
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from typing import Literal
|
|
9
|
+
from typing import Callable, Literal
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
from numpy.typing import ArrayLike, NDArray
|
|
@@ -431,7 +431,7 @@ def cross_covariance_transform(
|
|
|
431
431
|
|
|
432
432
|
|
|
433
433
|
def numerical_jacobian(
|
|
434
|
-
func,
|
|
434
|
+
func: Callable[[ArrayLike], ArrayLike],
|
|
435
435
|
x: ArrayLike,
|
|
436
436
|
dx: float = 1e-7,
|
|
437
437
|
) -> NDArray[np.floating]:
|
|
@@ -27,7 +27,7 @@ Examples
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
from pytcl.coordinate_systems.projections.projections import (
|
|
30
|
-
WGS84_A, # Constants; Result types
|
|
30
|
+
WGS84_A, # Constants; Result types
|
|
31
31
|
)
|
|
32
32
|
from pytcl.coordinate_systems.projections.projections import (
|
|
33
33
|
WGS84_B,
|
|
@@ -25,7 +25,7 @@ References
|
|
|
25
25
|
nanometers." Journal of Geodesy 85.8 (2011): 475-485.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
from typing import NamedTuple, Optional, Tuple
|
|
28
|
+
from typing import Any, NamedTuple, Optional, Tuple
|
|
29
29
|
|
|
30
30
|
import numpy as np
|
|
31
31
|
from numpy.typing import NDArray
|
|
@@ -1253,7 +1253,7 @@ def geodetic2utm_batch(
|
|
|
1253
1253
|
lats: NDArray[np.floating],
|
|
1254
1254
|
lons: NDArray[np.floating],
|
|
1255
1255
|
zone: Optional[int] = None,
|
|
1256
|
-
) ->
|
|
1256
|
+
) -> tuple[NDArray[np.floating], NDArray[np.floating], NDArray[np.intp], NDArray[Any]]:
|
|
1257
1257
|
"""
|
|
1258
1258
|
Batch convert geodetic coordinates to UTM.
|
|
1259
1259
|
|
|
@@ -6,7 +6,7 @@ representations including rotation matrices, quaternions, Euler angles,
|
|
|
6
6
|
axis-angle, and Rodrigues parameters.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from typing import Tuple
|
|
9
|
+
from typing import Any, Tuple
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
from numba import njit
|
|
@@ -14,7 +14,7 @@ from numpy.typing import ArrayLike, NDArray
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@njit(cache=True, fastmath=True)
|
|
17
|
-
def _rotx_inplace(angle: float, R: np.ndarray) -> None:
|
|
17
|
+
def _rotx_inplace(angle: float, R: np.ndarray[Any, Any]) -> None:
|
|
18
18
|
"""JIT-compiled rotation about x-axis (fills existing matrix)."""
|
|
19
19
|
c = np.cos(angle)
|
|
20
20
|
s = np.sin(angle)
|
|
@@ -30,7 +30,7 @@ def _rotx_inplace(angle: float, R: np.ndarray) -> None:
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@njit(cache=True, fastmath=True)
|
|
33
|
-
def _roty_inplace(angle: float, R: np.ndarray) -> None:
|
|
33
|
+
def _roty_inplace(angle: float, R: np.ndarray[Any, Any]) -> None:
|
|
34
34
|
"""JIT-compiled rotation about y-axis (fills existing matrix)."""
|
|
35
35
|
c = np.cos(angle)
|
|
36
36
|
s = np.sin(angle)
|
|
@@ -46,7 +46,7 @@ def _roty_inplace(angle: float, R: np.ndarray) -> None:
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
@njit(cache=True, fastmath=True)
|
|
49
|
-
def _rotz_inplace(angle: float, R: np.ndarray) -> None:
|
|
49
|
+
def _rotz_inplace(angle: float, R: np.ndarray[Any, Any]) -> None:
|
|
50
50
|
"""JIT-compiled rotation about z-axis (fills existing matrix)."""
|
|
51
51
|
c = np.cos(angle)
|
|
52
52
|
s = np.sin(angle)
|
|
@@ -62,7 +62,9 @@ def _rotz_inplace(angle: float, R: np.ndarray) -> None:
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
@njit(cache=True, fastmath=True)
|
|
65
|
-
def _euler_zyx_to_rotmat(
|
|
65
|
+
def _euler_zyx_to_rotmat(
|
|
66
|
+
yaw: float, pitch: float, roll: float, R: np.ndarray[Any, Any]
|
|
67
|
+
) -> None:
|
|
66
68
|
"""JIT-compiled ZYX Euler angles to rotation matrix."""
|
|
67
69
|
cy = np.cos(yaw)
|
|
68
70
|
sy = np.sin(yaw)
|
|
@@ -84,7 +86,9 @@ def _euler_zyx_to_rotmat(yaw: float, pitch: float, roll: float, R: np.ndarray) -
|
|
|
84
86
|
|
|
85
87
|
|
|
86
88
|
@njit(cache=True, fastmath=True)
|
|
87
|
-
def _matmul_3x3(
|
|
89
|
+
def _matmul_3x3(
|
|
90
|
+
A: np.ndarray[Any, Any], B: np.ndarray[Any, Any], C: np.ndarray[Any, Any]
|
|
91
|
+
) -> None:
|
|
88
92
|
"""JIT-compiled 3x3 matrix multiplication C = A @ B."""
|
|
89
93
|
for i in range(3):
|
|
90
94
|
for j in range(3):
|
pytcl/core/__init__.py
CHANGED
|
@@ -24,10 +24,19 @@ from pytcl.core.constants import (
|
|
|
24
24
|
PhysicalConstants,
|
|
25
25
|
)
|
|
26
26
|
from pytcl.core.validation import (
|
|
27
|
+
ArraySpec,
|
|
28
|
+
ScalarSpec,
|
|
29
|
+
ValidationError,
|
|
30
|
+
check_compatible_shapes,
|
|
27
31
|
ensure_2d,
|
|
28
32
|
ensure_column_vector,
|
|
33
|
+
ensure_positive_definite,
|
|
29
34
|
ensure_row_vector,
|
|
35
|
+
ensure_square_matrix,
|
|
36
|
+
ensure_symmetric,
|
|
30
37
|
validate_array,
|
|
38
|
+
validate_inputs,
|
|
39
|
+
validate_same_shape,
|
|
31
40
|
)
|
|
32
41
|
|
|
33
42
|
__all__ = [
|
|
@@ -40,10 +49,19 @@ __all__ = [
|
|
|
40
49
|
"WGS84",
|
|
41
50
|
"PhysicalConstants",
|
|
42
51
|
# Validation
|
|
52
|
+
"ValidationError",
|
|
43
53
|
"validate_array",
|
|
54
|
+
"validate_inputs",
|
|
55
|
+
"validate_same_shape",
|
|
56
|
+
"check_compatible_shapes",
|
|
57
|
+
"ArraySpec",
|
|
58
|
+
"ScalarSpec",
|
|
44
59
|
"ensure_2d",
|
|
45
60
|
"ensure_column_vector",
|
|
46
61
|
"ensure_row_vector",
|
|
62
|
+
"ensure_square_matrix",
|
|
63
|
+
"ensure_symmetric",
|
|
64
|
+
"ensure_positive_definite",
|
|
47
65
|
# Array utilities
|
|
48
66
|
"wrap_to_pi",
|
|
49
67
|
"wrap_to_2pi",
|