use oxictl::core::matrix::Matrix;
use oxictl::estimator::information_filter::InformationFilter;
const DT: f64 = 0.01; const N_STEPS: usize = 100;
fn true_roll(t: f64) -> f64 {
let omega = 2.0 * core::f64::consts::PI * 0.5; 0.3 * libm::sin(omega * t)
}
fn true_roll_rate(t: f64) -> f64 {
let omega = 2.0 * core::f64::consts::PI * 0.5;
0.3 * omega * libm::cos(omega * t)
}
fn pseudo_noise(t: f64, amplitude: f64, seed: f64) -> f64 {
amplitude
* (libm::sin(seed * t + 1.3)
+ 0.6 * libm::sin(seed * 2.7 * t + 2.1)
+ 0.3 * libm::sin(seed * 7.1 * t + 0.7))
/ 1.9 }
fn build_filter(sigma_imu1: f64) -> InformationFilter<f64, 2, 1, 1> {
let a = Matrix::<f64, 2, 2> {
data: [[1.0, DT], [0.0, 1.0]],
};
let b = Matrix::<f64, 2, 1> {
data: [[0.5 * DT * DT], [DT]],
};
let h = Matrix::<f64, 1, 2> { data: [[0.0, 1.0]] };
let q = Matrix::<f64, 2, 2> {
data: [[1e-6, 0.0], [0.0, 1e-5]],
};
let var1 = sigma_imu1 * sigma_imu1;
let r = Matrix::<f64, 1, 1> { data: [[var1]] };
let p0 = Matrix::<f64, 2, 2> {
data: [[1.0, 0.0], [0.0, 1.0]],
};
InformationFilter::new(a, b, h, q, r, [0.0_f64; 2], p0)
.expect("InformationFilter::new: p0 is positive definite")
}
fn main() {
let sigma = [0.02_f64, 0.08_f64, 0.20_f64];
let r_inv: [Matrix<f64, 1, 1>; 3] = [
Matrix::<f64, 1, 1> {
data: [[1.0 / (sigma[0] * sigma[0])]],
},
Matrix::<f64, 1, 1> {
data: [[1.0 / (sigma[1] * sigma[1])]],
},
Matrix::<f64, 1, 1> {
data: [[1.0 / (sigma[2] * sigma[2])]],
},
];
let h = Matrix::<f64, 1, 2> { data: [[0.0, 1.0]] };
let mut filter = build_filter(sigma[0]);
println!("# Information Filter: 3-IMU fusion for UAV roll estimation");
println!(
"# IMU noise σ: IMU1={:.3} IMU2={:.3} IMU3={:.3} (rad/s)",
sigma[0], sigma[1], sigma[2]
);
println!("step,t_s,phi_true,phidot_true,z1,z2,z3,phi_est,phidot_est,err_deg");
let mut rmse_sum = 0.0_f64;
for step in 0..N_STEPS {
let t = step as f64 * DT;
let phi_true = true_roll(t);
let phi_dot_true = true_roll_rate(t);
let z: [f64; 3] = [
phi_dot_true + pseudo_noise(t, sigma[0] * 3.0, 13.7),
phi_dot_true + pseudo_noise(t, sigma[1] * 3.0, 5.3),
phi_dot_true + pseudo_noise(t, sigma[2] * 3.0, 19.1),
];
filter
.predict(&[0.0_f64])
.expect("predict: information matrix should remain invertible");
let sensors: [(Matrix<f64, 1, 2>, Matrix<f64, 1, 1>, [f64; 1]); 3] = [
(h, r_inv[0], [z[0]]),
(h, r_inv[1], [z[1]]),
(h, r_inv[2], [z[2]]),
];
filter
.fuse_sensors(&sensors)
.expect("fuse_sensors: information fusion should not fail");
let state = filter
.state()
.expect("state: information matrix is invertible");
let phi_est = state[0];
let phi_dot_est = state[1];
let err_rad = (phi_est - phi_true).abs();
rmse_sum += err_rad * err_rad;
println!(
"{},{:.4},{:.5},{:.5},{:.5},{:.5},{:.5},{:.5},{:.5},{:.4}",
step,
t,
phi_true,
phi_dot_true,
z[0],
z[1],
z[2],
phi_est,
phi_dot_est,
err_rad.to_degrees(),
);
}
let rmse_deg = (rmse_sum / N_STEPS as f64).sqrt().to_degrees();
let cov = filter
.covariance()
.expect("covariance: filter should be well-conditioned at end of run");
eprintln!("\n=== Information Filter 3-IMU Fusion Summary ===");
eprintln!(
"IMU noise σ: IMU1={:.3} IMU2={:.3} IMU3={:.3} rad/s",
sigma[0], sigma[1], sigma[2]
);
eprintln!("Steps: {}", N_STEPS);
eprintln!("Roll RMSE: {:.4} deg", rmse_deg);
eprintln!(
"Final covariance trace: {:.6} (lower → more confident)",
cov.data[0][0] + cov.data[1][1]
);
eprintln!(
"Information matrix diagonal: Ω[0,0]={:.2}, Ω[1,1]={:.2}",
filter.information_matrix().data[0][0],
filter.information_matrix().data[1][1],
);
if rmse_deg < sigma[2].to_degrees() * 2.0 {
eprintln!("PASS: fused estimate significantly better than lowest-quality IMU alone.");
} else {
eprintln!("WARN: unexpected RMSE — check filter tuning.");
}
}