@@ -22,21 +22,23 @@ def solve_fde(navdata, method="residual", remove_outliers=False,
2222 Parameters
2323 ----------
2424 navdata : gnss_lib_py.parsers.navdata.NavData
25- NavData of GNSS measurements which must include the receiver's
26- estimated state: x_rx*_m, y_rx*_m, z_rx*_m, b_rx*_m as well as
27- the satellite states: x_sv_m, y_sv_m, z_sv_m, b_sv_m as well
28- as timing "gps_millis" and the corrected pseudorange corr_pr_m.
25+ NavData of GNSS measurements which must include the satellite
26+ positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m``, ``b_sv_m`` as
27+ well as the time row ``gps_millis`` and the corrected
28+ pseudorange `` corr_pr_m`` .
2929 method : string
3030 Method for fault detection and exclusion either "residual" for
3131 residual-based or "edm" for Euclidean Distance Matrix-based.
3232 remove_outliers : bool
33- If true, will remove detected faults from NavData instance and
34- unknown timesteps.
33+ If true, will remove detected faults from the returned NavData
34+ instance and unknown timesteps (both 1 and 2 fault status) .
3535 If false, will detect but not exclude faults or unknowns.
3636 max_faults : int
3737 Maximum number of faults to detect and/or exclude.
3838 threshold : float
3939 Detection threshold.
40+ verbose : bool
41+ If true, prints extra debugging statements.
4042
4143 Returns
4244 -------
@@ -64,22 +66,26 @@ def solve_fde(navdata, method="residual", remove_outliers=False,
6466
6567 return navdata
6668
67- def fde_edm (navdata , max_faults = None , threshold = 1.0 , verbose = False ,
68- time_fde = False ):
69+ def fde_edm (navdata , max_faults = None , threshold = 1.0 , time_fde = False ,
70+ verbose = False ):
6971 """Euclidean distance matrix-based fault detection and exclusion.
7072
7173 See [1]_ for more detailed explanation of algorithm.
7274
7375 Parameters
7476 ----------
7577 navdata : gnss_lib_py.parsers.navdata.NavData
76- NavData of GNSS measurements which must include the
77- the satellite states: x_sv_m, y_sv_m, z_sv_m, b_sv_m as well
78- as gps_millis and the corrected pseudorange corr_pr_m.
78+ NavData of GNSS measurements which must include the satellite
79+ positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m``, ``b_sv_m`` as
80+ well as the time row ``gps_millis`` and the corrected
81+ pseudorange ``corr_pr_m``.
7982 max_faults : int
8083 Maximum number of faults to detect and/or exclude.
8184 threshold : float
8285 Detection threshold.
86+ time_fde : bool
87+ If true, will time the fault detection and exclusion steps and
88+ return that info.
8389 verbose : bool
8490 Prints extra debugging print statements if true.
8591
@@ -90,6 +96,9 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
9096 value of 1 indicates a detected fault and 0 indicates that
9197 no fault was detected, and 2 indicates an unknown fault status
9298 usually due to lack of necessary columns or information.
99+ timing_info : dict
100+ If time_fde is true, also returns a dictionary of the compute
101+ times in seconds for each iteration.
93102
94103 References
95104 ----------
@@ -113,7 +122,7 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
113122 compute_times = []
114123 navdata_timing = []
115124
116- for ts , _ , navdata_subset in navdata .loop_time ('gps_millis' ):
125+ for _ , _ , navdata_subset in navdata .loop_time ('gps_millis' ):
117126 if time_fde :
118127 time_start = time .time ()
119128
@@ -149,7 +158,7 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
149158 break
150159
151160 edm_detect = np .delete (np .delete (edm ,fault_idxs ,0 ),fault_idxs ,1 )
152- detection_statistic_detect , svd_u , svd_s , _ = _edm_detection_statistic (edm_detect )
161+ detection_statistic_detect , svd_u , _ , _ = _edm_detection_statistic (edm_detect )
153162 detection_statistic = detection_statistic_detect [0 ]
154163
155164 if verbose :
@@ -240,23 +249,26 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, verbose=False,
240249 return navdata , timing_info
241250 return navdata
242251
243- def fde_greedy_residual (navdata , max_faults , threshold ,
244- verbose = False , time_fde = False ):
252+ def fde_greedy_residual (navdata , max_faults , threshold , time_fde = False ,
253+ verbose = False ):
245254 """Residual-based fault detection and exclusion.
246255
247- Implemented based on paper from Blanch et al [1 ]_.
256+ Implemented based on paper from Blanch et al [2 ]_.
248257
249258 Parameters
250259 ----------
251260 navdata : gnss_lib_py.parsers.navdata.NavData
252- NavData of GNSS measurements which must include the receiver's
253- estimated state: x_rx*_m, y_rx*_m, z_rx*_m, b_rx*_m as well as
254- the satellite states: x_sv_m, y_sv_m, z_sv_m, b_sv_m as well
255- as timing "gps_millis" and the corrected pseudorange corr_pr_m.
261+ NavData of GNSS measurements which must include the satellite
262+ positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m``, ``b_sv_m`` as
263+ well as the time row ``gps_millis`` and the corrected
264+ pseudorange `` corr_pr_m`` .
256265 max_faults : int
257266 Maximum number of faults to detect and/or exclude.
258267 threshold : float
259268 Detection threshold.
269+ time_fde : bool
270+ If true, will time the fault detection and exclusion steps and
271+ return that info.
260272 verbose : bool
261273 Prints extra debugging print statements if true.
262274
@@ -267,10 +279,13 @@ def fde_greedy_residual(navdata, max_faults, threshold,
267279 value of 1 indicates a detected fault and 0 indicates that
268280 no fault was detected, and 2 indicates an unknown fault status
269281 usually due to lack of necessary columns or information.
282+ timing_info : dict
283+ If time_fde is true, also returns a dictionary of the compute
284+ times in seconds for each iteration.
270285
271286 References
272287 ----------
273- .. [1 ] Blanch, Juan, Todd Walter, and Per Enge. "Fast multiple fault
288+ .. [2 ] Blanch, Juan, Todd Walter, and Per Enge. "Fast multiple fault
274289 exclusion with a large number of measurements." Proceedings
275290 of the 2015 International Technical Meeting of the Institute
276291 of Navigation. 2015.
@@ -321,7 +336,8 @@ def fde_greedy_residual(navdata, max_faults, threshold,
321336 # greedy removal if chi_square above detection threshold
322337 while chi_square > threshold :
323338
324- if len (navdata_subset ) < 5 or (max_faults is not None and len (fault_idxs ) >= max_faults ):
339+ if len (navdata_subset ) < 5 or (max_faults is not None \
340+ and len (fault_idxs ) >= max_faults ):
325341 break
326342
327343 normalized_residual = _residual_exclude (navdata_subset ,receiver_state )
@@ -365,10 +381,16 @@ def evaluate_fde(navdata, method, fault_truth_row="fault_gt",
365381 ** kwargs ):
366382 """Evaluate FDE methods and compute accuracy scores
367383
384+ The row designated in the ``fault_truth_row`` variable must indicate
385+ ground truth faults according to the following convention.
386+ A value of 1 indicates a fault and 0 indicates no fault. 2 indicates
387+ an unknown fault status usually due to lack of necessary columns or
388+ information.
389+
368390 Measurements that are returned as "unknown" (fault=2) by the fault
369391 detection method are excluded from all accuracy scores.
370392
371- Accuracy metrics are defined accordingly.
393+ Accuracy metrics are defined accordingly:
372394
373395 True Positive (TP) : estimated = 1, truth = 1
374396 True Negative (TN) : estimated = 0, truth = 0
@@ -389,8 +411,9 @@ def evaluate_fde(navdata, method, fault_truth_row="fault_gt",
389411 ----------
390412 navdata : gnss_lib_py.parsers.navdata.NavData
391413 NavData of GNSS measurements which must include the satellite
392- positions: x_sv_m, y_sv_m, z_sv_m, b_sv_m as well
393- as timing "gps_millis" and the corrected pseudorange corr_pr_m.
414+ positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m``, ``b_sv_m`` as
415+ well as the time row ``gps_millis`` and the corrected
416+ pseudorange ``corr_pr_m``.
394417 Additionally, the ground truth fault row must exist as indicated
395418 by the fault the ``fault_truth_row`` variable.
396419 method : string
@@ -402,10 +425,17 @@ def evaluate_fde(navdata, method, fault_truth_row="fault_gt",
402425 row is used to provide results on how well each method performs
403426 at fault detection and exclusion.
404427 time_fde : bool
405- Additional debugging info added like timing
428+ Additional debugging info added like timing.
406429 verbose : bool
407430 Prints extra debugging print statements if true.
408431
432+ Returns
433+ -------
434+ metrics : dict
435+ Combined metrics that were computed.
436+ navdata : gnss_lib_py.parsers.navdata.NavData
437+ Resulting NavData from ``solve_fde()``.
438+
409439 """
410440 truth_fault_counts = []
411441 measurement_counts = []
@@ -480,8 +510,6 @@ def evaluate_fde(navdata, method, fault_truth_row="fault_gt",
480510 if (true_positive + false_alarm ) else 0.
481511 recall = tpr
482512
483-
484-
485513 if verbose :
486514 print ("\n " )
487515 print (method .upper () + " FDE METRICS" )
@@ -545,7 +573,7 @@ def evaluate_fde(navdata, method, fault_truth_row="fault_gt",
545573def _edm (points ):
546574 """Creates a Euclidean distance matrix (EDM) from point locations.
547575
548- See [1 ]_ for more explanation.
576+ See [3 ]_ for more explanation.
549577
550578 Parameters
551579 ----------
@@ -562,7 +590,7 @@ def _edm(points):
562590
563591 References
564592 ----------
565- .. [1 ] I. Dokmanic, R. Parhizkar, J. Ranieri, and M. Vetterli.
593+ .. [3 ] I. Dokmanic, R. Parhizkar, J. Ranieri, and M. Vetterli.
566594 “Euclidean Distance Matrices: Essential Theory, Algorithms,
567595 and Applications.” 2015. arxiv.org/abs/1502.07541.
568596
@@ -579,6 +607,8 @@ def _edm_from_satellites_ranges(sv_pos, ranges):
579607 Creates an EDM from a combination of known satellite positions as
580608 well as ranges from between the receiver and satellites.
581609
610+ Technique introduced in detail in [4]_.
611+
582612 Parameters
583613 ----------
584614 sv_pos : np.array
@@ -590,10 +620,17 @@ def _edm_from_satellites_ranges(sv_pos, ranges):
590620
591621 Returns
592622 -------
593- D : np.array
623+ edm : np.array
594624 Euclidean distance matrix in the shape (1 + s) x (1 + s) where
595625 s is the number of satellites
596626
627+ References
628+ ----------
629+ .. [4] Knowles, Derek, and Grace Gao. "Euclidean distance
630+ matrix-based rapid fault detection and exclusion."
631+ NAVIGATION: Journal of the Institute of Navigation 70.1
632+ (2023).
633+
597634 """
598635 num_s = sv_pos .shape [1 ]
599636 edm = np .zeros ((num_s + 1 ,num_s + 1 ))
@@ -604,7 +641,7 @@ def _edm_from_satellites_ranges(sv_pos, ranges):
604641 return edm
605642
606643def _edm_detection_statistic (edm ):
607- """Calculate the EDM FDE detection statistic [4 ]_.
644+ """Calculate the EDM FDE detection statistic [5 ]_.
608645
609646 Parameters
610647 ----------
@@ -624,7 +661,7 @@ def _edm_detection_statistic(edm):
624661
625662 References
626663 ----------
627- .. [4 ] D. Knowles and G. Gao. "Detection and Exclusion of Multiple
664+ .. [5 ] D. Knowles and G. Gao. "Detection and Exclusion of Multiple
628665 Faults using Euclidean Distance Matrices." ION GNSS+ 2023.
629666
630667 """
@@ -651,20 +688,33 @@ def _edm_detection_statistic(edm):
651688def _residual_chi_square (navdata , receiver_state ):
652689 """Chi square test for residuals.
653690
654- Implemented from Blanch et al [2]_.
691+ Implemented from Blanch et al [6]_.
692+
693+ Parameters
694+ ----------
695+ navdata : gnss_lib_py.parsers.navdata.NavData
696+ NavData of GNSS measurements which must include the satellite
697+ positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m`` and residuals
698+ ``residuals_m``.
699+ receiver_state : gnss_lib_py.parsers.navdata.NavData
700+ Reciever state that must include the receiver's state of:
701+ ``x_rx_wls_m``, ``y_rx_wls_m``, and ``z_rx_wls_m``.
702+
703+ Returns
704+ -------
705+ chi_square : float
706+ Chi square test statistic.
655707
656708 References
657709 ----------
658- .. [2 ] Blanch, Juan, Todd Walter, and Per Enge. "Fast multiple fault
710+ .. [6 ] Blanch, Juan, Todd Walter, and Per Enge. "Fast multiple fault
659711 exclusion with a large number of measurements." Proceedings
660712 of the 2015 International Technical Meeting of the Institute
661713 of Navigation. 2015.
662714
663715 """
664716
665717 # solve for residuals
666- receiver_state = solve_wls (navdata )
667- solve_residuals (navdata , receiver_state , inplace = True )
668718 residuals = navdata ["residuals_m" ].reshape (- 1 ,1 )
669719
670720 # weights
@@ -676,26 +726,41 @@ def _residual_chi_square(navdata, receiver_state):
676726 geo_matrix /= np .linalg .norm (geo_matrix ,axis = 0 )
677727
678728 chi_square = residuals .T @ (weights - weights @ geo_matrix \
679- @ np .linalg .pinv (geo_matrix .T @ weights @ geo_matrix ) @ geo_matrix .T @ weights ) @ residuals
729+ @ np .linalg .pinv (geo_matrix .T @ weights @ geo_matrix ) \
730+ @ geo_matrix .T @ weights ) @ residuals
680731 chi_square = chi_square .item ()
681732
682733 return chi_square
683734
684735def _residual_exclude (navdata , receiver_state ):
685736 """Detection statistic for Residual-based fault detection.
686737
687- Implemented from Blanch et al [3]_.
738+ Implemented from Blanch et al [7]_.
739+
740+ Parameters
741+ ----------
742+ navdata : gnss_lib_py.parsers.navdata.NavData
743+ NavData of GNSS measurements which must include the satellite
744+ positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m`` and residuals
745+ ``residuals_m``.
746+ receiver_state : gnss_lib_py.parsers.navdata.NavData
747+ Reciever state that must include the receiver's state of:
748+ ``x_rx_wls_m``, ``y_rx_wls_m``, and ``z_rx_wls_m``.
749+
750+ Returns
751+ -------
752+ normalized_residual : np.ndarray
753+ Array of the normalized residual for each satellite.
688754
689755 References
690756 ----------
691- .. [3 ] Blanch, Juan, Todd Walter, and Per Enge. "Fast multiple fault
757+ .. [7 ] Blanch, Juan, Todd Walter, and Per Enge. "Fast multiple fault
692758 exclusion with a large number of measurements." Proceedings
693759 of the 2015 International Technical Meeting of the Institute
694760 of Navigation. 2015.
695761
696762 """
697763 # solve for residuals
698-
699764 residuals = navdata ["residuals_m" ].reshape (- 1 ,1 )
700765
701766 # weights
@@ -707,12 +772,14 @@ def _residual_exclude(navdata, receiver_state):
707772 geo_matrix /= np .linalg .norm (geo_matrix ,axis = 0 )
708773
709774 # calculate normalized residual
710- x_tilde = np .linalg .pinv (geo_matrix .T @ weights @ geo_matrix ) @ geo_matrix .T @ weights @ residuals
775+ x_tilde = np .linalg .pinv (geo_matrix .T @ weights @ geo_matrix ) \
776+ @ geo_matrix .T @ weights @ residuals
711777
712778 normalized_residual = np .divide (np .multiply (np .diag (weights ).reshape (- 1 ,1 ),
713779 (residuals - geo_matrix @ x_tilde )** 2 ),
714780 np .diag (1 - weights @ geo_matrix @ \
715- np .linalg .pinv (geo_matrix .T @ weights @ geo_matrix ) @ geo_matrix .T ).reshape (- 1 ,1 ))
781+ np .linalg .pinv (geo_matrix .T @ weights @ geo_matrix ) \
782+ @ geo_matrix .T ).reshape (- 1 ,1 ))
716783
717784 normalized_residual = normalized_residual [:,0 ]
718785
0 commit comments