use apex_manifolds::LieGroup;
use apex_manifolds::se3::SE3;
use faer::prelude::ReborrowMut;
use crate::core::variable::ManifoldVariable;
use crate::factors::Factor;
#[derive(Clone)]
pub struct BarometricFactor {
pub measured_height: f64,
}
impl BarometricFactor {
pub fn new(measured_height: f64) -> Self {
Self { measured_height }
}
}
impl Factor for BarometricFactor {
fn linearize(
&self,
params: &[&[f64]],
residual: &mut [f64],
jacobian: Option<faer::mat::MatMut<'_, f64>>,
) {
debug_assert_eq!(params.len(), 2, "BarometricFactor expects [pose, bias]");
debug_assert_eq!(params[0].len(), 7, "params[0] must be SE3 (7D)");
debug_assert_eq!(params[1].len(), 1, "params[1] must be a scalar bias");
let pose = SE3::from_param_slice(params[0]);
let p_world = pose.translation();
let bias = params[1][0];
residual[0] = p_world.z + bias - self.measured_height;
let Some(mut jac) = jacobian else { return };
let rotation = pose.rotation_so3().rotation_matrix();
for col in 0..3 {
*jac.rb_mut().get_mut(0, col) = rotation[(2, col)];
*jac.rb_mut().get_mut(0, 3 + col) = 0.0;
}
*jac.rb_mut().get_mut(0, 6) = 1.0;
}
fn residual_dim(&self) -> usize {
1
}
fn jacobian_shape(&self) -> (usize, usize) {
(1, 7)
}
fn validate_variables(&self, variables: &[&dyn ManifoldVariable]) -> Result<(), String> {
if variables.len() != 2
|| variables[0].as_param_slice().len() != SE3::REP_SIZE
|| variables[1].as_param_slice().len() != 1
{
return Err("BarometricFactor expects [SE3 pose, R¹ baro bias]".into());
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::UnitQuaternion as UQ;
#[test]
fn barometric_residual_and_jacobian() {
use apex_manifolds::Tangent;
use apex_manifolds::se3::SE3Tangent;
let pose = SE3::from_isometry(nalgebra::Isometry3::from_parts(
nalgebra::Translation3::new(1.0, 2.0, 48.5),
UQ::from_euler_angles(0.05, -0.03, 0.02),
));
let pose_v: Vec<f64> = pose.as_param_slice().to_vec();
let bias = vec![1.0];
let factor = BarometricFactor::new(50.0);
let mut residual = vec![0.0; 1];
let mut jac_buf = vec![0.0; 7];
let jac_mut = faer::mat::MatMut::from_column_major_slice_mut(&mut jac_buf, 1, 7);
factor.linearize(&[&pose_v, &bias], &mut residual, Some(jac_mut));
assert!((residual[0] - (-0.5)).abs() < 1e-12);
assert!((jac_buf[6] - 1.0).abs() < 1e-12);
const EPS: f64 = 1e-6;
for col in 0..6 {
let mut tan = [0.0f64; 6];
tan[col] = EPS;
let perturbed: Vec<f64> = pose
.right_plus(&SE3Tangent::from_slice(&tan), None, None)
.as_param_slice()
.to_vec();
let mut r_pert = vec![0.0; 1];
factor.linearize(&[&perturbed, &bias], &mut r_pert, None);
let fd = (r_pert[0] - residual[0]) / EPS;
let ana = jac_buf[col];
assert!(
(fd - ana).abs() < 1e-4,
"baro J[{col}]: analytical={ana:.6} fd={fd:.6}"
);
}
}
}