33import numpy as np
44import smsfusion as sf
55from smsfusion import FixedIntervalSmoother
6+ from smsfusion .benchmark import (
7+ benchmark_full_pva_beat_202311A ,
8+ benchmark_full_pva_chirp_202311A ,
9+ )
610
711
812class Test_FixedIntervalSmoother :
@@ -12,7 +16,7 @@ def ains(self):
1216 x0 [6 :10 ] = np .array ([1.0 , 0.0 , 0.0 , 0.0 ])
1317 ains = sf .AidedINS (10.24 , x0 )
1418 return ains
15-
19+
1620 def test__init__ (self , ains ):
1721 smoother = FixedIntervalSmoother (ains )
1822 assert smoother ._ains is ains
@@ -24,3 +28,76 @@ def test__init__(self, ains):
2428 assert smoother .quaternion ().size == 0
2529 assert smoother .bias_acc ().size == 0
2630 assert smoother .bias_gyro ().size == 0
31+
32+ @pytest .mark .parametrize (
33+ "benchmark_gen" ,
34+ [benchmark_full_pva_beat_202311A , benchmark_full_pva_chirp_202311A ],
35+ )
36+ def test_benchmark (self , benchmark_gen ):
37+ fs_imu = 10.0
38+ fs_aiding = 1.0
39+ fs_ratio = np .ceil (fs_imu / fs_aiding )
40+ warmup = int (fs_imu * 600.0 ) # truncate 600 seconds from the beginning
41+ compass_noise_std = 0.5
42+ gps_noise_std = 0.1
43+ vel_noise_std = 0.1
44+
45+ # Reference signals (without noise)
46+ t , pos_ref , vel_ref , euler_ref , acc_ref , gyro_ref = benchmark_gen (fs_imu )
47+ euler_ref = np .degrees (euler_ref )
48+ gyro_ref = np .degrees (gyro_ref )
49+
50+ rng = np .random .default_rng (seed = 1 )
51+
52+ # IMU measurements (with noise)
53+ err_acc = sf .constants .ERR_ACC_MOTION2
54+ err_gyro = sf .constants .ERR_GYRO_MOTION2
55+ noise_model = sf .noise .IMUNoise (err_acc , err_gyro , seed = 0 )
56+ imu_noise = noise_model (fs_imu , len (t ))
57+ acc_meas = acc_ref + imu_noise [:, :3 ]
58+ gyro_meas = gyro_ref + imu_noise [:, 3 :]
59+
60+ # Compass / heading (aiding) measurements
61+ head_noise = compass_noise_std * rng .standard_normal (len (t ))
62+ head_meas = euler_ref [:, 2 ] + head_noise
63+
64+ # GPS / position (aiding) measurements
65+ pos_noise = gps_noise_std * rng .standard_normal ((len (t ), 3 ))
66+ pos_meas = pos_ref + pos_noise
67+
68+ # Velocity (aiding) measurements
69+ vel_noise = vel_noise_std * rng .standard_normal ((len (t ), 3 ))
70+ vel_meas = vel_ref + vel_noise
71+
72+ # MEKF
73+ p0 = pos_ref [0 ] # position [m]
74+ v0 = vel_ref [0 ] # velocity [m/s]
75+ q0 = sf .quaternion_from_euler (euler_ref [0 ]) # attitude as unit quaternion
76+ ba0 = np .zeros (3 ) # accelerometer bias [m/s^2]
77+ bg0 = np .zeros (3 ) # gyroscope bias [rad/s]
78+ x0 = np .concatenate ((p0 , v0 , q0 , ba0 , bg0 ))
79+ P0 = sf .constants .P0
80+ ains = sf .AidedINS (fs_imu , x0 , P0 , err_acc , err_gyro )
81+
82+ smoother = FixedIntervalSmoother (ains , cov_smoothing = True )
83+
84+ for i , (acc_i , gyro_i , pos_i , vel_i , head_i ) in enumerate (
85+ zip (acc_meas , gyro_meas , pos_meas , vel_meas , head_meas )
86+ ):
87+ if not (i % fs_ratio ): # with aiding
88+ smoother .update (
89+ acc_i ,
90+ gyro_i ,
91+ degrees = True ,
92+ pos = pos_i ,
93+ pos_var = gps_noise_std ** 2 * np .ones (3 ),
94+ vel = vel_i ,
95+ vel_var = vel_noise_std ** 2 * np .ones (3 ),
96+ head = head_i ,
97+ head_var = compass_noise_std ** 2 ,
98+ head_degrees = True ,
99+ g_ref = True ,
100+ g_var = 0.1 ** 2 * np .ones (3 ),
101+ )
102+ else : # without aiding
103+ smoother .update (acc_i , gyro_i , degrees = True )
0 commit comments