1use crate::core::variable::ManifoldVariable;
2use crate::factors::Factor;
3use crate::factors::common::validate::expect_uniform_blocks;
4use apex_manifolds::{LieGroup, Tangent};
5use faer::prelude::ReborrowMut;
6
7type BetweenBlocks<T> = (
9 <T as LieGroup>::JacobianMatrix,
10 <T as LieGroup>::JacobianMatrix,
11);
12
13#[derive(Debug, Clone, PartialEq)]
118pub struct BetweenFactor<T>
119where
120 T: LieGroup + Clone + Send + Sync,
121{
122 pub relative_pose: T,
124}
125
126impl<T> BetweenFactor<T>
127where
128 T: LieGroup + Clone + Send + Sync,
129{
130 pub fn new(relative_pose: T) -> Self {
174 Self { relative_pose }
175 }
176
177 fn evaluate(
183 &self,
184 k0: &[f64],
185 k1: &[f64],
186 residual: &mut [f64],
187 want_jacobian: bool,
188 ) -> (usize, Option<BetweenBlocks<T>>) {
189 let se3_origin_k0 = T::from_param_slice(k0);
190 let se3_origin_k1 = T::from_param_slice(k1);
191 let se3_k0_k1_measured = &self.relative_pose;
192
193 let mut j_k1_k0_wrt_k1 = T::zero_jacobian();
195 let mut j_k1_k0_wrt_k0 = T::zero_jacobian();
196 let se3_k1_k0 = se3_origin_k1.between(
197 &se3_origin_k0,
198 Some(&mut j_k1_k0_wrt_k1),
199 Some(&mut j_k1_k0_wrt_k0),
200 );
201
202 let mut j_diff_wrt_k1_k0 = T::zero_jacobian();
204 let se3_diff = se3_k1_k0.compose(se3_k0_k1_measured, Some(&mut j_diff_wrt_k1_k0), None);
205
206 let mut j_log_wrt_diff = T::zero_jacobian();
208 let tangent = se3_diff.log(Some(&mut j_log_wrt_diff));
209 let tangent_slice = tangent.as_slice();
210 let dof = tangent_slice.len();
211
212 residual[..dof].copy_from_slice(tangent_slice);
213
214 if !want_jacobian {
215 return (dof, None);
216 }
217
218 let j_diff_wrt_k0 = j_diff_wrt_k1_k0.clone() * j_k1_k0_wrt_k0;
219 let j_diff_wrt_k1 = j_diff_wrt_k1_k0 * j_k1_k0_wrt_k1;
220 (
221 dof,
222 Some((
223 j_log_wrt_diff.clone() * j_diff_wrt_k0,
224 j_log_wrt_diff * j_diff_wrt_k1,
225 )),
226 )
227 }
228
229 pub(crate) fn linearize_wrt_second(
238 &self,
239 first: &[f64],
240 second: &[f64],
241 residual: &mut [f64],
242 jacobian: Option<faer::mat::MatMut<'_, f64>>,
243 ) {
244 let (dof, blocks) = self.evaluate(first, second, residual, jacobian.is_some());
245 if let (Some(mut jac), Some((_, jacobian_wrt_k1))) = (jacobian, blocks) {
246 for i in 0..dof {
247 for j in 0..dof {
248 *jac.rb_mut().get_mut(i, j) = jacobian_wrt_k1[(i, j)];
249 }
250 }
251 }
252 }
253}
254
255impl<T> Factor for BetweenFactor<T>
256where
257 T: LieGroup + Clone + Send + Sync,
258{
259 fn linearize(
260 &self,
261 params: &[&[f64]],
262 residual: &mut [f64],
263 jacobian: Option<faer::mat::MatMut<'_, f64>>,
264 ) {
265 let (dof, blocks) = self.evaluate(params[0], params[1], residual, jacobian.is_some());
266
267 if let (Some(mut jac), Some((jacobian_wrt_k0, jacobian_wrt_k1))) = (jacobian, blocks) {
268 for i in 0..dof {
269 for j in 0..dof {
270 *jac.rb_mut().get_mut(i, j) = jacobian_wrt_k0[(i, j)];
271 *jac.rb_mut().get_mut(i, j + dof) = jacobian_wrt_k1[(i, j)];
272 }
273 }
274 }
275 }
276
277 fn residual_dim(&self) -> usize {
278 self.relative_pose.tangent_dim()
279 }
280
281 fn jacobian_shape(&self) -> (usize, usize) {
282 let dof = self.relative_pose.tangent_dim();
283 (dof, 2 * dof)
284 }
285
286 fn validate_variables(&self, variables: &[&dyn ManifoldVariable]) -> Result<(), String> {
287 let size = self.relative_pose.as_param_slice().len();
290 expect_uniform_blocks(
291 variables,
292 2,
293 size,
294 "BetweenFactor expects two variables of the measurement's own group",
295 )
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 use apex_manifolds::rn::Rn;
303 use apex_manifolds::se2::{SE2, SE2Tangent};
304 use apex_manifolds::se3::SE3;
305 use apex_manifolds::so2::SO2;
306 use apex_manifolds::so3::SO3;
307 use nalgebra::{DMatrix, DVector, Quaternion, Vector3};
308
309 const TOLERANCE: f64 = 1e-9;
310 const FD_EPSILON: f64 = 1e-6;
311 type TestResult = Result<(), Box<dyn std::error::Error>>;
312
313 fn compute_residual<T>(
314 factor: &BetweenFactor<T>,
315 pose_i: &DVector<f64>,
316 pose_j: &DVector<f64>,
317 ) -> Vec<f64>
318 where
319 T: LieGroup + Clone + Send + Sync,
320 {
321 let mut residual = vec![0.0f64; factor.residual_dim()];
322 factor.linearize(&[pose_i.as_slice(), pose_j.as_slice()], &mut residual, None);
323 residual
324 }
325
326 fn compute_with_jacobian<T>(
327 factor: &BetweenFactor<T>,
328 pose_i: &DVector<f64>,
329 pose_j: &DVector<f64>,
330 ) -> (Vec<f64>, DMatrix<f64>)
331 where
332 T: LieGroup + Clone + Send + Sync,
333 {
334 let (rows, cols) = factor.jacobian_shape();
335 let mut residual = vec![0.0f64; rows];
336 let mut jac_buf = vec![0.0f64; rows * cols];
337 let jac_mut = faer::mat::MatMut::from_column_major_slice_mut(&mut jac_buf, rows, cols);
338 factor.linearize(
339 &[pose_i.as_slice(), pose_j.as_slice()],
340 &mut residual,
341 Some(jac_mut),
342 );
343 let jacobian = DMatrix::from_column_slice(rows, cols, &jac_buf);
344 (residual, jacobian)
345 }
346
347 #[test]
353 fn test_between_factor_rn_jacobian_matches_dimension() -> TestResult {
354 for dim in [1usize, 2, 3, 4, 7] {
355 let relative = Rn::from_slice(&vec![0.25f64; dim]);
356 let factor = BetweenFactor::new(relative);
357
358 let pose_i = DVector::from_vec(vec![0.0f64; dim]);
359 let pose_j = DVector::from_vec(vec![1.0f64; dim]);
360
361 let (rows, cols) = factor.jacobian_shape();
362 assert_eq!(rows, dim, "residual rows for Rn({dim})");
363 assert_eq!(cols, 2 * dim, "jacobian cols for Rn({dim})");
364
365 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
366 assert_eq!(residual.len(), dim);
367 assert_eq!(jacobian.nrows(), dim);
368 assert_eq!(jacobian.ncols(), 2 * dim);
369 assert!(
370 jacobian.iter().all(|v| v.is_finite()),
371 "non-finite Jacobian entry for Rn({dim})"
372 );
373 }
374 Ok(())
375 }
376
377 #[test]
381 fn test_between_factor_rn_jacobian_matches_finite_differences() -> TestResult {
382 for dim in [2usize, 4] {
383 let relative = Rn::from_slice(&vec![0.5f64; dim]);
384 let factor = BetweenFactor::new(relative);
385
386 let pose_i = DVector::from_vec((0..dim).map(|k| 0.1 * k as f64).collect::<Vec<_>>());
387 let pose_j =
388 DVector::from_vec((0..dim).map(|k| 1.0 - 0.2 * k as f64).collect::<Vec<_>>());
389
390 let (_, analytic) = compute_with_jacobian(&factor, &pose_i, &pose_j);
391
392 for (block, pose) in [(0usize, &pose_i), (1usize, &pose_j)] {
393 for c in 0..dim {
394 let mut plus = pose.clone();
395 let mut minus = pose.clone();
396 plus[c] += FD_EPSILON;
397 minus[c] -= FD_EPSILON;
398
399 let (rp, rm) = if block == 0 {
400 (
401 compute_residual(&factor, &plus, &pose_j),
402 compute_residual(&factor, &minus, &pose_j),
403 )
404 } else {
405 (
406 compute_residual(&factor, &pose_i, &plus),
407 compute_residual(&factor, &pose_i, &minus),
408 )
409 };
410
411 for r in 0..dim {
412 let fd = (rp[r] - rm[r]) / (2.0 * FD_EPSILON);
413 let an = analytic[(r, block * dim + c)];
414 assert!(
415 (fd - an).abs() < 1e-6,
416 "Rn({dim}) block {block} entry ({r},{c}): analytic {an}, fd {fd}"
417 );
418 }
419 }
420 }
421 }
422 Ok(())
423 }
424
425 #[test]
426 fn test_between_factor_se2_identity() {
427 let relative = SE2::identity();
428 let factor = BetweenFactor::new(relative);
429
430 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);
431 let pose_j = DVector::from_vec(vec![0.0, 0.0, 0.0]);
432
433 let residual = compute_residual(&factor, &pose_i, &pose_j);
434
435 assert_eq!(residual.len(), 3);
436 let norm: f64 = residual.iter().map(|x| x * x).sum::<f64>().sqrt();
437 assert!(norm < TOLERANCE, "Residual norm: {}", norm);
438 }
439
440 #[test]
441 fn test_between_factor_se3_identity() {
442 let relative = SE3::identity();
443 let factor = BetweenFactor::new(relative);
444
445 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
446 let pose_j = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
447
448 let residual = compute_residual(&factor, &pose_i, &pose_j);
449
450 assert_eq!(residual.len(), 6);
451 let norm: f64 = residual.iter().map(|x| x * x).sum::<f64>().sqrt();
452 assert!(norm < TOLERANCE, "Residual norm: {}", norm);
453 }
454
455 #[test]
456 fn test_between_factor_se2_jacobian_numerical() -> TestResult {
457 let relative = SE2::from_xy_angle(1.0, 0.0, 0.1);
458 let factor = BetweenFactor::new(relative);
459
460 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);
461 let pose_j = DVector::from_vec(vec![0.95, 0.05, 0.12]);
462
463 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
464
465 assert_eq!(jacobian.nrows(), 3);
466 assert_eq!(jacobian.ncols(), 6);
467
468 let mut jacobian_fd = DMatrix::<f64>::zeros(3, 6);
469 let se2_i = SE2::from_param_slice(pose_i.as_slice());
470 let se2_j = SE2::from_param_slice(pose_j.as_slice());
471
472 for i in 0..3 {
473 let delta = match i {
474 0 => SE2Tangent::new(FD_EPSILON, 0.0, 0.0),
475 1 => SE2Tangent::new(0.0, FD_EPSILON, 0.0),
476 2 => SE2Tangent::new(0.0, 0.0, FD_EPSILON),
477 _ => unreachable!(),
478 };
479 let pose_i_p =
480 DVector::from_column_slice(se2_i.plus(&delta, None, None).as_param_slice());
481 let residual_p = compute_residual(&factor, &pose_i_p, &pose_j);
482 for j in 0..3 {
483 jacobian_fd[(j, i)] = (residual_p[j] - residual[j]) / FD_EPSILON;
484 }
485 }
486
487 for i in 0..3 {
488 let delta = match i {
489 0 => SE2Tangent::new(FD_EPSILON, 0.0, 0.0),
490 1 => SE2Tangent::new(0.0, FD_EPSILON, 0.0),
491 2 => SE2Tangent::new(0.0, 0.0, FD_EPSILON),
492 _ => unreachable!(),
493 };
494 let pose_j_p =
495 DVector::from_column_slice(se2_j.plus(&delta, None, None).as_param_slice());
496 let residual_p = compute_residual(&factor, &pose_i, &pose_j_p);
497 for j in 0..3 {
498 jacobian_fd[(j, i + 3)] = (residual_p[j] - residual[j]) / FD_EPSILON;
499 }
500 }
501
502 let diff_norm = (jacobian - jacobian_fd).norm();
503 assert!(diff_norm < 1e-5, "Jacobian difference norm: {}", diff_norm);
504 Ok(())
505 }
506
507 #[test]
508 fn test_between_factor_se3_jacobian_numerical() -> TestResult {
509 let relative = SE3::from_translation_quaternion(
510 Vector3::new(1.0, 0.0, 0.0),
511 Quaternion::new(1.0, 0.0, 0.0, 0.0),
512 );
513 let factor = BetweenFactor::new(relative);
514
515 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
516 let pose_j = DVector::from_vec(vec![0.95, 0.05, 0.0, 1.0, 0.0, 0.0, 0.0]);
517
518 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
519
520 assert_eq!(jacobian.nrows(), 6);
521 assert_eq!(jacobian.ncols(), 12);
522
523 let mut jacobian_fd = DMatrix::<f64>::zeros(6, 12);
524
525 for i in 0..3 {
526 let mut pose_i_p = pose_i.clone();
527 pose_i_p[i] += FD_EPSILON;
528 let residual_p = compute_residual(&factor, &pose_i_p, &pose_j);
529 for j in 0..6 {
530 jacobian_fd[(j, i)] = (residual_p[j] - residual[j]) / FD_EPSILON;
531 }
532 }
533
534 for i in 0..3 {
535 let mut pose_j_p = pose_j.clone();
536 pose_j_p[i] += FD_EPSILON;
537 let residual_p = compute_residual(&factor, &pose_i, &pose_j_p);
538 for j in 0..6 {
539 jacobian_fd[(j, i + 6)] = (residual_p[j] - residual[j]) / FD_EPSILON;
540 }
541 }
542
543 let diff_norm_trans = (jacobian.columns(0, 3) - jacobian_fd.columns(0, 3)).norm();
544 assert!(
545 diff_norm_trans < 1e-5,
546 "Jacobian difference norm (translation): {}",
547 diff_norm_trans
548 );
549 Ok(())
550 }
551
552 #[test]
553 fn test_between_factor_dimension_se2() -> TestResult {
554 let relative = SE2::from_xy_angle(1.0, 0.5, 0.1);
555 let factor = BetweenFactor::new(relative);
556
557 assert_eq!(factor.residual_dim(), 3);
558 assert_eq!(factor.jacobian_shape(), (3, 6));
559
560 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0]);
561 let pose_j = DVector::from_vec(vec![1.0, 0.0, 0.0]);
562 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
563
564 assert_eq!(residual.len(), 3);
565 assert_eq!(jacobian.nrows(), 3);
566 assert_eq!(jacobian.ncols(), 6);
567 Ok(())
568 }
569
570 #[test]
571 fn test_between_factor_dimension_se3() -> TestResult {
572 let relative = SE3::identity();
573 let factor = BetweenFactor::new(relative);
574
575 assert_eq!(factor.residual_dim(), 6);
576 assert_eq!(factor.jacobian_shape(), (6, 12));
577
578 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
579 let pose_j = DVector::from_vec(vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
580 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
581
582 assert_eq!(residual.len(), 6);
583 assert_eq!(jacobian.nrows(), 6);
584 assert_eq!(jacobian.ncols(), 12);
585 Ok(())
586 }
587
588 #[test]
589 fn test_between_factor_so2_so3() -> TestResult {
590 let so2_relative = SO2::from_angle(0.1);
591 let so2_factor = BetweenFactor::new(so2_relative);
592
593 assert_eq!(so2_factor.residual_dim(), 1);
594 assert_eq!(so2_factor.jacobian_shape(), (1, 2));
595
596 let so2_i = DVector::from_vec(vec![0.0]);
597 let so2_j = DVector::from_vec(vec![0.12]);
598 let (res_so2, jac_so2) = compute_with_jacobian(&so2_factor, &so2_i, &so2_j);
599 assert_eq!(res_so2.len(), 1);
600 assert_eq!(jac_so2.nrows(), 1);
601 assert_eq!(jac_so2.ncols(), 2);
602
603 let so3_relative = SO3::identity();
604 let so3_factor = BetweenFactor::new(so3_relative);
605
606 let so3_i = DVector::from_vec(vec![1.0, 0.0, 0.0, 0.0]);
607 let so3_j = DVector::from_vec(vec![1.0, 0.0, 0.0, 0.0]);
608 let (res_so3, jac_so3) = compute_with_jacobian(&so3_factor, &so3_i, &so3_j);
609 assert_eq!(res_so3.len(), 3);
610 assert_eq!(jac_so3.nrows(), 3);
611 assert_eq!(jac_so3.ncols(), 6);
612 Ok(())
613 }
614
615 #[test]
616 fn test_between_factor_finiteness() -> TestResult {
617 let relative = SE2::from_xy_angle(100.0, -200.0, std::f64::consts::PI);
618 let factor = BetweenFactor::new(relative);
619
620 let pose_i = DVector::from_vec(vec![50.0, -100.0, 1.5]);
621 let pose_j = DVector::from_vec(vec![150.0, -300.0, -1.5]);
622
623 let (residual, jacobian) = compute_with_jacobian(&factor, &pose_i, &pose_j);
624
625 assert!(residual.iter().all(|x| x.is_finite()));
626 assert!(jacobian.iter().all(|x| x.is_finite()));
627 Ok(())
628 }
629
630 #[test]
631 fn test_between_factor_clone() {
632 let relative = SE3::identity();
633 let factor = BetweenFactor::new(relative);
634 let factor_clone = factor.clone();
635
636 let pose_i = DVector::from_vec(vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
637 let pose_j = DVector::from_vec(vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
638
639 let r1 = compute_residual(&factor, &pose_i, &pose_j);
640 let r2 = compute_residual(&factor_clone, &pose_i, &pose_j);
641
642 let diff: f64 = r1.iter().zip(r2.iter()).map(|(a, b)| (a - b).abs()).sum();
643 assert!(diff < TOLERANCE);
644 }
645}