-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathfrequency_bias_estimation.py
357 lines (319 loc) · 12.7 KB
/
frequency_bias_estimation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# -*- coding: utf-8 -*-
"""
Created on Thu Nov 12 09:40:15 2020
@author: Jonas Beuchert
"""
import numpy as np
import scipy.stats as ss
import scipy.optimize as so
import pymap3d as pm
import eph_util as ep
def estimate_frequency_bias_wrapper(
pos_geo, utc, signal, intermediate_frequency, sampling_frequency,
gps_file=None, sbas_file=None, galileo_file=None, beidou_file=None,
plot=False):
"""
Estimate common frequency bias w.r.t. intermediate frequency.
Assume intermediate frequency of zero.
Inputs:
pos_geo - Coarse receiver position in geodetic coordinates [deg,deg,m]
(numpy array)
utc - Universal coordinated time (numpy datetime64)
signal - Binary GNSS raw signal (numpy array)
intermediate_frequency - Uncorrected intermediate frequency [Hz]
gps_file - Path to GPS RINEX navigation file, default=None
sbas_file - Path to SBAS RINEX navigation file, default=None
galileo_file - Path to Galileo RINEX navigation file, default=None
beidou_file - Path to BeiDou RINEX navigation file, default=None
plot - Switch to enable likelihood plotting, default=False
Output:
frequency_bias - Frequency bias estimate [Hz]
Author: Jonas Beuchert
"""
# Subtract mean from signal
signal = signal.astype(float)
signal = signal - np.mean(signal)
# Convert geodetic coordinates to ECEF
pos_ecef = np.empty(3)
pos_ecef[0], pos_ecef[1], pos_ecef[2] = pm.geodetic2ecef(
pos_geo[0], pos_geo[1], pos_geo[2]
)
# Convert UTC to GPS time
gps_time = ep.utc_2_gps_time(utc)
# Read RINEX navigation files for all systems
eph = [] # List for navigation data
time = [] # List for coarse time in different system times
system = [] # List for GNSS strings
prn = [] # List for PRNs of satellite constellations
try:
print("Read GPS navigation data...")
eph.append(ep.rinexe(gps_file, 'G'))
time.append(gps_time)
system.append('gps')
prn.append(np.arange(1, 33))
except:
print("Could not read GPS navigation data.")
pass
try:
print("Read SBAS navigation data...")
eph.append(ep.rinexe(sbas_file, 'S'))
time.append(gps_time)
system.append('sbas')
prn.append(np.arange(120, 139))
except:
print("Could not read SBAS navigation data.")
pass
try:
print("Read Galileo navigation data...")
eph.append(ep.rinexe(galileo_file, 'E'))
time.append(gps_time)
system.append('galileo')
prn.append(np.arange(1, 51))
except:
print("Could not read Galileo navigation data.")
pass
try:
print("Read BeiDou navigation data...")
eph.append(ep.rinexe(beidou_file, 'C'))
beidou_time = ep.gps_time_2_beidou_time(gps_time)
time.append(beidou_time)
system.append('beidou')
prn.append(np.arange(1, 64))
except:
print("Could not read BeiDou (BDS) navigation data.")
pass
n_gnss = len(eph)
print("{} navigation satellite systems found.".format(n_gnss))
# Acqusition
vis = []
frequencies = []
snr = []
for i_gnss in range(n_gnss):
# Predict visible satellites
vis.append(ep.get_visible_sats(time[i_gnss], pos_geo, eph[i_gnss],
elev_mask=10, prn_list=prn[i_gnss]))
print("{} satellites of system {} expected to be visible.".format(
len(vis[i_gnss]), i_gnss+1))
print("Acquire satellites for system {}...".format(i_gnss+1))
# Acquisition
_, _, _, _, _, f, _, p = ep.acquisition(
signal, intermediate_frequency, sampling_frequency, freq_step=20,
ms_to_process=12, prn_list=vis[i_gnss], fine_freq=False,
gnss=system[i_gnss]
)
vis_idx = (vis[i_gnss]).astype(int) - 1
frequencies.append(f[vis_idx])
snr.append(p[vis_idx])
print("Estimate frequency bias...")
return estimate_frequency_bias(pos_ecef, time, frequencies, vis, eph, snr,
plot=plot)
def estimate_frequency_bias_minimal_wrapper(
snapshot_idx_dict,
prn_dict,
frequency_dict,
snr_dict,
eph_dict,
utc,
latitude, longitude, height
):
"""Estimate common frequency bias for a batch of snapshots.
Assume intermediate frequency of zero.
Inputs:
snapshot_idx_dict - Dictionary from acquisition_simplified
prn_dict - Dictionary from acquisition_simplified
frequency_dict - Dictionary from acquisition_simplified
snr_dict - Dictionary from acquisition_simplified
eph_dict - Dictionary from acquisition_simplified
utc - Timestamps in UTC, one value for each snapshot, 1D NumPy array
with elements of type numpy.datetime64
latitude_init - Single latitude for all snapshots [°]
longitude_init - Single longitude for all snapshots [°]
height_init - Single height w.r.t. WGS84 for all snapshots [m]
Output:
frequency_bias_vec - Frequency bias estimates [Hz], 1D NumPy array
Author: Jonas Beuchert
"""
# Check which GNSS are present
gnss_list = snapshot_idx_dict.keys()
# How many snapshots?
n_snapshots = utc.shape[0]
# Convert UTC to GPS time
reference_date = np.datetime64('1980-01-06') # GPS reference date
leap_seconds = np.timedelta64(18, 's') # Hardcoded 18 leap seconds
time = (utc - reference_date + leap_seconds) / np.timedelta64(1, 's')
# Convert GPS time to BeiDou time
time_dict = {gnss: time - 820108814.0 if gnss == 'C' else time
for gnss in gnss_list}
# Convert geodetic coordinates to Cartesian ECEF XYZ coordinates
# Same position for all snapshots
pos_ecef = np.array(pm.geodetic2ecef(
latitude, longitude, height
))
# Loop over all snapshots
return np.array([
estimate_frequency_bias(
init_pos=pos_ecef,
init_time=[
time_dict[gnss][snapshot_idx]
for gnss in gnss_list
],
observed_frequencies=[
frequency_dict[gnss][snapshot_idx_dict[gnss] == snapshot_idx]
for gnss in gnss_list
],
vis=[
prn_dict[gnss][snapshot_idx_dict[gnss] == snapshot_idx]
for gnss in gnss_list
],
eph=[
eph_dict[gnss][:, snapshot_idx_dict[gnss] == snapshot_idx]
for gnss in gnss_list
],
peak_height=[
snr_dict[gnss][snapshot_idx_dict[gnss] == snapshot_idx]
for gnss in gnss_list
],
plot=False
)
for snapshot_idx in np.arange(n_snapshots)])
def estimate_frequency_bias(init_pos, init_time, observed_frequencies, vis,
eph, peak_height, max_frequency_bias=2.5e3,
band_width=5.0e3, plot=False):
"""
Estimate common frequency bias w.r.t. intermediate frequency.
Assume intermediate frequency of zero.
Inputs:
init_pos - Coarse receiver position in ECEF XYZ coordinates [m,m,m]
init_time - Coarse absolute GNSS times [s], list with one value for
each GNSS
observed_frequencies - Observed carrier frequencies of satellites that
are expected to be visible [Hz]; list of numpy
arrays, one for each GNSS
vis - PRNs of satellites that are expected to be visible, list of numpy
arrays, one for each GNSS
eph - Matching navigation data, list of 21-row 2D numpy arrays
peak_height - Reliability measures for satellites that are expected to
be visible, e.g., SNR [dB], list of numpy arrays
max_frequency_bias - Maximum absolute frequency bias [Hz],
default=2.5e3
band_width - Original width of frequency search space during
acquistion [Hz], default=5.0e3
plot - Switch to enable likelihood plotting, default=False
Output:
frequency_bias - Frequency bias estimate [Hz]
Author: Jonas Beuchert
"""
def bayes_classifier(x, galileo=False):
"""Probability of code phase being invalid or valid based on SNR.
Inputs:
x - Signal-to-noise ratio (SNR) array [dB]
galileo - Type of GNSS, GPS (galileo=False) or Galileo or BeiDou
(galileo=True), default=False
Output:
p - 2xN array with probabilities for code phases being invalid
(1st row) or valid (2nd row)
Author: Jonas Beuchert
"""
# if not galileo: # GPS
# Class means
mu0 = 7.7
mu1 = 15.1
# Class standard deviations
sigma0 = 1#0.67 * 4
sigma1 = 1#4.65
# Class priors
p0 = 0.5#27
p1 = 0.5#73
# else: # Galileo
# # Class means
# mu0 = 14.2
# mu1 = 19.1
# # Class standard deviations
# sigma0 = 1.13
# sigma1 = 4.12
# # Class priors
# p0 = 0.62
# p1 = 0.38
px0 = ss.norm(mu0, sigma0).pdf(x) * p0
px1 = ss.norm(mu1, sigma1).pdf(x) * p1
return np.array([px0, px1]) / (px0 + px1)
def neg_log_likelihood(
bias, std, predicted_frequencies, observed_frequencies, pValid,
band_width):
# Initialize log-likelihood
cd = 0.0
# Define Gaussian kernel
kernel = ss.norm(0.0, std)
for i_gnss in range(n_gnss):
n_sat = np.int(predicted_frequencies[i_gnss].shape[0] / 2)
idx = predicted_frequencies[i_gnss] \
+ bias * np.concatenate((-np.ones(n_sat), np.ones(n_sat))) \
- observed_frequencies[i_gnss]
# Likelihood of frequency observation given that it is valid
pPhiValid = kernel.pdf(idx)
# Likelihood of frequency observation given that it is invalid
pPhiInvalid = 1.0 / band_width
# Probability of frequency being valid / invalid given SNR
pInvalid_sat = 1.0 - pValid[i_gnss]
pValid_sat = pValid[i_gnss]
# Likelihood of frequency
pPhi = pPhiValid * pValid_sat + pPhiInvalid * pInvalid_sat
# Log-likelihood
cd = cd + np.sum(np.log(pPhi))
cd = np.clip(cd, -np.finfo(np.float).max, np.finfo(np.float).max)
return -cd
# Constrain search space
bounds = [(-max_frequency_bias, max_frequency_bias)]
# Decreasing standard deviation of Gaussian noise on frequencies [Hz]
std = 2.0**np.arange(9, 1, -2)
# Probability of code phase being valid / invalid given SNR
n_gnss = len(vis)
pValid = []
pInvalid = []
for i_gnss in range(n_gnss):
if i_gnss == 0:
galileo = False
else:
galileo = True
p = bayes_classifier(peak_height[i_gnss], galileo=galileo)
pInvalid.append(p[0])
pValid.append(p[1])
# Predict frequencies
n_gnss = len(vis)
predicted_frequencies = []
for i_gnss in range(n_gnss):
predicted_frequencies.append(np.empty(vis[i_gnss].shape))
for i_sat in range(vis[i_gnss].shape[0]):
predicted_frequencies[i_gnss][i_sat] = ep.get_doppler(
init_time[i_gnss], init_pos, vis[i_gnss][i_sat],
eph[i_gnss])
# Double (Doppler sign unknown)
band_width = band_width * 2.0
for i_gnss in range(n_gnss):
predicted_frequencies[i_gnss] = np.concatenate(
(-predicted_frequencies[i_gnss], predicted_frequencies[i_gnss])
)
observed_frequencies[i_gnss] = np.concatenate(
(-observed_frequencies[i_gnss], observed_frequencies[i_gnss])
)
pValid[i_gnss] = np.concatenate(
(pValid[i_gnss] / 2.0, pValid[i_gnss] / 2.0)
)
# Initial hypothesis
frequency_bias = 0.0
if plot:
import matplotlib.pyplot as plt
b_vec = np.arange(-max_frequency_bias, max_frequency_bias+20, 20)
for it_idx in range(len(std)):
frequency_bias = so.minimize(
neg_log_likelihood, frequency_bias, args=(
std[it_idx], predicted_frequencies, observed_frequencies,
pValid, band_width),
method='L-BFGS-B', bounds=bounds).x
if plot:
lik = np.array([neg_log_likelihood(
b, std[it_idx], predicted_frequencies, observed_frequencies,
pValid, band_width) for b in b_vec])
plt.plot(b_vec, lik)
plt.show()
return frequency_bias