@@ -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
545580def fde_solution_separation_old (navdata , max_faults , threshold ,
546581 verbose = False ):
0 commit comments