use super::Factor;
use apex_manifolds::LieGroup;
use nalgebra::{DMatrix, DVector};
#[derive(Clone, PartialEq)]
pub struct BetweenFactor<T>
where
T: LieGroup + Clone + Send + Sync,
T::TangentVector: Into<DVector<f64>>,
{
pub relative_pose: T,
}
impl<T> BetweenFactor<T>
where
T: LieGroup + Clone + Send + Sync,
T::TangentVector: Into<DVector<f64>>,
{
pub fn new(relative_pose: T) -> Self {
Self { relative_pose }
}
}
impl<T> Factor for BetweenFactor<T>
where
T: LieGroup + Clone + Send + Sync + From<DVector<f64>>,
T::TangentVector: Into<DVector<f64>>,
{
fn linearize(
&self,
params: &[DVector<f64>],
compute_jacobian: bool,
) -> (DVector<f64>, Option<DMatrix<f64>>) {
let se3_origin_k0 = T::from(params[0].clone());
let se3_origin_k1 = T::from(params[1].clone());
let se3_k0_k1_measured = &self.relative_pose;
let mut j_k1_k0_wrt_k1 = T::zero_jacobian();
let mut j_k1_k0_wrt_k0 = T::zero_jacobian();
let se3_k1_k0 = se3_origin_k1.between(
&se3_origin_k0,
Some(&mut j_k1_k0_wrt_k1),
Some(&mut j_k1_k0_wrt_k0),
);
let mut j_diff_wrt_k1_k0 = T::zero_jacobian();
let se3_diff = se3_k1_k0.compose(se3_k0_k1_measured, Some(&mut j_diff_wrt_k1_k0), None);
let mut j_log_wrt_diff = T::zero_jacobian();
let residual = se3_diff.log(Some(&mut j_log_wrt_diff));
let jacobian = if compute_jacobian {
let dof = se3_origin_k0.tangent_dim();
let j_diff_wrt_k0 = j_diff_wrt_k1_k0.clone() * j_k1_k0_wrt_k0;
let j_diff_wrt_k1 = j_diff_wrt_k1_k0 * j_k1_k0_wrt_k1;
let jacobian_wrt_k0 = j_log_wrt_diff.clone() * j_diff_wrt_k0;
let jacobian_wrt_k1 = j_log_wrt_diff * j_diff_wrt_k1;
let mut jacobian = DMatrix::<f64>::zeros(dof, 2 * dof);
for i in 0..dof {
for j in 0..dof {
jacobian[(i, j)] = jacobian_wrt_k0[(i, j)];
jacobian[(i, j + dof)] = jacobian_wrt_k1[(i, j)];
}
}
Some(jacobian)
} else {
None
};
(residual.into(), jacobian)
}
fn get_dimension(&self) -> usize {
self.relative_pose.tangent_dim()
}
}
#[cfg(test)]
mod tests {
use super::*;
use apex_manifolds::se2::{SE2, SE2Tangent};
use apex_manifolds::se3::SE3;
use apex_manifolds::so2::SO2;
use apex_manifolds::so3::SO3;
use nalgebra::{DVector, Quaternion, Vector3};
const TOLERANCE: f64 = 1e-9;
const FD_EPSILON: f64 = 1e-6;
#[test]
fn test_between_factor_se2_identity() {
let relative = SE2::identity();
let factor = BetweenFactor::new(relative);
let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);
let pose_j = DVector::from_vec(vec![0.0, 0.0, 0.0]);
let (residual, _) = factor.linearize(&[pose_i, pose_j], false);
assert_eq!(residual.len(), 3);
assert!(
residual.norm() < TOLERANCE,
"Residual norm: {}",
residual.norm()
);
}
#[test]
fn test_between_factor_se3_identity() {
let relative = SE3::identity();
let factor = BetweenFactor::new(relative);
let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
let pose_j = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
let (residual, _) = factor.linearize(&[pose_i, pose_j], false);
assert_eq!(residual.len(), 6);
assert!(
residual.norm() < TOLERANCE,
"Residual norm: {}",
residual.norm()
);
}
#[test]
fn test_between_factor_se2_jacobian_numerical() -> Result<(), Box<dyn std::error::Error>> {
let relative = SE2::from_xy_angle(1.0, 0.0, 0.1);
let factor = BetweenFactor::new(relative);
let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);
let pose_j = DVector::from_vec(vec![0.95, 0.05, 0.12]);
let (residual, jacobian_opt) = factor.linearize(&[pose_i.clone(), pose_j.clone()], true);
let jacobian = jacobian_opt.ok_or("Jacobian should be Some when compute_jacobians=true")?;
assert_eq!(jacobian.nrows(), 3);
assert_eq!(jacobian.ncols(), 6);
let mut jacobian_fd = DMatrix::<f64>::zeros(3, 6);
let se2_i = SE2::from(pose_i.clone());
let se2_j = SE2::from(pose_j.clone());
for i in 0..3 {
let delta = match i {
0 => SE2Tangent::new(FD_EPSILON, 0.0, 0.0),
1 => SE2Tangent::new(0.0, FD_EPSILON, 0.0),
2 => SE2Tangent::new(0.0, 0.0, FD_EPSILON),
_ => unreachable!(),
};
let se2_i_perturbed = se2_i.plus(&delta, None, None);
let pose_i_perturbed = DVector::<f64>::from(se2_i_perturbed);
let (residual_perturbed, _) =
factor.linearize(&[pose_i_perturbed, pose_j.clone()], false);
for j in 0..3 {
jacobian_fd[(j, i)] = (residual_perturbed[j] - residual[j]) / FD_EPSILON;
}
}
for i in 0..3 {
let delta = match i {
0 => SE2Tangent::new(FD_EPSILON, 0.0, 0.0),
1 => SE2Tangent::new(0.0, FD_EPSILON, 0.0),
2 => SE2Tangent::new(0.0, 0.0, FD_EPSILON),
_ => unreachable!(),
};
let se2_j_perturbed = se2_j.plus(&delta, None, None);
let pose_j_perturbed = DVector::<f64>::from(se2_j_perturbed);
let (residual_perturbed, _) =
factor.linearize(&[pose_i.clone(), pose_j_perturbed], false);
for j in 0..3 {
jacobian_fd[(j, i + 3)] = (residual_perturbed[j] - residual[j]) / FD_EPSILON;
}
}
let diff_norm = (jacobian - jacobian_fd).norm();
assert!(diff_norm < 1e-5, "Jacobian difference norm: {}", diff_norm);
Ok(())
}
#[test]
fn test_between_factor_se3_jacobian_numerical() -> Result<(), Box<dyn std::error::Error>> {
let relative = SE3::from_translation_quaternion(
Vector3::new(1.0, 0.0, 0.0),
Quaternion::new(1.0, 0.0, 0.0, 0.0),
);
let factor = BetweenFactor::new(relative);
let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
let pose_j = DVector::from_vec(vec![0.95, 0.05, 0.0, 1.0, 0.0, 0.0, 0.0]);
let (residual, jacobian_opt) = factor.linearize(&[pose_i.clone(), pose_j.clone()], true);
let jacobian = jacobian_opt.ok_or("Jacobian should be Some when compute_jacobians=true")?;
assert_eq!(jacobian.nrows(), 6);
assert_eq!(jacobian.ncols(), 12);
let mut jacobian_fd = DMatrix::<f64>::zeros(6, 12);
for i in 0..3 {
let mut pose_i_perturbed = pose_i.clone();
pose_i_perturbed[i] += FD_EPSILON;
let (residual_perturbed, _) =
factor.linearize(&[pose_i_perturbed, pose_j.clone()], false);
for j in 0..6 {
jacobian_fd[(j, i)] = (residual_perturbed[j] - residual[j]) / FD_EPSILON;
}
}
for i in 0..3 {
let mut pose_j_perturbed = pose_j.clone();
pose_j_perturbed[i] += FD_EPSILON;
let (residual_perturbed, _) =
factor.linearize(&[pose_i.clone(), pose_j_perturbed], false);
for j in 0..6 {
jacobian_fd[(j, i + 6)] = (residual_perturbed[j] - residual[j]) / FD_EPSILON;
}
}
let diff_norm_trans = (jacobian.columns(0, 3) - jacobian_fd.columns(0, 3)).norm();
assert!(
diff_norm_trans < 1e-5,
"Jacobian difference norm (translation): {}",
diff_norm_trans
);
Ok(())
}
#[test]
fn test_between_factor_dimension_se2() -> Result<(), Box<dyn std::error::Error>> {
let relative = SE2::from_xy_angle(1.0, 0.5, 0.1);
let factor = BetweenFactor::new(relative);
let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);
let pose_j = DVector::from_vec(vec![1.0, 0.0, 0.0]);
let (residual, jacobian) = factor.linearize(&[pose_i, pose_j], true);
assert_eq!(residual.len(), 3);
assert_eq!(factor.get_dimension(), 3);
let jac = jacobian.ok_or("Jacobian should be Some when compute_jacobians=true")?;
assert_eq!(jac.nrows(), 3);
assert_eq!(jac.ncols(), 6);
Ok(())
}
#[test]
fn test_between_factor_dimension_se3() -> Result<(), Box<dyn std::error::Error>> {
let relative = SE3::identity();
let factor = BetweenFactor::new(relative);
let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
let pose_j = DVector::from_vec(vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
let (residual, jacobian) = factor.linearize(&[pose_i, pose_j], true);
assert_eq!(residual.len(), 6);
assert_eq!(factor.get_dimension(), 6);
let jac = jacobian.ok_or("Jacobian should be Some when compute_jacobians=true")?;
assert_eq!(jac.nrows(), 6);
assert_eq!(jac.ncols(), 12);
Ok(())
}
#[test]
fn test_between_factor_so2_so3() -> Result<(), Box<dyn std::error::Error>> {
let so2_relative = SO2::from_angle(0.1);
let so2_factor = BetweenFactor::new(so2_relative);
let so2_i = DVector::from_vec(vec![0.0]);
let so2_j = DVector::from_vec(vec![0.12]);
let (residual_so2, jacobian_so2) = so2_factor.linearize(&[so2_i, so2_j], true);
assert_eq!(residual_so2.len(), 1);
assert_eq!(so2_factor.get_dimension(), 1);
let jac_so2 = jacobian_so2.ok_or("Jacobian should be Some when compute_jacobians=true")?;
assert_eq!(jac_so2.nrows(), 1);
assert_eq!(jac_so2.ncols(), 2);
let so3_relative = SO3::identity();
let so3_factor = BetweenFactor::new(so3_relative);
let so3_i = DVector::from_vec(vec![1.0, 0.0, 0.0, 0.0]);
let so3_j = DVector::from_vec(vec![1.0, 0.0, 0.0, 0.0]);
let (residual_so3, jacobian_so3) = so3_factor.linearize(&[so3_i, so3_j], true);
assert_eq!(residual_so3.len(), 3);
assert_eq!(so3_factor.get_dimension(), 3);
let jac_so3 = jacobian_so3.ok_or("Jacobian should be Some when compute_jacobians=true")?;
assert_eq!(jac_so3.nrows(), 3);
assert_eq!(jac_so3.ncols(), 6);
Ok(())
}
#[test]
fn test_between_factor_finiteness() -> Result<(), Box<dyn std::error::Error>> {
let relative = SE2::from_xy_angle(100.0, -200.0, std::f64::consts::PI);
let factor = BetweenFactor::new(relative);
let pose_i = DVector::from_vec(vec![50.0, -100.0, 1.5]);
let pose_j = DVector::from_vec(vec![150.0, -300.0, -1.5]);
let (residual, jacobian) = factor.linearize(&[pose_i, pose_j], true);
assert!(residual.iter().all(|&x| x.is_finite()));
let jac = jacobian.ok_or("Jacobian should be Some when compute_jacobians=true")?;
assert!(jac.iter().all(|&x| x.is_finite()));
Ok(())
}
#[test]
fn test_between_factor_clone() {
let relative = SE3::identity();
let factor = BetweenFactor::new(relative);
let factor_clone = factor.clone();
let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
let pose_j = DVector::from_vec(vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
let (residual1, _) = factor.linearize(&[pose_i.clone(), pose_j.clone()], false);
let (residual2, _) = factor_clone.linearize(&[pose_i, pose_j], false);
assert!((residual1 - residual2).norm() < TOLERANCE);
}
}