Skip to content

Commit aa3308d

Browse files
committed
residual FDE updates
1 parent ae6217b commit aa3308d

1 file changed

Lines changed: 127 additions & 92 deletions

File tree

gnss_lib_py/algorithms/fde.py

Lines changed: 127 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def solve_fde(navdata, method="residual", remove_outliers=False,
7575

7676

7777
if method == "residual":
78-
navdata = fde_residual_old(navdata, max_faults=max_faults,
78+
navdata = fde_greedy_residual(navdata, max_faults=max_faults,
7979
threshold=threshold,verbose=verbose,
8080
**kwargs)
8181
elif method == "ss":
@@ -144,7 +144,9 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
144144
else:
145145
nci_nominal = max_faults + 5
146146

147-
compute_times = []
147+
if debug:
148+
compute_times = []
149+
navdata_timing = []
148150

149151
for _, _, navdata_subset in navdata.loop_time('gps_millis'):
150152

@@ -209,52 +211,22 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
209211
nci = min(nci_nominal,len(edm_detect))
210212
u3_suspects = list(np.argsort(np.abs(svd_u[:,3]))[::-1][:nci])
211213
u4_suspects = list(np.argsort(np.abs(svd_u[:,4]))[::-1][:nci])
212-
v3_suspects = list(np.argsort(np.abs(svd_v[:,3]))[::-1][:nci])
213-
v4_suspects = list(np.argsort(np.abs(svd_v[:,4]))[::-1][:nci])
214-
suspects = u3_suspects + u4_suspects \
215-
+ v3_suspects + v4_suspects
214+
suspects = u3_suspects + u4_suspects
216215
counts = {i:suspects.count(i) for i in (set(suspects)-set([0]))}
217216
if verbose:
218217
print("counts:",counts)
219218
# suspects must be in all four singular vectors
220219
# also convert to the original edm indexes
221220
fault_suspects = [[np.delete(orig_idxs,fault_idxs)[i]]
222-
for i,v in counts.items() if v == 4]
223-
224-
# avg_u = list(np.argsort(np.mean(np.abs(svd_u)[:,3:5],axis=1))[::-1][:nci])
225-
# avg_v = list(np.argsort(np.mean(np.abs(svd_v)[:,3:5],axis=1))[::-1][:nci])
226-
# suspects = avg_u + avg_v
227-
# counts = {i:suspects.count(i) for i in (set(suspects)-set([0]))}
228-
# if verbose:
229-
# print("counts:",counts)
230-
# # suspects must be in all four singular vectors
231-
# # also convert to the original edm indexes
232-
# fault_suspects = [[np.delete(orig_idxs,fault_idxs)[i]]
233-
# for i,v in counts.items() if v == 2]
234-
235-
# print(np.mean(np.abs(svd_u)[:,3:5],axis=1))
236-
# print(svd_u.shape)
237-
# print("avg_u:",avg_u)
238-
# print("unitity:",list(np.argsort(np.mean(np.abs(svd_u)[:,3:5],axis=1)[::-1][:nci]))
239-
221+
for i,v in counts.items() if v == 2]
240222

241223
# break out if no new suspects
242224
if len(fault_suspects) == 0:
243225
break
244226

245-
# plt.figure()
246-
# plt.imshow(U)
247-
# plt.colorbar()
248-
#
249-
# plt.figure()
250-
# plt.imshow(Vh)
251-
# plt.colorbar()
252-
253227
if verbose:
254228
print("U3 abs",u3_suspects)
255229
print("U4 abs",u4_suspects)
256-
print("V3 abs",v3_suspects)
257-
print("V4 abs",v4_suspects)
258230

259231
stacked_edms = [np.delete(np.delete(edm,fault_idxs+i,0),
260232
fault_idxs+i,1) \
@@ -266,19 +238,19 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
266238
detection_statistic_exclude, _, svd_s_exclude, _ = _edm_detection_statistic(edms_exclude)
267239

268240
# add also all fault suspects combined together
269-
if len(fault_suspects) > 1 and (max_faults is None
270-
or len(fault_suspects)+len(fault_idxs) <= max_faults) \
271-
and (len(edm) - len(fault_suspects) - len(fault_idxs)) > MIN_SATELLITES+1:
272-
all_faults = [i[0] for i in fault_suspects]
273-
edm_all_faults = np.delete(np.delete(edm,fault_idxs+all_faults,0),
274-
fault_idxs+all_faults,1)
275-
detection_statistic_all, _, svd_s_all, _ = _edm_detection_statistic(edm_all_faults)
276-
277-
# add to combined arrays to make argmin easy
278-
stacked_edms.append(edm_all_faults)
279-
detection_statistic_exclude += detection_statistic_all
280-
adjusted_indexes.append(np.delete(orig_idxs,fault_idxs+all_faults))
281-
fault_suspects.append(all_faults)
241+
# if len(fault_suspects) > 1 and (max_faults is None
242+
# or len(fault_suspects)+len(fault_idxs) <= max_faults) \
243+
# and (len(edm) - len(fault_suspects) - len(fault_idxs)) > MIN_SATELLITES+1:
244+
# all_faults = [i[0] for i in fault_suspects]
245+
# edm_all_faults = np.delete(np.delete(edm,fault_idxs+all_faults,0),
246+
# fault_idxs+all_faults,1)
247+
# detection_statistic_all, _, svd_s_all, _ = _edm_detection_statistic(edm_all_faults)
248+
#
249+
# # add to combined arrays to make argmin easy
250+
# stacked_edms.append(edm_all_faults)
251+
# detection_statistic_exclude += detection_statistic_all
252+
# adjusted_indexes.append(np.delete(orig_idxs,fault_idxs+all_faults))
253+
# fault_suspects.append(all_faults)
282254

283255
# also add detection with none removed
284256
detection_statistic_exclude += detection_statistic_detect
@@ -294,7 +266,7 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
294266
for s_index in range(len(svd_s_exclude)):
295267
plt.scatter(list(range(svd_s_exclude.shape[1])),svd_s_exclude[s_index,:],
296268
label=str(fault_suspects[s_index])+" removed")
297-
plt.scatter(list(range(svd_s_all.shape[1])),svd_s_all[0],label="all removed")
269+
# plt.scatter(list(range(svd_s_all.shape[1])),svd_s_all[0],label="all removed")
298270
# plt.yscale("log")
299271
plt.legend()
300272

@@ -303,7 +275,7 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
303275
for s_index in range(len(svd_s_exclude)):
304276
plt.scatter(0,detection_statistic_exclude[s_index],
305277
label=str(fault_suspects[s_index])+" removed")
306-
plt.scatter(0,detection_statistic_all,label="all removed")
278+
# plt.scatter(0,detection_statistic_all,label="all removed")
307279
# plt.yscale("log")
308280
plt.legend()
309281

@@ -334,13 +306,16 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
334306
if debug:
335307
time_end = time.time()
336308
compute_times.append(time_end-time_start)
309+
navdata_timing += list([time_end-time_start] * nsl)
310+
337311

338312
navdata["fault_edm"] = fault_edm
339313
if verbose:
340314
print(navdata["fault_edm"])
341315

342316
debug_info = {}
343317
if debug:
318+
navdata["compute_time_s"] = navdata_timing
344319
debug_info["compute_times"] = compute_times
345320
return navdata, debug_info
346321
return navdata
@@ -401,11 +376,11 @@ def _edm_detection_statistic(edm):
401376

402377
return detection_statistic, svd_u, svd_s, svd_v
403378

404-
def fde_residual_old(navdata, max_faults, threshold,
379+
def fde_greedy_residual(navdata, max_faults, threshold,
405380
verbose=False, debug=False):
406381
"""Residual-based fault detection and exclusion.
407382
408-
Implemented based on paper from Parkinson [1]_.
383+
Implemented based on paper from Blanch et al [1]_.
409384
410385
Parameters
411386
----------
@@ -436,9 +411,10 @@ def fde_residual_old(navdata, max_faults, threshold,
436411
437412
References
438413
----------
439-
.. [1] Parkinson, Bradford W., and Penina Axelrad. "Autonomous GPS
440-
integrity monitoring using the pseudorange residual."
441-
Navigation 35.2 (1988): 255-274.
414+
.. [1] Blanch, Juan, Todd Walter, and Per Enge. "Fast multiple fault
415+
exclusion with a large number of measurements." Proceedings
416+
of the 2015 International Technical Meeting of the Institute
417+
of Navigation. 2015.
442418
443419
"""
444420

@@ -447,7 +423,9 @@ def fde_residual_old(navdata, max_faults, threshold,
447423
threshold = 10
448424

449425

450-
compute_times = []
426+
if debug:
427+
compute_times = []
428+
navdata_timing = []
451429

452430
for _, _, navdata_subset in navdata.loop_time('gps_millis'):
453431

@@ -469,78 +447,135 @@ def fde_residual_old(navdata, max_faults, threshold,
469447
navdata_subset.remove(cols=nan_idxs,inplace=True)
470448
original_indexes = np.delete(original_indexes, nan_idxs)
471449

472-
ri = []
473-
if len(navdata_subset) > 6:
450+
fault_idxs = []
451+
if len(navdata_subset) > 5:
474452

475453
# test statistic
476-
r = _residual_detection_statistic(navdata_subset)
454+
receiver_state = solve_wls(navdata_subset)
455+
solve_residuals(navdata_subset, receiver_state, inplace=True)
456+
457+
chi_square = _residual_chi_square(navdata_subset, receiver_state)
477458

478459
if verbose:
479-
print("residual test statistic:",r)
460+
print("residual test statistic:",chi_square)
480461

481-
# iterate through subsets if r is above detection threshold
482-
if r > threshold:
483-
ri = set()
484-
r_subsets = []
485-
for ss in range(len(navdata_subset)):
486-
navdata_exclude = navdata_subset.remove(cols=[ss])
487-
r_subset = _residual_detection_statistic(navdata_exclude)
462+
# greedy removal if chi_square above detection threshold
463+
while chi_square > threshold:
488464

489-
if verbose:
490-
r_subsets.append(r_subset)
491-
# adjusted threshold metric
492-
if verbose:
493-
print("r_subset/r:",r_subset/r,ss,"removed")
494-
# 1. chosen based on similarity to 8m/10m ratio from [1]_.
495-
if r_subset/r < 1.:
496-
ri.add(ss)
465+
if len(navdata_subset) < 5 or (max_faults is not None and len(fault_idxs) >= max_faults):
466+
break
497467

498-
if len(ri) == 0:
499-
if verbose:
500-
print("NONE fail:")
501-
print("r: ",r)
502-
print("ri: ",ri)
503-
else:
504-
if verbose:
505-
print("threshold fail:")
506-
print("r: ",r)
468+
normalized_residual = _residual_exclude(navdata_subset,receiver_state)
469+
fault_idx = np.argsort(normalized_residual)[-1]
470+
471+
navdata_subset.remove(cols=[fault_idx], inplace=True)
472+
fault_idxs.append(original_indexes[fault_idx])
473+
original_indexes = np.delete(original_indexes, fault_idx)
507474

508-
ri = list(ri)
475+
# test statistic
476+
receiver_state = solve_wls(navdata_subset)
477+
solve_residuals(navdata_subset, receiver_state, inplace=True)
478+
chi_square = _residual_chi_square(navdata_subset, receiver_state)
479+
480+
if verbose:
481+
print("chi square:",chi_square,"after removing index:",fault_idxs)
509482

510483
fault_residual_subset = np.array([0] * subset_length)
511-
fault_residual_subset[original_indexes[ri]] = 1
484+
fault_residual_subset[fault_idxs] = 1
512485
fault_residual_subset[nan_idxs] = 2
513486
fault_residual += list(fault_residual_subset)
514487

515488
if debug:
516489
time_end = time.time()
517490
compute_times.append(time_end-time_start)
491+
navdata_timing += list([time_end-time_start] * subset_length)
518492

519493
navdata["fault_residual"] = fault_residual
520494
if verbose:
521495
print(navdata["fault_residual"])
522496

523497
debug_info = {}
524498
if debug:
499+
navdata["compute_time_s"] = navdata_timing
525500
debug_info["compute_times"] = compute_times
526501
return navdata, debug_info
527502
return navdata
528503

529-
def _residual_detection_statistic(navdata):
530-
"""Detection statistic for Residual-based fault detection.
504+
def _residual_chi_square(navdata, receiver_state):
505+
"""Chi square test for residuals.
506+
507+
Implemented from Blanch et al [2]_.
508+
509+
References
510+
----------
511+
.. [2] Blanch, Juan, Todd Walter, and Per Enge. "Fast multiple fault
512+
exclusion with a large number of measurements." Proceedings
513+
of the 2015 International Technical Meeting of the Institute
514+
of Navigation. 2015.
531515
532516
"""
517+
533518
# solve for residuals
534519
receiver_state = solve_wls(navdata)
535520
solve_residuals(navdata, receiver_state, inplace=True)
536-
# if len(navdata_subset) < 6:
537521
residuals = navdata["residuals_m"].reshape(-1,1)
538522

539-
# test statistic
540-
detection_statistic = np.sqrt(residuals.T.dot(residuals)[0,0] \
541-
/ (len(navdata) - 4) )
523+
# weights
524+
weights = np.eye(len(navdata))
525+
526+
# geometry matrix
527+
geo_matrix = (receiver_state[["x_rx_wls_m","y_rx_wls_m","z_rx_wls_m"]].reshape(-1,1) \
528+
- navdata[["x_sv_m","y_sv_m","z_sv_m"]]).T
529+
geo_matrix /= np.linalg.norm(geo_matrix,axis=0)
530+
531+
chi_square = residuals.T @ (weights - weights @ geo_matrix \
532+
@ np.linalg.pinv(geo_matrix.T @ weights @ geo_matrix) @ geo_matrix.T @ weights ) @ residuals
533+
chi_square = chi_square.item()
534+
535+
return chi_square
536+
537+
def _residual_exclude(navdata, receiver_state):
538+
"""Detection statistic for Residual-based fault detection.
539+
540+
Implemented from Blanch et al [3]_.
541+
542+
References
543+
----------
544+
.. [3] Blanch, Juan, Todd Walter, and Per Enge. "Fast multiple fault
545+
exclusion with a large number of measurements." Proceedings
546+
of the 2015 International Technical Meeting of the Institute
547+
of Navigation. 2015.
548+
549+
"""
550+
# solve for residuals
551+
552+
residuals = navdata["residuals_m"].reshape(-1,1)
553+
554+
# weights
555+
weights = np.eye(len(navdata))
556+
557+
# geometry matrix
558+
geo_matrix = (receiver_state[["x_rx_wls_m","y_rx_wls_m","z_rx_wls_m"]].reshape(-1,1) \
559+
- navdata[["x_sv_m","y_sv_m","z_sv_m"]]).T
560+
geo_matrix /= np.linalg.norm(geo_matrix,axis=0)
561+
562+
# calculate normalized residual
563+
x_tilde = np.linalg.pinv(geo_matrix.T @ weights @ geo_matrix) @ geo_matrix.T @ weights @ residuals
564+
565+
normalized_residual = np.divide(np.multiply(np.diag(weights).reshape(-1,1),
566+
(residuals - geo_matrix @ x_tilde)**2),
567+
np.diag(1 - weights @ geo_matrix @ \
568+
np.linalg.pinv(geo_matrix.T @ weights @ geo_matrix) @ geo_matrix.T).reshape(-1,1))
569+
570+
normalized_residual = normalized_residual[:,0]
571+
# for i in range(len(navdata)):
572+
# ri = weights[i,i]*(residuals[i,0] - geo_matrix[i,:].reshape(1,-1) @ x_tilde)**2 \
573+
# / (1 - weights[i,i] * geo_matrix[i,:].reshape(1,-1) @ \
574+
# np.linalg.pinv(geo_matrix.T @ weights @ geo_matrix) @ geo_matrix[i,:].reshape(-1,1))
575+
# print(ri)
576+
# print(hi)
542577

543-
return detection_statistic
578+
return normalized_residual
544579

545580
def fde_solution_separation_old(navdata, max_faults, threshold,
546581
verbose=False):

0 commit comments

Comments
 (0)