nrl-tracker 0.21.5__py3-none-any.whl → 0.22.1__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 (84) hide show
  1. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/METADATA +2 -2
  2. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/RECORD +84 -82
  3. pytcl/__init__.py +1 -1
  4. pytcl/assignment_algorithms/data_association.py +2 -7
  5. pytcl/assignment_algorithms/gating.py +3 -3
  6. pytcl/assignment_algorithms/jpda.py +4 -12
  7. pytcl/assignment_algorithms/two_dimensional/kbest.py +1 -3
  8. pytcl/astronomical/__init__.py +60 -7
  9. pytcl/astronomical/ephemerides.py +522 -0
  10. pytcl/astronomical/lambert.py +4 -14
  11. pytcl/astronomical/orbital_mechanics.py +1 -3
  12. pytcl/astronomical/reference_frames.py +1 -3
  13. pytcl/astronomical/relativity.py +466 -0
  14. pytcl/atmosphere/__init__.py +2 -2
  15. pytcl/atmosphere/models.py +1 -3
  16. pytcl/clustering/gaussian_mixture.py +4 -8
  17. pytcl/clustering/hierarchical.py +1 -5
  18. pytcl/clustering/kmeans.py +1 -3
  19. pytcl/containers/__init__.py +4 -21
  20. pytcl/containers/cluster_set.py +4 -19
  21. pytcl/containers/measurement_set.py +3 -15
  22. pytcl/containers/rtree.py +2 -5
  23. pytcl/coordinate_systems/conversions/geodetic.py +3 -15
  24. pytcl/coordinate_systems/projections/__init__.py +4 -2
  25. pytcl/coordinate_systems/projections/projections.py +11 -38
  26. pytcl/coordinate_systems/rotations/rotations.py +1 -3
  27. pytcl/core/array_utils.py +1 -4
  28. pytcl/core/constants.py +1 -3
  29. pytcl/core/validation.py +6 -17
  30. pytcl/dynamic_estimation/imm.py +4 -13
  31. pytcl/dynamic_estimation/kalman/extended.py +1 -4
  32. pytcl/dynamic_estimation/kalman/square_root.py +2 -6
  33. pytcl/dynamic_estimation/kalman/unscented.py +1 -4
  34. pytcl/dynamic_estimation/particle_filters/bootstrap.py +2 -6
  35. pytcl/dynamic_estimation/smoothers.py +2 -8
  36. pytcl/dynamic_models/discrete_time/__init__.py +1 -5
  37. pytcl/dynamic_models/process_noise/__init__.py +1 -5
  38. pytcl/dynamic_models/process_noise/polynomial.py +2 -6
  39. pytcl/gravity/clenshaw.py +2 -6
  40. pytcl/gravity/egm.py +1 -3
  41. pytcl/gravity/models.py +1 -3
  42. pytcl/gravity/spherical_harmonics.py +4 -10
  43. pytcl/gravity/tides.py +7 -16
  44. pytcl/magnetism/__init__.py +3 -14
  45. pytcl/magnetism/emm.py +3 -12
  46. pytcl/magnetism/wmm.py +2 -9
  47. pytcl/mathematical_functions/basic_matrix/decompositions.py +1 -3
  48. pytcl/mathematical_functions/combinatorics/combinatorics.py +1 -3
  49. pytcl/mathematical_functions/geometry/geometry.py +4 -12
  50. pytcl/mathematical_functions/interpolation/__init__.py +2 -2
  51. pytcl/mathematical_functions/interpolation/interpolation.py +1 -3
  52. pytcl/mathematical_functions/signal_processing/detection.py +3 -6
  53. pytcl/mathematical_functions/signal_processing/filters.py +2 -6
  54. pytcl/mathematical_functions/signal_processing/matched_filter.py +2 -4
  55. pytcl/mathematical_functions/special_functions/__init__.py +2 -2
  56. pytcl/mathematical_functions/special_functions/elliptic.py +1 -3
  57. pytcl/mathematical_functions/special_functions/gamma_functions.py +1 -3
  58. pytcl/mathematical_functions/special_functions/lambert_w.py +1 -3
  59. pytcl/mathematical_functions/statistics/distributions.py +12 -36
  60. pytcl/mathematical_functions/statistics/estimators.py +1 -3
  61. pytcl/mathematical_functions/transforms/stft.py +3 -9
  62. pytcl/mathematical_functions/transforms/wavelets.py +6 -18
  63. pytcl/navigation/__init__.py +14 -10
  64. pytcl/navigation/geodesy.py +9 -31
  65. pytcl/navigation/great_circle.py +4 -12
  66. pytcl/navigation/ins.py +3 -13
  67. pytcl/navigation/ins_gnss.py +7 -25
  68. pytcl/navigation/rhumb.py +3 -10
  69. pytcl/performance_evaluation/track_metrics.py +1 -3
  70. pytcl/plotting/coordinates.py +5 -17
  71. pytcl/plotting/metrics.py +3 -9
  72. pytcl/plotting/tracks.py +3 -11
  73. pytcl/static_estimation/least_squares.py +1 -2
  74. pytcl/static_estimation/maximum_likelihood.py +3 -3
  75. pytcl/terrain/dem.py +2 -8
  76. pytcl/terrain/loaders.py +5 -13
  77. pytcl/terrain/visibility.py +2 -7
  78. pytcl/trackers/__init__.py +3 -14
  79. pytcl/trackers/hypothesis.py +2 -7
  80. pytcl/trackers/mht.py +5 -15
  81. pytcl/trackers/multi_target.py +1 -4
  82. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/LICENSE +0 -0
  83. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/WHEEL +0 -0
  84. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/top_level.txt +0 -0
pytcl/magnetism/emm.py CHANGED
@@ -531,8 +531,7 @@ def _high_res_field_spherical(
531
531
  factor = np.sqrt((n - m) * (n + m + 1))
532
532
  if m + 1 <= n:
533
533
  dP[n, m] = (
534
- n * cos_theta / sin_theta * P[n, m]
535
- - factor * P[n, m + 1] / sin_theta
534
+ n * cos_theta / sin_theta * P[n, m] - factor * P[n, m + 1] / sin_theta
536
535
  if m + 1 <= n_max_eval
537
536
  else n * cos_theta / sin_theta * P[n, m]
538
537
  )
@@ -559,13 +558,7 @@ def _high_res_field_spherical(
559
558
  B_theta += -r_power * dP[n, m] * (gnm * cos_m_lon + hnm * sin_m_lon)
560
559
 
561
560
  if abs(sin_theta) > 1e-10:
562
- B_phi += (
563
- r_power
564
- * m
565
- * P[n, m]
566
- / sin_theta
567
- * (gnm * sin_m_lon - hnm * cos_m_lon)
568
- )
561
+ B_phi += r_power * m * P[n, m] / sin_theta * (gnm * sin_m_lon - hnm * cos_m_lon)
569
562
 
570
563
  return B_r, B_theta, B_phi
571
564
 
@@ -624,9 +617,7 @@ def emm(
624
617
  r = a + h
625
618
 
626
619
  # Compute field in spherical coordinates
627
- B_r, B_theta, B_phi = _high_res_field_spherical(
628
- lat_gc, lon, r, year, coefficients, n_max
629
- )
620
+ B_r, B_theta, B_phi = _high_res_field_spherical(lat_gc, lon, r, year, coefficients, n_max)
630
621
 
631
622
  # Convert to geodetic coordinates
632
623
  X = -B_theta # North
pytcl/magnetism/wmm.py CHANGED
@@ -468,8 +468,7 @@ def magnetic_field_spherical(
468
468
  factor = np.sqrt((n - m) * (n + m + 1))
469
469
  if m + 1 <= n:
470
470
  dP[n, m] = (
471
- n * cos_theta / sin_theta * P[n, m]
472
- - factor * P[n, m + 1] / sin_theta
471
+ n * cos_theta / sin_theta * P[n, m] - factor * P[n, m + 1] / sin_theta
473
472
  if m + 1 <= n_max
474
473
  else n * cos_theta / sin_theta * P[n, m]
475
474
  )
@@ -499,13 +498,7 @@ def magnetic_field_spherical(
499
498
  B_theta += -r_power * dP[n, m] * (gnm * cos_m_lon + hnm * sin_m_lon)
500
499
 
501
500
  if abs(sin_theta) > 1e-10:
502
- B_phi += (
503
- r_power
504
- * m
505
- * P[n, m]
506
- / sin_theta
507
- * (gnm * sin_m_lon - hnm * cos_m_lon)
508
- )
501
+ B_phi += r_power * m * P[n, m] / sin_theta * (gnm * sin_m_lon - hnm * cos_m_lon)
509
502
 
510
503
  return B_r, B_theta, B_phi
511
504
 
@@ -160,9 +160,7 @@ def tria_sqrt(
160
160
  if B is not None:
161
161
  B = np.asarray(B, dtype=np.float64)
162
162
  if A.shape[0] != B.shape[0]:
163
- raise ValueError(
164
- f"A and B must have same number of rows: {A.shape[0]} vs {B.shape[0]}"
165
- )
163
+ raise ValueError(f"A and B must have same number of rows: {A.shape[0]} vs {B.shape[0]}")
166
164
  combined = np.hstack([A, B])
167
165
  else:
168
166
  combined = A
@@ -378,9 +378,7 @@ def partitions(n: int, k: Optional[int] = None) -> Iterator[Tuple[int, ...]]:
378
378
  [(4,), (3, 1), (2, 2), (2, 1, 1), (1, 1, 1, 1)]
379
379
  """
380
380
 
381
- def gen_partitions(
382
- n: int, max_val: int, prefix: Tuple[int, ...]
383
- ) -> Iterator[Tuple[int, ...]]:
381
+ def gen_partitions(n: int, max_val: int, prefix: Tuple[int, ...]) -> Iterator[Tuple[int, ...]]:
384
382
  if n == 0:
385
383
  yield prefix
386
384
  return
@@ -191,12 +191,8 @@ def polygon_centroid(vertices: ArrayLike) -> NDArray[np.floating]:
191
191
 
192
192
  # Centroid
193
193
  factor = 1.0 / (3.0 * a)
194
- cx = factor * np.sum(
195
- (x + np.roll(x, -1)) * (x * np.roll(y, -1) - np.roll(x, -1) * y)
196
- )
197
- cy = factor * np.sum(
198
- (y + np.roll(y, -1)) * (x * np.roll(y, -1) - np.roll(x, -1) * y)
199
- )
194
+ cx = factor * np.sum((x + np.roll(x, -1)) * (x * np.roll(y, -1) - np.roll(x, -1) * y))
195
+ cy = factor * np.sum((y + np.roll(y, -1)) * (x * np.roll(y, -1) - np.roll(x, -1) * y))
200
196
 
201
197
  return np.array([cx, cy], dtype=np.float64)
202
198
 
@@ -551,14 +547,10 @@ def minimum_bounding_circle(
551
547
  return circle_from_two_points(p1, p3)
552
548
 
553
549
  ux = (
554
- (ax**2 + ay**2) * (by - cy)
555
- + (bx**2 + by**2) * (cy - ay)
556
- + (cx**2 + cy**2) * (ay - by)
550
+ (ax**2 + ay**2) * (by - cy) + (bx**2 + by**2) * (cy - ay) + (cx**2 + cy**2) * (ay - by)
557
551
  ) / d
558
552
  uy = (
559
- (ax**2 + ay**2) * (cx - bx)
560
- + (bx**2 + by**2) * (ax - cx)
561
- + (cx**2 + cy**2) * (bx - ax)
553
+ (ax**2 + ay**2) * (cx - bx) + (bx**2 + by**2) * (ax - cx) + (cx**2 + cy**2) * (bx - ax)
562
554
  ) / d
563
555
 
564
556
  center = np.array([ux, uy])
@@ -8,8 +8,8 @@ This module provides:
8
8
  - Spherical interpolation
9
9
  """
10
10
 
11
- from pytcl.mathematical_functions.interpolation.interpolation import ( # noqa: E501
12
- akima,
11
+ from pytcl.mathematical_functions.interpolation.interpolation import akima # noqa: E501
12
+ from pytcl.mathematical_functions.interpolation.interpolation import (
13
13
  barycentric,
14
14
  cubic_spline,
15
15
  interp1d,
@@ -371,9 +371,7 @@ def rbf_interpolate(
371
371
  points = np.asarray(points, dtype=np.float64)
372
372
  values = np.asarray(values, dtype=np.float64)
373
373
 
374
- return interpolate.RBFInterpolator(
375
- points, values, kernel=kernel, smoothing=smoothing
376
- )
374
+ return interpolate.RBFInterpolator(points, values, kernel=kernel, smoothing=smoothing)
377
375
 
378
376
 
379
377
  def barycentric(
@@ -410,8 +410,7 @@ def _cfar_2d_ca_kernel(
410
410
  for ri in range(row_min, row_max):
411
411
  for ci in range(col_min, col_max):
412
412
  if not (
413
- guard_row_min <= ri < guard_row_max
414
- and guard_col_min <= ci < guard_col_max
413
+ guard_row_min <= ri < guard_row_max and guard_col_min <= ci < guard_col_max
415
414
  ):
416
415
  ref_sum += image[ri, ci]
417
416
  n_cells += 1
@@ -460,8 +459,7 @@ def _cfar_2d_go_kernel(
460
459
  for ri in range(row_min, row_max):
461
460
  for ci in range(col_min, col_max):
462
461
  if not (
463
- guard_row_min <= ri < guard_row_max
464
- and guard_col_min <= ci < guard_col_max
462
+ guard_row_min <= ri < guard_row_max and guard_col_min <= ci < guard_col_max
465
463
  ):
466
464
  if ri < i:
467
465
  top_sum += image[ri, ci]
@@ -512,8 +510,7 @@ def _cfar_2d_so_kernel(
512
510
  for ri in range(row_min, row_max):
513
511
  for ci in range(col_min, col_max):
514
512
  if not (
515
- guard_row_min <= ri < guard_row_max
516
- and guard_col_min <= ci < guard_col_max
513
+ guard_row_min <= ri < guard_row_max and guard_col_min <= ci < guard_col_max
517
514
  ):
518
515
  if ri < i:
519
516
  top_sum += image[ri, ci]
@@ -606,13 +606,9 @@ def filtfilt(
606
606
 
607
607
  if isinstance(coeffs, FilterCoefficients):
608
608
  if coeffs.sos is not None:
609
- return scipy_signal.sosfiltfilt(
610
- coeffs.sos, x, padtype=padtype, padlen=padlen
611
- )
609
+ return scipy_signal.sosfiltfilt(coeffs.sos, x, padtype=padtype, padlen=padlen)
612
610
  else:
613
- return scipy_signal.filtfilt(
614
- coeffs.b, coeffs.a, x, padtype=padtype, padlen=padlen
615
- )
611
+ return scipy_signal.filtfilt(coeffs.b, coeffs.a, x, padtype=padtype, padlen=padlen)
616
612
  elif isinstance(coeffs, tuple) and len(coeffs) == 2:
617
613
  b, a = coeffs
618
614
  return scipy_signal.filtfilt(b, a, x, padtype=padtype, padlen=padlen)
@@ -590,8 +590,7 @@ def _ambiguity_function_kernel(
590
590
  for k in range(n_signal - delay_samples):
591
591
  s1 = signal[k]
592
592
  s2_conj = (
593
- shifted[delay_samples + k].real
594
- - 1j * shifted[delay_samples + k].imag
593
+ shifted[delay_samples + k].real - 1j * shifted[delay_samples + k].imag
595
594
  )
596
595
  result += s1 * s2_conj
597
596
 
@@ -638,8 +637,7 @@ def _cross_ambiguity_kernel(
638
637
  for k in range(n_signal - delay_samples):
639
638
  s1 = signal1[k]
640
639
  s2_conj = (
641
- shifted[delay_samples + k].real
642
- - 1j * shifted[delay_samples + k].imag
640
+ shifted[delay_samples + k].real - 1j * shifted[delay_samples + k].imag
643
641
  )
644
642
  result += s1 * s2_conj
645
643
 
@@ -40,8 +40,8 @@ from pytcl.mathematical_functions.special_functions.debye import (
40
40
  debye_entropy,
41
41
  debye_heat_capacity,
42
42
  )
43
- from pytcl.mathematical_functions.special_functions.elliptic import ( # noqa: E501
44
- ellipe,
43
+ from pytcl.mathematical_functions.special_functions.elliptic import ellipe # noqa: E501
44
+ from pytcl.mathematical_functions.special_functions.elliptic import (
45
45
  ellipeinc,
46
46
  ellipk,
47
47
  ellipkinc,
@@ -243,9 +243,7 @@ def elliprg(x: ArrayLike, y: ArrayLike, z: ArrayLike) -> NDArray[np.floating]:
243
243
  return np.asarray(sp.elliprg(x, y, z), dtype=np.float64)
244
244
 
245
245
 
246
- def elliprj(
247
- x: ArrayLike, y: ArrayLike, z: ArrayLike, p: ArrayLike
248
- ) -> NDArray[np.floating]:
246
+ def elliprj(x: ArrayLike, y: ArrayLike, z: ArrayLike, p: ArrayLike) -> NDArray[np.floating]:
249
247
  """
250
248
  Carlson symmetric elliptic integral R_J.
251
249
 
@@ -421,9 +421,7 @@ def comb(
421
421
  --------
422
422
  scipy.special.comb : Combinations.
423
423
  """
424
- return np.asarray(
425
- sp.comb(n, k, exact=exact, repetition=repetition), dtype=np.float64
426
- )
424
+ return np.asarray(sp.comb(n, k, exact=exact, repetition=repetition), dtype=np.float64)
427
425
 
428
426
 
429
427
  def perm(n: ArrayLike, k: ArrayLike, exact: bool = False) -> NDArray:
@@ -108,9 +108,7 @@ def lambert_w_real(
108
108
  raise ValueError(f"For branch 0, x must be >= -1/e ≈ {branch_point:.6f}")
109
109
  elif branch == -1:
110
110
  if np.any((x < branch_point) | (x >= 0)):
111
- raise ValueError(
112
- f"For branch -1, x must be in [-1/e, 0) ≈ [{branch_point:.6f}, 0)"
113
- )
111
+ raise ValueError(f"For branch -1, x must be in [-1/e, 0) ≈ [{branch_point:.6f}, 0)")
114
112
  else:
115
113
  raise ValueError(f"branch must be 0 or -1, got {branch}")
116
114
 
@@ -43,9 +43,7 @@ class Distribution(ABC):
43
43
  pass
44
44
 
45
45
  @abstractmethod
46
- def sample(
47
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
48
- ) -> NDArray[np.floating]:
46
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
49
47
  """Generate random samples."""
50
48
  pass
51
49
 
@@ -104,9 +102,7 @@ class Gaussian(Distribution):
104
102
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
105
103
  return np.asarray(self._dist.ppf(q), dtype=np.float64)
106
104
 
107
- def sample(
108
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
109
- ) -> NDArray[np.floating]:
105
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
110
106
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
111
107
 
112
108
  def mean(self) -> float:
@@ -167,9 +163,7 @@ class MultivariateGaussian(Distribution):
167
163
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
168
164
  raise NotImplementedError("PPF not available for multivariate normal")
169
165
 
170
- def sample(
171
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
172
- ) -> NDArray[np.floating]:
166
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
173
167
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
174
168
 
175
169
  def mean(self) -> NDArray[np.floating]:
@@ -239,9 +233,7 @@ class Uniform(Distribution):
239
233
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
240
234
  return np.asarray(self._dist.ppf(q), dtype=np.float64)
241
235
 
242
- def sample(
243
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
244
- ) -> NDArray[np.floating]:
236
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
245
237
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
246
238
 
247
239
  def mean(self) -> float:
@@ -279,9 +271,7 @@ class Exponential(Distribution):
279
271
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
280
272
  return np.asarray(self._dist.ppf(q), dtype=np.float64)
281
273
 
282
- def sample(
283
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
284
- ) -> NDArray[np.floating]:
274
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
285
275
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
286
276
 
287
277
  def mean(self) -> float:
@@ -347,9 +337,7 @@ class Gamma(Distribution):
347
337
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
348
338
  return np.asarray(self._dist.ppf(q), dtype=np.float64)
349
339
 
350
- def sample(
351
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
352
- ) -> NDArray[np.floating]:
340
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
353
341
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
354
342
 
355
343
  def mean(self) -> float:
@@ -387,9 +375,7 @@ class ChiSquared(Distribution):
387
375
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
388
376
  return np.asarray(self._dist.ppf(q), dtype=np.float64)
389
377
 
390
- def sample(
391
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
392
- ) -> NDArray[np.floating]:
378
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
393
379
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
394
380
 
395
381
  def mean(self) -> float:
@@ -436,9 +422,7 @@ class StudentT(Distribution):
436
422
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
437
423
  return np.asarray(self._dist.ppf(q), dtype=np.float64)
438
424
 
439
- def sample(
440
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
441
- ) -> NDArray[np.floating]:
425
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
442
426
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
443
427
 
444
428
  def mean(self) -> float:
@@ -485,9 +469,7 @@ class Beta(Distribution):
485
469
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
486
470
  return np.asarray(self._dist.ppf(q), dtype=np.float64)
487
471
 
488
- def sample(
489
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
490
- ) -> NDArray[np.floating]:
472
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
491
473
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
492
474
 
493
475
  def mean(self) -> float:
@@ -528,9 +510,7 @@ class Poisson(Distribution):
528
510
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
529
511
  return np.asarray(self._dist.ppf(q), dtype=np.float64)
530
512
 
531
- def sample(
532
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
533
- ) -> NDArray[np.floating]:
513
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
534
514
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
535
515
 
536
516
  def mean(self) -> float:
@@ -573,9 +553,7 @@ class VonMises(Distribution):
573
553
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
574
554
  return np.asarray(self._dist.ppf(q), dtype=np.float64)
575
555
 
576
- def sample(
577
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
578
- ) -> NDArray[np.floating]:
556
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
579
557
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
580
558
 
581
559
  def mean(self) -> float:
@@ -628,9 +606,7 @@ class Wishart(Distribution):
628
606
  def ppf(self, q: ArrayLike) -> NDArray[np.floating]:
629
607
  raise NotImplementedError("PPF not available for Wishart distribution")
630
608
 
631
- def sample(
632
- self, size: Optional[Union[int, Tuple[int, ...]]] = None
633
- ) -> NDArray[np.floating]:
609
+ def sample(self, size: Optional[Union[int, Tuple[int, ...]]] = None) -> NDArray[np.floating]:
634
610
  return np.asarray(self._dist.rvs(size=size), dtype=np.float64)
635
611
 
636
612
  def mean(self) -> NDArray[np.floating]:
@@ -364,9 +364,7 @@ def kurtosis(
364
364
  """
365
365
  from scipy.stats import kurtosis as scipy_kurtosis
366
366
 
367
- return np.asarray(
368
- scipy_kurtosis(x, axis=axis, fisher=fisher, bias=bias), dtype=np.float64
369
- )
367
+ return np.asarray(scipy_kurtosis(x, axis=axis, fisher=fisher, bias=bias), dtype=np.float64)
370
368
 
371
369
 
372
370
  def moment(
@@ -503,12 +503,8 @@ def reassigned_spectrogram(
503
503
  win_d = np.gradient(win)
504
504
 
505
505
  # STFT with modified windows
506
- result_t = stft(
507
- x, fs=fs, window=win_t, nperseg=nperseg, noverlap=noverlap, nfft=nfft
508
- )
509
- result_d = stft(
510
- x, fs=fs, window=win_d, nperseg=nperseg, noverlap=noverlap, nfft=nfft
511
- )
506
+ result_t = stft(x, fs=fs, window=win_t, nperseg=nperseg, noverlap=noverlap, nfft=nfft)
507
+ result_d = stft(x, fs=fs, window=win_d, nperseg=nperseg, noverlap=noverlap, nfft=nfft)
512
508
 
513
509
  # Compute reassigned coordinates
514
510
  Zxx = result1.Zxx
@@ -592,9 +588,7 @@ def mel_spectrogram(
592
588
  noverlap = nperseg // 4
593
589
 
594
590
  # Compute linear spectrogram
595
- spec_result = spectrogram(
596
- x, fs=fs, window=window, nperseg=nperseg, noverlap=noverlap
597
- )
591
+ spec_result = spectrogram(x, fs=fs, window=window, nperseg=nperseg, noverlap=noverlap)
598
592
 
599
593
  # Create mel filterbank
600
594
  mel_fb = _mel_filterbank(
@@ -526,9 +526,7 @@ def dwt(
526
526
  - 'biorN.M': Biorthogonal wavelets
527
527
  """
528
528
  if not PYWT_AVAILABLE:
529
- raise ImportError(
530
- "pywavelets is required for DWT. Install with: pip install pywavelets"
531
- )
529
+ raise ImportError("pywavelets is required for DWT. Install with: pip install pywavelets")
532
530
 
533
531
  signal = np.asarray(signal, dtype=np.float64)
534
532
 
@@ -579,9 +577,7 @@ def idwt(
579
577
  True
580
578
  """
581
579
  if not PYWT_AVAILABLE:
582
- raise ImportError(
583
- "pywavelets is required for IDWT. Install with: pip install pywavelets"
584
- )
580
+ raise ImportError("pywavelets is required for IDWT. Install with: pip install pywavelets")
585
581
 
586
582
  # Reconstruct coeffs list in pywt format
587
583
  # [cA_n, cD_n, cD_n-1, ..., cD_1]
@@ -617,9 +613,7 @@ def dwt_single_level(
617
613
  Detail coefficients.
618
614
  """
619
615
  if not PYWT_AVAILABLE:
620
- raise ImportError(
621
- "pywavelets is required for DWT. Install with: pip install pywavelets"
622
- )
616
+ raise ImportError("pywavelets is required for DWT. Install with: pip install pywavelets")
623
617
 
624
618
  signal = np.asarray(signal, dtype=np.float64)
625
619
  cA, cD = pywt.dwt(signal, wavelet, mode=mode)
@@ -653,9 +647,7 @@ def idwt_single_level(
653
647
  Reconstructed signal.
654
648
  """
655
649
  if not PYWT_AVAILABLE:
656
- raise ImportError(
657
- "pywavelets is required for IDWT. Install with: pip install pywavelets"
658
- )
650
+ raise ImportError("pywavelets is required for IDWT. Install with: pip install pywavelets")
659
651
 
660
652
  cA = np.asarray(cA, dtype=np.float64)
661
653
  cD = np.asarray(cD, dtype=np.float64)
@@ -709,9 +701,7 @@ def wpt(
709
701
  True
710
702
  """
711
703
  if not PYWT_AVAILABLE:
712
- raise ImportError(
713
- "pywavelets is required for WPT. Install with: pip install pywavelets"
714
- )
704
+ raise ImportError("pywavelets is required for WPT. Install with: pip install pywavelets")
715
705
 
716
706
  signal = np.asarray(signal, dtype=np.float64)
717
707
 
@@ -817,9 +807,7 @@ def threshold_coefficients(
817
807
  Thresholded coefficients.
818
808
  """
819
809
  if not PYWT_AVAILABLE:
820
- raise ImportError(
821
- "pywavelets is required. Install with: pip install pywavelets"
822
- )
810
+ raise ImportError("pywavelets is required. Install with: pip install pywavelets")
823
811
 
824
812
  # Estimate noise from finest detail coefficients
825
813
  if value is None:
@@ -11,8 +11,10 @@ needed in tracking applications, including:
11
11
  - Rhumb line navigation
12
12
  """
13
13
 
14
- from pytcl.navigation.geodesy import ( # Ellipsoids; Coordinate conversions; Geodetic problems
15
- GRS80,
14
+ from pytcl.navigation.geodesy import (
15
+ GRS80, # Ellipsoids; Coordinate conversions; Geodetic problems
16
+ )
17
+ from pytcl.navigation.geodesy import (
16
18
  SPHERE,
17
19
  WGS84,
18
20
  Ellipsoid,
@@ -26,8 +28,8 @@ from pytcl.navigation.geodesy import ( # Ellipsoids; Coordinate conversions; Ge
26
28
  inverse_geodetic,
27
29
  ned_to_ecef,
28
30
  )
29
- from pytcl.navigation.great_circle import ( # Great circle navigation
30
- EARTH_RADIUS,
31
+ from pytcl.navigation.great_circle import EARTH_RADIUS # Great circle navigation
32
+ from pytcl.navigation.great_circle import (
31
33
  CrossTrackResult,
32
34
  GreatCircleResult,
33
35
  IntersectionResult,
@@ -45,8 +47,10 @@ from pytcl.navigation.great_circle import ( # Great circle navigation
45
47
  great_circle_waypoint,
46
48
  great_circle_waypoints,
47
49
  )
48
- from pytcl.navigation.ins import ( # Constants; State representation; Gravity and Earth rate
49
- A_EARTH,
50
+ from pytcl.navigation.ins import (
51
+ A_EARTH, # Constants; State representation; Gravity and Earth rate
52
+ )
53
+ from pytcl.navigation.ins import (
50
54
  B_EARTH,
51
55
  E2_EARTH,
52
56
  F_EARTH,
@@ -73,8 +77,8 @@ from pytcl.navigation.ins import ( # Constants; State representation; Gravity a
73
77
  update_attitude_ned,
74
78
  update_quaternion,
75
79
  )
76
- from pytcl.navigation.ins_gnss import ( # INS/GNSS integration
77
- GPS_L1_FREQ,
80
+ from pytcl.navigation.ins_gnss import GPS_L1_FREQ # INS/GNSS integration
81
+ from pytcl.navigation.ins_gnss import (
78
82
  GPS_L1_WAVELENGTH,
79
83
  SPEED_OF_LIGHT,
80
84
  GNSSMeasurement,
@@ -99,8 +103,8 @@ from pytcl.navigation.ins_gnss import ( # INS/GNSS integration
99
103
  tight_coupled_update,
100
104
  velocity_measurement_matrix,
101
105
  )
102
- from pytcl.navigation.rhumb import ( # Rhumb line navigation
103
- RhumbDirectResult,
106
+ from pytcl.navigation.rhumb import RhumbDirectResult # Rhumb line navigation
107
+ from pytcl.navigation.rhumb import (
104
108
  RhumbIntersectionResult,
105
109
  RhumbResult,
106
110
  compare_great_circle_rhumb,
@@ -356,9 +356,7 @@ def ned_to_ecef(
356
356
  x, y, z : ndarray
357
357
  ECEF coordinates in meters.
358
358
  """
359
- return enu_to_ecef(
360
- east, north, -np.asarray(down), lat_ref, lon_ref, alt_ref, ellipsoid
361
- )
359
+ return enu_to_ecef(east, north, -np.asarray(down), lat_ref, lon_ref, alt_ref, ellipsoid)
362
360
 
363
361
 
364
362
  def direct_geodetic(
@@ -436,11 +434,7 @@ def direct_geodetic(
436
434
  / 4
437
435
  * (
438
436
  cos_sigma * (-1 + 2 * cos_2sigma_m**2)
439
- - B
440
- / 6
441
- * cos_2sigma_m
442
- * (-3 + 4 * sin_sigma**2)
443
- * (-3 + 4 * cos_2sigma_m**2)
437
+ - B / 6 * cos_2sigma_m * (-3 + 4 * sin_sigma**2) * (-3 + 4 * cos_2sigma_m**2)
444
438
  )
445
439
  )
446
440
  )
@@ -458,27 +452,20 @@ def direct_geodetic(
458
452
  lat2 = np.arctan2(
459
453
  sin_U2,
460
454
  (1 - f)
461
- * np.sqrt(
462
- sin_alpha**2 + (sin_U1 * sin_sigma - cos_U1 * cos_sigma * cos_alpha1) ** 2
463
- ),
455
+ * np.sqrt(sin_alpha**2 + (sin_U1 * sin_sigma - cos_U1 * cos_sigma * cos_alpha1) ** 2),
464
456
  )
465
457
 
466
- lam = np.arctan2(
467
- sin_sigma * sin_alpha1, cos_U1 * cos_sigma - sin_U1 * sin_sigma * cos_alpha1
468
- )
458
+ lam = np.arctan2(sin_sigma * sin_alpha1, cos_U1 * cos_sigma - sin_U1 * sin_sigma * cos_alpha1)
469
459
 
470
460
  C = f / 16 * cos2_alpha * (4 + f * (4 - 3 * cos2_alpha))
471
461
  L = lam - (1 - C) * f * sin_alpha * (
472
- sigma
473
- + C * sin_sigma * (cos_2sigma_m + C * cos_sigma * (-1 + 2 * cos_2sigma_m**2))
462
+ sigma + C * sin_sigma * (cos_2sigma_m + C * cos_sigma * (-1 + 2 * cos_2sigma_m**2))
474
463
  )
475
464
 
476
465
  lon2 = lon1 + L
477
466
 
478
467
  # Back azimuth
479
- azimuth2 = np.arctan2(
480
- sin_alpha, -sin_U1 * sin_sigma + cos_U1 * cos_sigma * cos_alpha1
481
- )
468
+ azimuth2 = np.arctan2(sin_alpha, -sin_U1 * sin_sigma + cos_U1 * cos_sigma * cos_alpha1)
482
469
 
483
470
  return float(lat2), float(lon2), float(azimuth2)
484
471
 
@@ -565,10 +552,7 @@ def inverse_geodetic(
565
552
  C = f / 16 * cos2_alpha * (4 + f * (4 - 3 * cos2_alpha))
566
553
 
567
554
  lam_new = L + (1 - C) * f * sin_alpha * (
568
- sigma
569
- + C
570
- * sin_sigma
571
- * (cos_2sigma_m + C * cos_sigma * (-1 + 2 * cos_2sigma_m**2))
555
+ sigma + C * sin_sigma * (cos_2sigma_m + C * cos_sigma * (-1 + 2 * cos_2sigma_m**2))
572
556
  )
573
557
 
574
558
  if abs(lam_new - lam) < 1e-12:
@@ -588,11 +572,7 @@ def inverse_geodetic(
588
572
  / 4
589
573
  * (
590
574
  cos_sigma * (-1 + 2 * cos_2sigma_m**2)
591
- - B
592
- / 6
593
- * cos_2sigma_m
594
- * (-3 + 4 * sin_sigma**2)
595
- * (-3 + 4 * cos_2sigma_m**2)
575
+ - B / 6 * cos_2sigma_m * (-3 + 4 * sin_sigma**2) * (-3 + 4 * cos_2sigma_m**2)
596
576
  )
597
577
  )
598
578
  )
@@ -601,9 +581,7 @@ def inverse_geodetic(
601
581
 
602
582
  # Azimuths
603
583
  azimuth1 = np.arctan2(cos_U2 * sin_lam, cos_U1 * sin_U2 - sin_U1 * cos_U2 * cos_lam)
604
- azimuth2 = np.arctan2(
605
- cos_U1 * sin_lam, -sin_U1 * cos_U2 + cos_U1 * sin_U2 * cos_lam
606
- )
584
+ azimuth2 = np.arctan2(cos_U1 * sin_lam, -sin_U1 * cos_U2 + cos_U1 * sin_U2 * cos_lam)
607
585
 
608
586
  return float(distance), float(azimuth1), float(azimuth2)
609
587