1use super::Factor;
2use apex_manifolds::{LieGroup, Tangent};
3use faer::prelude::ReborrowMut;
4
5#[derive(Clone, PartialEq)]
108pub struct BetweenFactor<T>
109where
110 T: LieGroup + Clone + Send + Sync,
111{
112 pub relative_pose: T,
114}
115
116impl<T> BetweenFactor<T>
117where
118 T: LieGroup + Clone + Send + Sync,
119{
120 pub fn new(relative_pose: T) -> Self {
164 Self { relative_pose }
165 }
166}
167
168impl<T> Factor for BetweenFactor<T>
169where
170 T: LieGroup + Clone + Send + Sync,
171{
172 fn linearize(
173 &self,
174 params: &[&[f64]],
175 residual: &mut [f64],
176 jacobian: Option<faer::mat::MatMut<'_, f64>>,
177 ) {
178 let se3_origin_k0 = T::from_param_slice(params[0]);
179 let se3_origin_k1 = T::from_param_slice(params[1]);
180 let se3_k0_k1_measured = &self.relative_pose;
181
182 let mut j_k1_k0_wrt_k1 = T::zero_jacobian();
184 let mut j_k1_k0_wrt_k0 = T::zero_jacobian();
185 let se3_k1_k0 = se3_origin_k1.between(
186 &se3_origin_k0,
187 Some(&mut j_k1_k0_wrt_k1),
188 Some(&mut j_k1_k0_wrt_k0),
189 );
190
191 let mut j_diff_wrt_k1_k0 = T::zero_jacobian();
193 let se3_diff = se3_k1_k0.compose(se3_k0_k1_measured, Some(&mut j_diff_wrt_k1_k0), None);
194
195 let mut j_log_wrt_diff = T::zero_jacobian();
197 let tangent = se3_diff.log(Some(&mut j_log_wrt_diff));
198 let tangent_slice = tangent.as_slice();
199 let dof = tangent_slice.len();
200
201 residual[..dof].copy_from_slice(tangent_slice);
202
203 if let Some(mut jac) = jacobian {
204 let j_diff_wrt_k0 = j_diff_wrt_k1_k0.clone() * j_k1_k0_wrt_k0;
205 let j_diff_wrt_k1 = j_diff_wrt_k1_k0 * j_k1_k0_wrt_k1;
206 let jacobian_wrt_k0 = j_log_wrt_diff.clone() * j_diff_wrt_k0;
207 let jacobian_wrt_k1 = j_log_wrt_diff * j_diff_wrt_k1;
208
209 for i in 0..dof {
210 for j in 0..dof {
211 *jac.rb_mut().get_mut(i, j) = jacobian_wrt_k0[(i, j)];
212 *jac.rb_mut().get_mut(i, j + dof) = jacobian_wrt_k1[(i, j)];
213 }
214 }
215 }
216 }
217
218 fn residual_dim(&self) -> usize {
219 self.relative_pose.tangent_dim()
220 }
221
222 fn jacobian_shape(&self) -> (usize, usize) {
223 let dof = self.relative_pose.tangent_dim();
224 (dof, 2 * dof)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use apex_manifolds::se2::{SE2, SE2Tangent};
232 use apex_manifolds::se3::SE3;
233 use apex_manifolds::so2::SO2;
234 use apex_manifolds::so3::SO3;
235 use nalgebra::{DMatrix, DVector, Quaternion, Vector3};
236
237 const TOLERANCE: f64 = 1e-9;
238 const FD_EPSILON: f64 = 1e-6;
239 type TestResult = Result<(), Box<dyn std::error::Error>>;
240
241 fn compute_residual<T>(
242 factor: &BetweenFactor<T>,
243 pose_i: &DVector<f64>,
244 pose_j: &DVector<f64>,
245 ) -> Vec<f64>
246 where
247 T: LieGroup + Clone + Send + Sync,
248 {
249 let mut residual = vec![0.0f64; factor.residual_dim()];
250 factor.linearize(&[pose_i.as_slice(), pose_j.as_slice()], &mut residual, None);
251 residual
252 }
253
254 fn compute_with_jacobian<T>(
255 factor: &BetweenFactor<T>,
256 pose_i: &DVector<f64>,
257 pose_j: &DVector<f64>,
258 ) -> (Vec<f64>, DMatrix<f64>)
259 where
260 T: LieGroup + Clone + Send + Sync,
261 {
262 let (rows, cols) = factor.jacobian_shape();
263 let mut residual = vec![0.0f64; rows];
264 let mut jac_buf = vec![0.0f64; rows * cols];
265 let jac_mut = faer::mat::MatMut::from_column_major_slice_mut(&mut jac_buf, rows, cols);
266 factor.linearize(
267 &[pose_i.as_slice(), pose_j.as_slice()],
268 &mut residual,
269 Some(jac_mut),
270 );
271 let jacobian = DMatrix::from_column_slice(rows, cols, &jac_buf);
272 (residual, jacobian)
273 }
274
275 #[test]
276 fn test_between_factor_se2_identity() {
277 let relative = SE2::identity();
278 let factor = BetweenFactor::new(relative);
279
280 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);
281 let pose_j = DVector::from_vec(vec![0.0, 0.0, 0.0]);
282
283 let residual = compute_residual(&factor, &pose_i, &pose_j);
284
285 assert_eq!(residual.len(), 3);
286 let norm: f64 = residual.iter().map(|x| x * x).sum::<f64>().sqrt();
287 assert!(norm < TOLERANCE, "Residual norm: {}", norm);
288 }
289
290 #[test]
291 fn test_between_factor_se3_identity() {
292 let relative = SE3::identity();
293 let factor = BetweenFactor::new(relative);
294
295 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
296 let pose_j = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
297
298 let residual = compute_residual(&factor, &pose_i, &pose_j);
299
300 assert_eq!(residual.len(), 6);
301 let norm: f64 = residual.iter().map(|x| x * x).sum::<f64>().sqrt();
302 assert!(norm < TOLERANCE, "Residual norm: {}", norm);
303 }
304
305 #[test]
306 fn test_between_factor_se2_jacobian_numerical() -> TestResult {
307 let relative = SE2::from_xy_angle(1.0, 0.0, 0.1);
308 let factor = BetweenFactor::new(relative);
309
310 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);
311 let pose_j = DVector::from_vec(vec![0.95, 0.05, 0.12]);
312
313 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
314
315 assert_eq!(jacobian.nrows(), 3);
316 assert_eq!(jacobian.ncols(), 6);
317
318 let mut jacobian_fd = DMatrix::<f64>::zeros(3, 6);
319 let se2_i = SE2::from_param_slice(pose_i.as_slice());
320 let se2_j = SE2::from_param_slice(pose_j.as_slice());
321
322 for i in 0..3 {
323 let delta = match i {
324 0 => SE2Tangent::new(FD_EPSILON, 0.0, 0.0),
325 1 => SE2Tangent::new(0.0, FD_EPSILON, 0.0),
326 2 => SE2Tangent::new(0.0, 0.0, FD_EPSILON),
327 _ => unreachable!(),
328 };
329 let pose_i_p =
330 DVector::from_column_slice(se2_i.plus(&delta, None, None).as_param_slice());
331 let residual_p = compute_residual(&factor, &pose_i_p, &pose_j);
332 for j in 0..3 {
333 jacobian_fd[(j, i)] = (residual_p[j] - residual[j]) / FD_EPSILON;
334 }
335 }
336
337 for i in 0..3 {
338 let delta = match i {
339 0 => SE2Tangent::new(FD_EPSILON, 0.0, 0.0),
340 1 => SE2Tangent::new(0.0, FD_EPSILON, 0.0),
341 2 => SE2Tangent::new(0.0, 0.0, FD_EPSILON),
342 _ => unreachable!(),
343 };
344 let pose_j_p =
345 DVector::from_column_slice(se2_j.plus(&delta, None, None).as_param_slice());
346 let residual_p = compute_residual(&factor, &pose_i, &pose_j_p);
347 for j in 0..3 {
348 jacobian_fd[(j, i + 3)] = (residual_p[j] - residual[j]) / FD_EPSILON;
349 }
350 }
351
352 let diff_norm = (jacobian - jacobian_fd).norm();
353 assert!(diff_norm < 1e-5, "Jacobian difference norm: {}", diff_norm);
354 Ok(())
355 }
356
357 #[test]
358 fn test_between_factor_se3_jacobian_numerical() -> TestResult {
359 let relative = SE3::from_translation_quaternion(
360 Vector3::new(1.0, 0.0, 0.0),
361 Quaternion::new(1.0, 0.0, 0.0, 0.0),
362 );
363 let factor = BetweenFactor::new(relative);
364
365 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
366 let pose_j = DVector::from_vec(vec![0.95, 0.05, 0.0, 1.0, 0.0, 0.0, 0.0]);
367
368 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
369
370 assert_eq!(jacobian.nrows(), 6);
371 assert_eq!(jacobian.ncols(), 12);
372
373 let mut jacobian_fd = DMatrix::<f64>::zeros(6, 12);
374
375 for i in 0..3 {
376 let mut pose_i_p = pose_i.clone();
377 pose_i_p[i] += FD_EPSILON;
378 let residual_p = compute_residual(&factor, &pose_i_p, &pose_j);
379 for j in 0..6 {
380 jacobian_fd[(j, i)] = (residual_p[j] - residual[j]) / FD_EPSILON;
381 }
382 }
383
384 for i in 0..3 {
385 let mut pose_j_p = pose_j.clone();
386 pose_j_p[i] += FD_EPSILON;
387 let residual_p = compute_residual(&factor, &pose_i, &pose_j_p);
388 for j in 0..6 {
389 jacobian_fd[(j, i + 6)] = (residual_p[j] - residual[j]) / FD_EPSILON;
390 }
391 }
392
393 let diff_norm_trans = (jacobian.columns(0, 3) - jacobian_fd.columns(0, 3)).norm();
394 assert!(
395 diff_norm_trans < 1e-5,
396 "Jacobian difference norm (translation): {}",
397 diff_norm_trans
398 );
399 Ok(())
400 }
401
402 #[test]
403 fn test_between_factor_dimension_se2() -> TestResult {
404 let relative = SE2::from_xy_angle(1.0, 0.5, 0.1);
405 let factor = BetweenFactor::new(relative);
406
407 assert_eq!(factor.residual_dim(), 3);
408 assert_eq!(factor.jacobian_shape(), (3, 6));
409
410 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);
411 let pose_j = DVector::from_vec(vec![1.0, 0.0, 0.0]);
412 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
413
414 assert_eq!(residual.len(), 3);
415 assert_eq!(jacobian.nrows(), 3);
416 assert_eq!(jacobian.ncols(), 6);
417 Ok(())
418 }
419
420 #[test]
421 fn test_between_factor_dimension_se3() -> TestResult {
422 let relative = SE3::identity();
423 let factor = BetweenFactor::new(relative);
424
425 assert_eq!(factor.residual_dim(), 6);
426 assert_eq!(factor.jacobian_shape(), (6, 12));
427
428 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
429 let pose_j = DVector::from_vec(vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
430 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
431
432 assert_eq!(residual.len(), 6);
433 assert_eq!(jacobian.nrows(), 6);
434 assert_eq!(jacobian.ncols(), 12);
435 Ok(())
436 }
437
438 #[test]
439 fn test_between_factor_so2_so3() -> TestResult {
440 let so2_relative = SO2::from_angle(0.1);
441 let so2_factor = BetweenFactor::new(so2_relative);
442
443 assert_eq!(so2_factor.residual_dim(), 1);
444 assert_eq!(so2_factor.jacobian_shape(), (1, 2));
445
446 let so2_i = DVector::from_vec(vec![0.0]);
447 let so2_j = DVector::from_vec(vec![0.12]);
448 let (res_so2, jac_so2) = compute_with_jacobian(&so2_factor, &so2_i, &so2_j);
449 assert_eq!(res_so2.len(), 1);
450 assert_eq!(jac_so2.nrows(), 1);
451 assert_eq!(jac_so2.ncols(), 2);
452
453 let so3_relative = SO3::identity();
454 let so3_factor = BetweenFactor::new(so3_relative);
455
456 let so3_i = DVector::from_vec(vec![1.0, 0.0, 0.0, 0.0]);
457 let so3_j = DVector::from_vec(vec![1.0, 0.0, 0.0, 0.0]);
458 let (res_so3, jac_so3) = compute_with_jacobian(&so3_factor, &so3_i, &so3_j);
459 assert_eq!(res_so3.len(), 3);
460 assert_eq!(jac_so3.nrows(), 3);
461 assert_eq!(jac_so3.ncols(), 6);
462 Ok(())
463 }
464
465 #[test]
466 fn test_between_factor_finiteness() -> TestResult {
467 let relative = SE2::from_xy_angle(100.0, -200.0, std::f64::consts::PI);
468 let factor = BetweenFactor::new(relative);
469
470 let pose_i = DVector::from_vec(vec![50.0, -100.0, 1.5]);
471 let pose_j = DVector::from_vec(vec![150.0, -300.0, -1.5]);
472
473 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
474
475 assert!(residual.iter().all(|x| x.is_finite()));
476 assert!(jacobian.iter().all(|x| x.is_finite()));
477 Ok(())
478 }
479
480 #[test]
481 fn test_between_factor_clone() {
482 let relative = SE3::identity();
483 let factor = BetweenFactor::new(relative);
484 let factor_clone = factor.clone();
485
486 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
487 let pose_j = DVector::from_vec(vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
488
489 let r1 = compute_residual(&factor, &pose_i, &pose_j);
490 let r2 = compute_residual(&factor_clone, &pose_i, &pose_j);
491
492 let diff: f64 = r1.iter().zip(r2.iter()).map(|(a, b)| (a - b).abs()).sum();
493 assert!(diff < TOLERANCE);
494 }
495}