Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Numerically vulnerable axis calculation in Rotation3 #1382

Open
IshitaTakeshi opened this issue Apr 11, 2024 · 0 comments
Open

Numerically vulnerable axis calculation in Rotation3 #1382

IshitaTakeshi opened this issue Apr 11, 2024 · 0 comments

Comments

@IshitaTakeshi
Copy link

Abstract

nalgebra::Rotation3::axis relies on numerically vulnerable calculation.
It may return None in the place where it should return a non-None value.

Issue

In the current implementation of Rotation3, the rotation axis is calculated in the following way.

    #[inline]
    #[must_use]
    pub fn axis(&self) -> Option<Unit<Vector3<T>>>
    where
        T: RealField,
    {
        let rotmat = self.matrix();
        let axis = SVector::<T, 3>::new(
            rotmat[(2, 1)].clone() - rotmat[(1, 2)].clone(),
            rotmat[(0, 2)].clone() - rotmat[(2, 0)].clone(),
            rotmat[(1, 0)].clone() - rotmat[(0, 1)].clone(),
        );

        Unit::try_new(axis, T::default_epsilon())
    }

If I initialize Rotation3 with a rotation of π radians around the y-axis, the code will look like this.

use nalgebra::{Rotation3, Vector3};

const PI: f64 = std::f64::consts::PI;

fn main() {
    let r = Rotation3::from_axis_angle(&Vector3::y_axis(), PI);
    println!("axis = {:?}", r.axis());
    println!("axis_angle = {:?}", r.axis_angle());
}

And it actually works.

axis = Some([[0.0, 1.0, 0.0]])
axis_angle = Some(([[0.0, 1.0, 0.0]], 3.141592653589793))

However, this is very vulnerable, because this behavior relies on the rounding error of the internal matrix representation.

I added the line to print the axis value in the axis function.

    #[inline]
    #[must_use]
    pub fn axis(&self) -> Option<Unit<Vector3<T>>>
    where  
        T: RealField,
    {
        let rotmat = self.matrix();
        let axis = SVector::<T, 3>::new(
            rotmat[(2, 1)].clone() - rotmat[(1, 2)].clone(),
            rotmat[(0, 2)].clone() - rotmat[(2, 0)].clone(),
            rotmat[(1, 0)].clone() - rotmat[(0, 1)].clone(),
        );
          
        println!("intelnal axis value = {:?}", axis);
        Unit::try_new(axis, T::default_epsilon())    
    }

And it said

intelnal axis value = [[0.0, 2.4492935982947064e-16, 0.0]]

This is very close to [0, 0, 0]. If there was no rounding error, this code would return None in the place where it should return [0, 1, 0].

Why this happens

The matrix corresponding to the rotation of π radians around the y-axis is, without rounding error,

-1  0  0
 0  1  0
 0  0 -1

So if we calculate axis based on the matrix above, it becomes [0, 0, 0].
The same happens for the rotation around the x-axis, z-axis, or rotation of π radians around any axis. Actually, on my Ubuntu desktop, the code below printed None.

use nalgebra::{Rotation3, Vector3, Unit};

const PI: f64 = std::f64::consts::PI;

fn main() {
    let x = Unit::new_normalize(Vector3::new(1., 2., 1.));
    let r = Rotation3::from_axis_angle(&x, PI);
    println!("axis = {:?}", r.axis());
}

Possible solution

One possible solution is to handle these singular value cases as explained in this paper, section 9.4.1.2 "Logarithm map".
A tutorial on SE(3) transformation parameterizations and on-manifold optimization

Another solution is to replace the internal rotation representation in Rotation3 from a 3x3 matrix to a unit quaternion.
The conversion from a unit quaternion to a rotation vector is described in the same section of the paper.
This will improve the overall performance including rotation multiplication, but requires a large amount of modifications.

@IshitaTakeshi IshitaTakeshi changed the title Numecirally vulnerable axis calculation in Rotation3 Numerically vulnerable axis calculation in Rotation3 Apr 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant