@@ -1918,3 +1918,126 @@ fn test_rbpf_with_degraded_gnss() {
19181918 assert ! ( result. altitude. is_finite( ) ) ;
19191919 }
19201920}
1921+
1922+ /// Test that filter output length matches input data length
1923+ ///
1924+ /// This test verifies that all filter implementations (UKF, EKF, ESKF, dead reckoning)
1925+ /// produce output with the same number of records as the input data. This is critical for
1926+ /// downstream analysis tools that expect aligned data streams.
1927+ #[ test]
1928+ fn test_filter_output_length_matches_input ( ) {
1929+ let manifest_dir = env ! ( "CARGO_MANIFEST_DIR" ) ;
1930+ let test_data_path = Path :: new ( manifest_dir) . join ( "tests/test_data.csv" ) ;
1931+ let records = load_test_data ( & test_data_path) ;
1932+
1933+ assert ! (
1934+ !records. is_empty( ) ,
1935+ "Test data should contain at least one record"
1936+ ) ;
1937+
1938+ let input_length = records. len ( ) ;
1939+ println ! ( "Testing with {} input records" , input_length) ;
1940+
1941+ // Test dead reckoning
1942+ let dr_results = dead_reckoning ( & records) ;
1943+ assert_eq ! (
1944+ dr_results. len( ) ,
1945+ input_length,
1946+ "Dead reckoning output length {} should match input length {}" ,
1947+ dr_results. len( ) ,
1948+ input_length
1949+ ) ;
1950+ println ! (
1951+ "✓ Dead reckoning: {} outputs for {} inputs" ,
1952+ dr_results. len( ) ,
1953+ input_length
1954+ ) ;
1955+
1956+ // Create initial state from first record
1957+ let initial_state = create_initial_state ( & records[ 0 ] ) ;
1958+ let imu_biases = vec ! [ 0.0 ; 6 ] ; // Zero initial bias estimates
1959+ let initial_covariance = DEFAULT_INITIAL_COVARIANCE . to_vec ( ) ;
1960+ let process_noise = DMatrix :: from_diagonal ( & DVector :: from_vec ( DEFAULT_PROCESS_NOISE . to_vec ( ) ) ) ;
1961+ let degradation = GnssDegradationConfig :: default ( ) ;
1962+
1963+ // Test UKF
1964+ let mut ukf = UnscentedKalmanFilter :: new (
1965+ initial_state. clone ( ) ,
1966+ imu_biases. clone ( ) ,
1967+ None , // No measurement bias
1968+ initial_covariance. clone ( ) ,
1969+ process_noise. clone ( ) ,
1970+ 1e-3 , // alpha
1971+ 2.0 , // beta
1972+ 0.0 , // kappa
1973+ ) ;
1974+
1975+ let event_stream = build_event_stream ( & records, & degradation) ;
1976+ let ukf_results = run_closed_loop ( & mut ukf, event_stream, None )
1977+ . expect ( "UKF closed loop should complete successfully" ) ;
1978+
1979+ assert_eq ! (
1980+ ukf_results. len( ) ,
1981+ input_length,
1982+ "UKF output length {} should match input length {}" ,
1983+ ukf_results. len( ) ,
1984+ input_length
1985+ ) ;
1986+ println ! (
1987+ "✓ UKF: {} outputs for {} inputs" ,
1988+ ukf_results. len( ) ,
1989+ input_length
1990+ ) ;
1991+
1992+ // Test EKF
1993+ let mut ekf = ExtendedKalmanFilter :: new (
1994+ initial_state. clone ( ) ,
1995+ imu_biases. clone ( ) ,
1996+ initial_covariance. clone ( ) ,
1997+ process_noise. clone ( ) ,
1998+ true ,
1999+ ) ;
2000+
2001+ let event_stream = build_event_stream ( & records, & degradation) ;
2002+ let ekf_results = run_closed_loop ( & mut ekf, event_stream, None )
2003+ . expect ( "EKF closed loop should complete successfully" ) ;
2004+
2005+ assert_eq ! (
2006+ ekf_results. len( ) ,
2007+ input_length,
2008+ "EKF output length {} should match input length {}" ,
2009+ ekf_results. len( ) ,
2010+ input_length
2011+ ) ;
2012+ println ! (
2013+ "✓ EKF: {} outputs for {} inputs" ,
2014+ ekf_results. len( ) ,
2015+ input_length
2016+ ) ;
2017+
2018+ // Test ESKF
2019+ let mut eskf =
2020+ ErrorStateKalmanFilter :: new ( initial_state, imu_biases, initial_covariance, process_noise) ;
2021+
2022+ let event_stream = build_event_stream ( & records, & degradation) ;
2023+ let eskf_results = run_closed_loop ( & mut eskf, event_stream, None )
2024+ . expect ( "ESKF closed loop should complete successfully" ) ;
2025+
2026+ assert_eq ! (
2027+ eskf_results. len( ) ,
2028+ input_length,
2029+ "ESKF output length {} should match input length {}" ,
2030+ eskf_results. len( ) ,
2031+ input_length
2032+ ) ;
2033+ println ! (
2034+ "✓ ESKF: {} outputs for {} inputs" ,
2035+ eskf_results. len( ) ,
2036+ input_length
2037+ ) ;
2038+
2039+ println ! (
2040+ "\n ✅ All filters produce output length matching input length: {}" ,
2041+ input_length
2042+ ) ;
2043+ }
0 commit comments