nrl-tracker 0.21.4__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.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/METADATA +57 -10
- nrl_tracker-1.7.5.dist-info/RECORD +165 -0
- pytcl/__init__.py +4 -3
- pytcl/assignment_algorithms/__init__.py +28 -0
- pytcl/assignment_algorithms/data_association.py +2 -7
- 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 +162 -8
- pytcl/astronomical/ephemerides.py +533 -0
- pytcl/astronomical/reference_frames.py +865 -56
- pytcl/astronomical/relativity.py +473 -0
- pytcl/astronomical/sgp4.py +710 -0
- pytcl/astronomical/special_orbits.py +532 -0
- pytcl/astronomical/tle.py +558 -0
- pytcl/atmosphere/__init__.py +45 -3
- 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 +28 -21
- pytcl/containers/base.py +219 -0
- pytcl/containers/cluster_set.py +2 -1
- pytcl/containers/covertree.py +26 -29
- pytcl/containers/kd_tree.py +94 -29
- pytcl/containers/measurement_set.py +1 -9
- 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 +4 -2
- 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 +15 -18
- pytcl/dynamic_estimation/kalman/__init__.py +30 -0
- pytcl/dynamic_estimation/kalman/constrained.py +382 -0
- pytcl/dynamic_estimation/kalman/extended.py +9 -12
- 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 +9 -10
- pytcl/dynamic_estimation/particle_filters/bootstrap.py +15 -15
- pytcl/dynamic_estimation/rbpf.py +589 -0
- pytcl/dynamic_estimation/smoothers.py +1 -5
- pytcl/dynamic_models/discrete_time/__init__.py +1 -5
- pytcl/dynamic_models/process_noise/__init__.py +1 -5
- 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 +10 -14
- 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/interpolation/__init__.py +2 -2
- 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/__init__.py +2 -2
- 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/__init__.py +14 -10
- pytcl/navigation/geodesy.py +246 -160
- pytcl/navigation/great_circle.py +101 -19
- pytcl/navigation/ins.py +1 -5
- 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/__init__.py +3 -14
- pytcl/trackers/hypothesis.py +1 -1
- pytcl/trackers/mht.py +9 -9
- pytcl/trackers/multi_target.py +2 -5
- nrl_tracker-0.21.4.dist-info/RECORD +0 -148
- {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/LICENSE +0 -0
- {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/WHEEL +0 -0
- {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constrained Extended Kalman Filter (CEKF).
|
|
3
|
+
|
|
4
|
+
Extends the Extended Kalman Filter to enforce constraints on the state
|
|
5
|
+
estimate. Uses Lagrange multiplier method to project onto constraint manifold
|
|
6
|
+
while maintaining positive definite covariance.
|
|
7
|
+
|
|
8
|
+
References
|
|
9
|
+
----------
|
|
10
|
+
.. [1] Simon, D. (2006). Optimal State Estimation: Kalman, H∞, and Nonlinear
|
|
11
|
+
Approaches. Wiley-Interscience.
|
|
12
|
+
.. [2] Simon, D. & Simon, D. L. (2010). Constrained Kalman filtering via
|
|
13
|
+
density function truncation. Journal of Guidance, Control, and Dynamics.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Any, Callable, Optional
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
from numpy.typing import ArrayLike, NDArray
|
|
20
|
+
|
|
21
|
+
from pytcl.dynamic_estimation.kalman.extended import ekf_predict, ekf_update
|
|
22
|
+
from pytcl.dynamic_estimation.kalman.linear import KalmanPrediction, KalmanUpdate
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConstraintFunction:
|
|
26
|
+
"""Base class for state constraints."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
g: Callable[[NDArray[Any]], NDArray[Any]],
|
|
31
|
+
G: Optional[Callable[[NDArray[Any]], NDArray[Any]]] = None,
|
|
32
|
+
constraint_type: str = "inequality",
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Define a constraint: g(x) ≤ 0 (inequality) or g(x) = 0 (equality).
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
g : callable
|
|
40
|
+
Constraint function: g(x) -> scalar or array
|
|
41
|
+
Inequality: g(x) ≤ 0
|
|
42
|
+
Equality: g(x) = 0
|
|
43
|
+
G : callable, optional
|
|
44
|
+
Jacobian of g with respect to x: ∂g/∂x
|
|
45
|
+
If None, computed numerically.
|
|
46
|
+
constraint_type : {'inequality', 'equality'}
|
|
47
|
+
Constraint type.
|
|
48
|
+
"""
|
|
49
|
+
self.g = g
|
|
50
|
+
self.G = G
|
|
51
|
+
self.constraint_type = constraint_type
|
|
52
|
+
|
|
53
|
+
def evaluate(self, x: NDArray[Any]) -> NDArray[Any]:
|
|
54
|
+
"""Evaluate constraint at state x."""
|
|
55
|
+
return np.atleast_1d(np.asarray(self.g(x), dtype=np.float64))
|
|
56
|
+
|
|
57
|
+
def jacobian(self, x: NDArray[Any]) -> NDArray[Any]:
|
|
58
|
+
"""Compute constraint Jacobian at x."""
|
|
59
|
+
if self.G is not None:
|
|
60
|
+
return np.atleast_2d(np.asarray(self.G(x), dtype=np.float64))
|
|
61
|
+
else:
|
|
62
|
+
# Numerical differentiation
|
|
63
|
+
eps = 1e-6
|
|
64
|
+
n = len(x)
|
|
65
|
+
g_x = self.evaluate(x)
|
|
66
|
+
m = len(g_x)
|
|
67
|
+
J = np.zeros((m, n))
|
|
68
|
+
for i in range(n):
|
|
69
|
+
x_plus = x.copy()
|
|
70
|
+
x_plus[i] += eps
|
|
71
|
+
g_plus = self.evaluate(x_plus)
|
|
72
|
+
J[:, i] = (g_plus - g_x) / eps
|
|
73
|
+
return J
|
|
74
|
+
|
|
75
|
+
def is_satisfied(self, x: NDArray[Any], tol: float = 1e-6) -> bool:
|
|
76
|
+
"""Check if constraint is satisfied."""
|
|
77
|
+
g_val = self.evaluate(x)
|
|
78
|
+
if self.constraint_type == "inequality":
|
|
79
|
+
return np.all(g_val <= tol)
|
|
80
|
+
else: # equality
|
|
81
|
+
return np.allclose(g_val, 0, atol=tol)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ConstrainedEKF:
|
|
85
|
+
"""
|
|
86
|
+
Extended Kalman Filter with state constraints.
|
|
87
|
+
|
|
88
|
+
Enforces linear and/or nonlinear constraints on state estimate using
|
|
89
|
+
Lagrange multiplier method with covariance projection.
|
|
90
|
+
|
|
91
|
+
Attributes
|
|
92
|
+
----------
|
|
93
|
+
constraints : list of ConstraintFunction
|
|
94
|
+
List of active constraints.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(self) -> None:
|
|
98
|
+
"""Initialize Constrained EKF."""
|
|
99
|
+
self.constraints: list[ConstraintFunction] = []
|
|
100
|
+
|
|
101
|
+
def add_constraint(self, constraint: ConstraintFunction) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Add a constraint to the filter.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
constraint : ConstraintFunction
|
|
108
|
+
Constraint to enforce.
|
|
109
|
+
"""
|
|
110
|
+
self.constraints.append(constraint)
|
|
111
|
+
|
|
112
|
+
def predict(
|
|
113
|
+
self,
|
|
114
|
+
x: ArrayLike,
|
|
115
|
+
P: ArrayLike,
|
|
116
|
+
f: Callable[[NDArray[Any]], NDArray[Any]],
|
|
117
|
+
F: ArrayLike,
|
|
118
|
+
Q: ArrayLike,
|
|
119
|
+
) -> KalmanPrediction:
|
|
120
|
+
"""
|
|
121
|
+
Constrained EKF prediction step.
|
|
122
|
+
|
|
123
|
+
Performs standard EKF prediction (constraints not enforced here,
|
|
124
|
+
only checked). Constraint enforcement happens in update step.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
x : array_like
|
|
129
|
+
Current state estimate, shape (n,).
|
|
130
|
+
P : array_like
|
|
131
|
+
Current state covariance, shape (n, n).
|
|
132
|
+
f : callable
|
|
133
|
+
Nonlinear state transition function.
|
|
134
|
+
F : array_like
|
|
135
|
+
Jacobian of f at current state.
|
|
136
|
+
Q : array_like
|
|
137
|
+
Process noise covariance, shape (n, n).
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
result : KalmanPrediction
|
|
142
|
+
Predicted state and covariance.
|
|
143
|
+
"""
|
|
144
|
+
return ekf_predict(x, P, f, F, Q)
|
|
145
|
+
|
|
146
|
+
def update(
|
|
147
|
+
self,
|
|
148
|
+
x: ArrayLike,
|
|
149
|
+
P: ArrayLike,
|
|
150
|
+
z: ArrayLike,
|
|
151
|
+
h: Callable[[NDArray[Any]], NDArray[Any]],
|
|
152
|
+
H: ArrayLike,
|
|
153
|
+
R: ArrayLike,
|
|
154
|
+
) -> KalmanUpdate:
|
|
155
|
+
"""
|
|
156
|
+
Constrained EKF update step.
|
|
157
|
+
|
|
158
|
+
Performs standard EKF update, then projects result onto constraint
|
|
159
|
+
manifold.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
x : array_like
|
|
164
|
+
Predicted state estimate, shape (n,).
|
|
165
|
+
P : array_like
|
|
166
|
+
Predicted state covariance, shape (n, n).
|
|
167
|
+
z : array_like
|
|
168
|
+
Measurement, shape (m,).
|
|
169
|
+
h : callable
|
|
170
|
+
Nonlinear measurement function.
|
|
171
|
+
H : array_like
|
|
172
|
+
Jacobian of h at current state.
|
|
173
|
+
R : array_like
|
|
174
|
+
Measurement noise covariance, shape (m, m).
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
result : KalmanUpdate
|
|
179
|
+
Updated state and covariance (constrained).
|
|
180
|
+
"""
|
|
181
|
+
# Standard EKF update
|
|
182
|
+
result = ekf_update(x, P, z, h, H, R)
|
|
183
|
+
x_upd = result.x
|
|
184
|
+
P_upd = result.P
|
|
185
|
+
|
|
186
|
+
# Apply constraint projection
|
|
187
|
+
if self.constraints:
|
|
188
|
+
x_upd, P_upd = self._project_onto_constraints(x_upd, P_upd)
|
|
189
|
+
|
|
190
|
+
return KalmanUpdate(
|
|
191
|
+
x=x_upd,
|
|
192
|
+
P=P_upd,
|
|
193
|
+
y=result.y,
|
|
194
|
+
S=result.S,
|
|
195
|
+
K=result.K,
|
|
196
|
+
likelihood=result.likelihood,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def _project_onto_constraints(
|
|
200
|
+
self,
|
|
201
|
+
x: NDArray[Any],
|
|
202
|
+
P: NDArray[Any],
|
|
203
|
+
max_iter: int = 10,
|
|
204
|
+
tol: float = 1e-6,
|
|
205
|
+
) -> tuple[NDArray[Any], NDArray[Any]]:
|
|
206
|
+
"""
|
|
207
|
+
Project state and covariance onto constraint manifold.
|
|
208
|
+
|
|
209
|
+
Uses iterative Lagrange multiplier method with covariance
|
|
210
|
+
projection to enforce constraints while maintaining positive
|
|
211
|
+
definiteness.
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
x : ndarray
|
|
216
|
+
State estimate, shape (n,).
|
|
217
|
+
P : ndarray
|
|
218
|
+
Covariance matrix, shape (n, n).
|
|
219
|
+
max_iter : int
|
|
220
|
+
Maximum iterations for constraint projection.
|
|
221
|
+
tol : float
|
|
222
|
+
Convergence tolerance.
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
x_proj : ndarray
|
|
227
|
+
Constrained state estimate.
|
|
228
|
+
P_proj : ndarray
|
|
229
|
+
Projected covariance.
|
|
230
|
+
"""
|
|
231
|
+
x_proj = x.copy()
|
|
232
|
+
P_proj = P.copy()
|
|
233
|
+
|
|
234
|
+
# Check which constraints are violated
|
|
235
|
+
violated: list[ConstraintFunction] = [
|
|
236
|
+
c for c in self.constraints if not c.is_satisfied(x_proj)
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
if not violated:
|
|
240
|
+
return x_proj, P_proj
|
|
241
|
+
|
|
242
|
+
# Iterative projection
|
|
243
|
+
for iteration in range(max_iter):
|
|
244
|
+
converged = True
|
|
245
|
+
|
|
246
|
+
for constraint in violated:
|
|
247
|
+
g_val = constraint.evaluate(x_proj)
|
|
248
|
+
G = constraint.jacobian(x_proj)
|
|
249
|
+
|
|
250
|
+
# Only process violated constraints
|
|
251
|
+
if constraint.constraint_type == "inequality":
|
|
252
|
+
mask = g_val > tol
|
|
253
|
+
else:
|
|
254
|
+
mask = np.abs(g_val) > tol
|
|
255
|
+
|
|
256
|
+
if not np.any(mask):
|
|
257
|
+
continue
|
|
258
|
+
|
|
259
|
+
converged = False
|
|
260
|
+
|
|
261
|
+
# Lagrange multiplier for this constraint
|
|
262
|
+
# λ = -(G P Gᵀ + μ)⁻¹ G (x - x_ref)
|
|
263
|
+
# where x_ref is desired state (we use x)
|
|
264
|
+
|
|
265
|
+
GP = G @ P_proj
|
|
266
|
+
GPGt = GP @ G.T
|
|
267
|
+
|
|
268
|
+
# Add small regularization for numerical stability
|
|
269
|
+
mu = np.eye(GPGt.shape[0]) * 1e-6
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
GPGt_inv = np.linalg.inv(GPGt + mu)
|
|
273
|
+
lam = -GPGt_inv @ (G @ x_proj + g_val)
|
|
274
|
+
|
|
275
|
+
# State correction
|
|
276
|
+
x_corr = P_proj @ G.T @ lam
|
|
277
|
+
x_proj = x_proj + x_corr
|
|
278
|
+
|
|
279
|
+
# Covariance projection
|
|
280
|
+
# P_proj = P - P G^T (G P G^T)^{-1} G P
|
|
281
|
+
P_proj = P_proj - GP.T @ GPGt_inv @ GP
|
|
282
|
+
|
|
283
|
+
# Ensure symmetry
|
|
284
|
+
P_proj = (P_proj + P_proj.T) / 2
|
|
285
|
+
|
|
286
|
+
# Enforce positive definiteness
|
|
287
|
+
eigvals, eigvecs = np.linalg.eigh(P_proj)
|
|
288
|
+
if np.any(eigvals < 0):
|
|
289
|
+
eigvals[eigvals < 1e-10] = 1e-10
|
|
290
|
+
P_proj = eigvecs @ np.diag(eigvals) @ eigvecs.T
|
|
291
|
+
|
|
292
|
+
except np.linalg.LinAlgError:
|
|
293
|
+
# If inversion fails, use pseudoinverse
|
|
294
|
+
GPGt_pinv = np.linalg.pinv(GPGt)
|
|
295
|
+
lam = -GPGt_pinv @ (G @ x_proj + g_val)
|
|
296
|
+
x_proj = x_proj + P_proj @ G.T @ lam
|
|
297
|
+
|
|
298
|
+
if converged:
|
|
299
|
+
break
|
|
300
|
+
|
|
301
|
+
return x_proj, P_proj
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def constrained_ekf_predict(
|
|
305
|
+
x: ArrayLike,
|
|
306
|
+
P: ArrayLike,
|
|
307
|
+
f: Callable[[NDArray[Any]], NDArray[Any]],
|
|
308
|
+
F: ArrayLike,
|
|
309
|
+
Q: ArrayLike,
|
|
310
|
+
) -> KalmanPrediction:
|
|
311
|
+
"""
|
|
312
|
+
Convenience function for constrained EKF prediction.
|
|
313
|
+
|
|
314
|
+
Parameters
|
|
315
|
+
----------
|
|
316
|
+
x : array_like
|
|
317
|
+
Current state estimate.
|
|
318
|
+
P : array_like
|
|
319
|
+
Current covariance.
|
|
320
|
+
f : callable
|
|
321
|
+
Nonlinear dynamics function.
|
|
322
|
+
F : array_like
|
|
323
|
+
Jacobian of f.
|
|
324
|
+
Q : array_like
|
|
325
|
+
Process noise covariance.
|
|
326
|
+
|
|
327
|
+
Returns
|
|
328
|
+
-------
|
|
329
|
+
result : KalmanPrediction
|
|
330
|
+
Predicted state and covariance.
|
|
331
|
+
"""
|
|
332
|
+
cekf = ConstrainedEKF()
|
|
333
|
+
return cekf.predict(x, P, f, F, Q)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def constrained_ekf_update(
|
|
337
|
+
x: ArrayLike,
|
|
338
|
+
P: ArrayLike,
|
|
339
|
+
z: ArrayLike,
|
|
340
|
+
h: Callable[[NDArray[Any]], NDArray[Any]],
|
|
341
|
+
H: ArrayLike,
|
|
342
|
+
R: ArrayLike,
|
|
343
|
+
constraints: Optional[list[ConstraintFunction]] = None,
|
|
344
|
+
) -> KalmanUpdate:
|
|
345
|
+
"""
|
|
346
|
+
Convenience function for constrained EKF update.
|
|
347
|
+
|
|
348
|
+
Parameters
|
|
349
|
+
----------
|
|
350
|
+
x : array_like
|
|
351
|
+
Predicted state.
|
|
352
|
+
P : array_like
|
|
353
|
+
Predicted covariance.
|
|
354
|
+
z : array_like
|
|
355
|
+
Measurement.
|
|
356
|
+
h : callable
|
|
357
|
+
Nonlinear measurement function.
|
|
358
|
+
H : array_like
|
|
359
|
+
Jacobian of h.
|
|
360
|
+
R : array_like
|
|
361
|
+
Measurement noise covariance.
|
|
362
|
+
constraints : list, optional
|
|
363
|
+
List of ConstraintFunction objects.
|
|
364
|
+
|
|
365
|
+
Returns
|
|
366
|
+
-------
|
|
367
|
+
result : KalmanUpdate
|
|
368
|
+
Updated state and covariance.
|
|
369
|
+
"""
|
|
370
|
+
cekf = ConstrainedEKF()
|
|
371
|
+
if constraints:
|
|
372
|
+
for c in constraints:
|
|
373
|
+
cekf.add_constraint(c)
|
|
374
|
+
return cekf.update(x, P, z, h, H, R)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
__all__ = [
|
|
378
|
+
"ConstraintFunction",
|
|
379
|
+
"ConstrainedEKF",
|
|
380
|
+
"constrained_ekf_predict",
|
|
381
|
+
"constrained_ekf_update",
|
|
382
|
+
]
|
|
@@ -5,21 +5,18 @@ The EKF handles nonlinear dynamics and/or measurements by linearizing
|
|
|
5
5
|
around the current state estimate using Jacobians.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import Callable
|
|
8
|
+
from typing import Any, Callable
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
from numpy.typing import ArrayLike, NDArray
|
|
12
12
|
|
|
13
|
-
from pytcl.dynamic_estimation.kalman.linear import
|
|
14
|
-
KalmanPrediction,
|
|
15
|
-
KalmanUpdate,
|
|
16
|
-
)
|
|
13
|
+
from pytcl.dynamic_estimation.kalman.linear import KalmanPrediction, KalmanUpdate
|
|
17
14
|
|
|
18
15
|
|
|
19
16
|
def ekf_predict(
|
|
20
17
|
x: ArrayLike,
|
|
21
18
|
P: ArrayLike,
|
|
22
|
-
f: Callable[[NDArray], NDArray],
|
|
19
|
+
f: Callable[[NDArray[Any]], NDArray[Any]],
|
|
23
20
|
F: ArrayLike,
|
|
24
21
|
Q: ArrayLike,
|
|
25
22
|
) -> KalmanPrediction:
|
|
@@ -89,7 +86,7 @@ def ekf_update(
|
|
|
89
86
|
x: ArrayLike,
|
|
90
87
|
P: ArrayLike,
|
|
91
88
|
z: ArrayLike,
|
|
92
|
-
h: Callable[[NDArray], NDArray],
|
|
89
|
+
h: Callable[[NDArray[Any]], NDArray[Any]],
|
|
93
90
|
H: ArrayLike,
|
|
94
91
|
R: ArrayLike,
|
|
95
92
|
) -> KalmanUpdate:
|
|
@@ -186,7 +183,7 @@ def ekf_update(
|
|
|
186
183
|
|
|
187
184
|
|
|
188
185
|
def numerical_jacobian(
|
|
189
|
-
f: Callable[[NDArray], NDArray],
|
|
186
|
+
f: Callable[[NDArray[Any]], NDArray[Any]],
|
|
190
187
|
x: ArrayLike,
|
|
191
188
|
dx: float = 1e-7,
|
|
192
189
|
) -> NDArray[np.floating]:
|
|
@@ -242,7 +239,7 @@ def numerical_jacobian(
|
|
|
242
239
|
def ekf_predict_auto(
|
|
243
240
|
x: ArrayLike,
|
|
244
241
|
P: ArrayLike,
|
|
245
|
-
f: Callable[[NDArray], NDArray],
|
|
242
|
+
f: Callable[[NDArray[Any]], NDArray[Any]],
|
|
246
243
|
Q: ArrayLike,
|
|
247
244
|
dx: float = 1e-7,
|
|
248
245
|
) -> KalmanPrediction:
|
|
@@ -276,7 +273,7 @@ def ekf_update_auto(
|
|
|
276
273
|
x: ArrayLike,
|
|
277
274
|
P: ArrayLike,
|
|
278
275
|
z: ArrayLike,
|
|
279
|
-
h: Callable[[NDArray], NDArray],
|
|
276
|
+
h: Callable[[NDArray[Any]], NDArray[Any]],
|
|
280
277
|
R: ArrayLike,
|
|
281
278
|
dx: float = 1e-7,
|
|
282
279
|
) -> KalmanUpdate:
|
|
@@ -312,8 +309,8 @@ def iterated_ekf_update(
|
|
|
312
309
|
x: ArrayLike,
|
|
313
310
|
P: ArrayLike,
|
|
314
311
|
z: ArrayLike,
|
|
315
|
-
h: Callable[[NDArray], NDArray],
|
|
316
|
-
H_func: Callable[[NDArray], NDArray],
|
|
312
|
+
h: Callable[[NDArray[Any]], NDArray[Any]],
|
|
313
|
+
H_func: Callable[[NDArray[Any]], NDArray[Any]],
|
|
317
314
|
R: ArrayLike,
|
|
318
315
|
max_iter: int = 10,
|
|
319
316
|
tol: float = 1e-6,
|