Skip to content

Commit f428877

Browse files
committed
outline for solve_gnss_ekf
1 parent 183aec2 commit f428877

7 files changed

Lines changed: 247 additions & 151 deletions

File tree

docs/source/reference/test_algorithms/modules.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ algorithms
44
.. toctree::
55
:maxdepth: 4
66

7-
test_gnss_ekf
7+
test_gnss_filters
88
test_residuals
99
test_snapshot

docs/source/reference/test_algorithms/test_gnss_ekf.rst

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
test\_gnss\_filters module
2+
==========================
3+
4+
.. automodule:: test_gnss_filters
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

gnss_lib_py/algorithms/gnss_filters.py

Lines changed: 133 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,98 @@
22
33
"""
44

5-
__authors__ = "Ashwin Kanhere"
6-
__date__ = "25 Januray 2020"
5+
__authors__ = "Ashwin Kanhere, Derek Knowles"
6+
__date__ = "25 Jan 2020"
7+
8+
import warnings
79

810
import numpy as np
911

12+
from gnss_lib_py.parsers.navdata import NavData
13+
from gnss_lib_py.utils.coordinates import ecef_to_geodetic
1014
from gnss_lib_py.utils.filters import BaseExtendedKalmanFilter
1115

16+
def solve_ekf_gnss(measurements, init_dict = dict(),
17+
params_dict = dict()):
18+
"""Runs a GNSS Extended Kalman Filter across each timestep.
19+
20+
Runs an Extended Kalman Filter across each timestep and adds a new
21+
row for the receiver's position and clock bias.
22+
23+
Parameters
24+
----------
25+
measurements : gnss_lib_py.parsers.navdata.NavData
26+
Instance of the NavData class
27+
init_dict : dict
28+
Initialization dict with initial states and covariances.
29+
params_dict : dict
30+
Dictionary of parameters for GNSS EKF.
31+
32+
Returns
33+
-------
34+
state_estimate : gnss_lib_py.parsers.navdata.NavData
35+
Estimated receiver position in ECEF frame in meters and the
36+
estimated receiver clock bias also in meters as an instance of
37+
the NavData class with shape (4 x # unique timesteps) and
38+
the following rows: x_rx_m, y_rx_m, z_rx_m, b_rx_m.
39+
40+
"""
41+
42+
# check that all necessary rows exist
43+
measurements.in_rows(["gps_millis","corr_pr_m",
44+
"x_sv_m","y_sv_m","z_sv_m",
45+
])
46+
47+
# create initialization parameters.
48+
gnss_ekf = GNSSEKF(init_dict, params_dict)
49+
50+
states = []
51+
52+
for timestamp, delta_t, measurement_subset in measurements.loop_time("gps_millis"):
53+
54+
pos_sv_m = measurement_subset[["x_sv_m","y_sv_m","z_sv_m"]].T
55+
pos_sv_m = np.atleast_2d(pos_sv_m)
56+
57+
corr_pr_m = measurement_subset["corr_pr_m"].reshape(-1,1)
58+
59+
# remove NaN indexes
60+
not_nan_indexes = ~np.isnan(pos_sv_m).any(axis=1)
61+
pos_sv_m = pos_sv_m[not_nan_indexes]
62+
corr_pr_m = corr_pr_m[not_nan_indexes]
63+
64+
# prediction step
65+
predict_dict = {"delta_t" : delta_t}
66+
gnss_ekf.predict(predict_dict=predict_dict)
67+
68+
# update step
69+
update_dict = {"pos_sv_m" : pos_sv_m}
70+
gnss_ekf.update(corr_pr_m, update_dict=update_dict)
71+
72+
states.append([timestamp] + np.squeeze(gnss_ekf.state).tolist())
73+
74+
states = np.array(states)
75+
76+
if states.size == 0:
77+
warnings.warn("No valid state estimate computed in solve_ekf_gnss, "\
78+
+ "returning None.", RuntimeWarning)
79+
return None
80+
81+
state_estimate = NavData()
82+
state_estimate["gps_millis"] = states[:,0]
83+
state_estimate["x_rx_m"] = states[:,1]
84+
state_estimate["y_rx_m"] = states[:,2]
85+
state_estimate["z_rx_m"] = states[:,3]
86+
state_estimate["b_rx_m"] = states[:,4]
87+
88+
lat,lon,alt = ecef_to_geodetic(state_estimate[["x_rx_m",
89+
"y_rx_m",
90+
"z_rx_m"]])
91+
state_estimate["lat_rx_deg"] = lat
92+
state_estimate["lon_rx_deg"] = lon
93+
state_estimate["alt_rx_deg"] = alt
94+
95+
return state_estimate
96+
1297
class GNSSEKF(BaseExtendedKalmanFilter):
1398
"""GNSS-only EKF implementation.
1499
@@ -17,42 +102,40 @@ class GNSSEKF(BaseExtendedKalmanFilter):
17102
18103
Attributes
19104
----------
20-
dt : float
105+
params_dict : dict
106+
Dictionary of parameters that may include the following.
107+
delta_t : float
21108
Time between prediction instances
22109
motion_type : string
23-
Type of motion (stationary or constant velocity)
110+
Type of motion (``stationary`` or ``constant_velocity``)
24111
measure_type : string
25-
NavData types (pseudoranges)
112+
NavData types (pseudorange)
26113
"""
27114
def __init__(self, init_dict, params_dict):
28115
super().__init__(init_dict, params_dict)
29-
self.dt = params_dict['dt']
30-
try:
31-
self.motion_type = params_dict['motion_type']
32-
except KeyError:
33-
self.motion_type = 'stationary'
34-
try:
35-
self.measure_type = params_dict['measure_type']
36-
except KeyError:
37-
self.measure_type = 'pseudoranges'
38-
39-
def dyn_model(self, u, predict_dict=None):
40-
"""Non linear dynamics
116+
117+
self.delta_t = params_dict.get('dt',1.0)
118+
self.motion_type = params_dict.get('motion_type','stationary')
119+
self.measure_type = params_dict.get('measure_type','pseudorange')
120+
121+
def dyn_model(self, u, predict_dict=dict()):
122+
"""Nonlinear dynamics
41123
42124
Parameters
43125
----------
44126
u : np.ndarray
45127
Control signal, not used for propagation
46-
predict_dict : Dict
47-
Additional prediction parameters, not used currently
128+
predict_dict : dict
129+
Additional prediction parameters, including ``delta_t``
130+
updates.
48131
49132
Returns
50133
-------
51134
new_x : np.ndarray
52135
Propagated state
53136
"""
54-
A = self.linearize_dynamics()
55-
new_x = A @ self.x
137+
A = self.linearize_dynamics(predict_dict)
138+
new_x = A @ self.state
56139
return new_x
57140

58141
def measure_model(self, update_dict):
@@ -62,10 +145,15 @@ def measure_model(self, update_dict):
62145
:math:`\\rho = \\sqrt{(x-x_{sv})^2 + (y-y_{sv})^2 + (z-z_{sv})^2} + b`.
63146
See [1]_ for more details and models.
64147
148+
``pos_sv_m`` must be a key in update_dict and must be an array
149+
of shape [3 x N] with rows of x_sv_m, y_sv_m, and z_sv_m in that
150+
order.
151+
65152
Parameters
66153
----------
67-
update_dict : Dict
68-
Update dictionary containing satellite positions with key 'sv_pos'
154+
update_dict : dict
155+
Update dictionary containing satellite positions with key
156+
``pos_sv_m``.
69157
70158
Returns
71159
-------
@@ -80,34 +168,41 @@ def measure_model(self, update_dict):
80168
applications. John Wiley & Sons, 2021.
81169
"""
82170
if self.measure_type=='pseudorange':
83-
sv_pos = update_dict['sv_pos']
84-
pseudo = np.sqrt((self.x[0] - sv_pos[0, :])**2
85-
+ (self.x[1] - sv_pos[1, :])**2
86-
+ (self.x[2] - sv_pos[2, :])**2) \
87-
+ self.x[6]
171+
pos_sv_m = update_dict['pos_sv_m']
172+
pseudo = np.sqrt((self.state[0] - pos_sv_m[0, :])**2
173+
+ (self.state[1] - pos_sv_m[1, :])**2
174+
+ (self.state[2] - pos_sv_m[2, :])**2) \
175+
+ self.state[6]
88176
z = np.reshape(pseudo, [-1, 1])
89177
else:
90178
raise NotImplementedError
91179
return z
92180

93-
def linearize_dynamics(self, predict_dict=None):
181+
def linearize_dynamics(self, predict_dict=dict()):
94182
"""Linearization of dynamics model
95183
96184
Parameters
97185
----------
98-
predict_dict : Dict
186+
predict_dict : dict
99187
Additional predict parameters, not used in current implementation
100188
101189
Returns
102190
-------
103191
A : np.ndarray
104192
Linear dynamics model depending on motion_type
193+
predict_dict : dict
194+
Dictionary of prediction parameters.
105195
"""
196+
197+
# uses delta_t from predict_dict if exists, otherwise delta_t
198+
# from the class initialization.
199+
delta_t = predict_dict.get('delta_t', self.delta_t)
200+
106201
if self.motion_type == 'stationary':
107202
A = np.eye(7)
108203
elif self.motion_type == 'constant_velocity':
109204
A = np.eye(7)
110-
A[:3, -4:-1] = self.dt*np.eye(3)
205+
A[:3, -4:-1] = delta_t*np.eye(3)
111206
else:
112207
raise NotImplementedError
113208
return A
@@ -117,21 +212,21 @@ def linearize_measurements(self, update_dict):
117212
118213
Parameters
119214
----------
120-
update_dict : Dict
121-
Update dictionary containing satellite positions with key 'sv_pos'
215+
update_dict : dict
216+
Update dictionary containing satellite positions with key 'pos_sv_m'
122217
123218
Returns
124219
-------
125220
H : np.ndarray
126221
Jacobian of measurement model, dimension M x N
127222
"""
128223
if self.measure_type == 'pseudorange':
129-
sv_pos = update_dict['sv_pos']
130-
m = np.shape(sv_pos)[1]
131-
H = np.zeros([m, self.x_dim])
224+
pos_sv_m = update_dict['pos_sv_m']
225+
m = np.shape(pos_sv_m)[1]
226+
H = np.zeros([m, self.state_dim])
132227
pseudo_expect = self.measure_model(update_dict)
133-
rx_pos = np.reshape(self.x[:3], [-1, 1])
134-
H[:, :3] = (rx_pos - sv_pos).T/pseudo_expect
228+
rx_pos = np.reshape(self.state[:3], [-1, 1])
229+
H[:, :3] = (rx_pos - pos_sv_m).T/pseudo_expect
135230
H[:, 6] = 1
136231
else:
137232
raise NotImplementedError

0 commit comments

Comments
 (0)