-
-
Notifications
You must be signed in to change notification settings - Fork 282
rotation_matrix issues #1360
Comments
The Astropy result could an alias vs alibi kind of confusion https://en.m.wikipedia.org/wiki/Rotation_matrix#Ambiguities For our code, this test is not very convincing:
Maybe this is a genuine bug? That's embarrassing... |
Surprisingly, you do use the astropy function in quite a number of places in poliastro, hopefully, aware of the differences... By the way, after a few experiments, I do believe that the astropy function can be both vectorized and jitted with a few modifications and the latest numba version.
I don't see axis=1 ever used in the code, so it wouldn't be surprising that nobody noticed until now. Is there some testsuite that could be used to validate the functions? |
I'm happy to send a pull request but I would like to make sure what the correct behavior should be. |
Just for the record in case I forget about this. I created a vectorized by angle and JITable version of @jit
def rotation_matrix_vec(angle, axis):
assert axis in (0,1,2)
angle = np.asarray(angle)
c = cos(angle)
s = sin(angle)
i = axis
a1 = (i + 1) % 3
a2 = (i + 2) % 3
R = np.zeros(angle.shape + (3, 3))
R[..., i, i] = 1.
R[..., a1, a1] = c
R[..., a1, a2] = -s
R[..., a2, a1] = s
R[..., a2, a2] = c
return R Moreover, I believe the above function is nice to have as a utility, however, given that there are only 3 possible values of axis, it would be better to create 3 specializations that are simpler and faster: import numpy as np
from numba import njit as jit
from numpy import cos, sin
# Original from poliastro
@jit
def rotation_matrix(angle, axis):
c = cos(angle)
s = sin(angle)
if axis == 0:
return np.array([[1.0, 0.0, 0.0], [0.0, c, -s], [0.0, s, c]])
elif axis == 1:
# Original: np.array([[c, 0.0, s], [0.0, 1.0, 0.0], [s, 0.0, c]])
# Fixed ???
return np.array([[c, 0.0, s], [0.0, 1.0, 0.0], [-s, 0.0, c]])
elif axis == 2:
return np.array([[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]])
else:
raise ValueError("Invalid axis: must be one of 'x', 'y' or 'z'")
@jit
def rotation_matrix_vec(angle, axis):
assert axis in (0,1,2)
angle = np.asarray(angle)
c = cos(angle)
s = sin(angle)
i = axis
a1 = (i + 1) % 3
a2 = (i + 2) % 3
R = np.zeros(angle.shape + (3, 3))
R[..., i, i] = 1.
R[..., a1, a1] = c
R[..., a1, a2] = -s
R[..., a2, a1] = s
R[..., a2, a2] = c
return R
@jit
def rotation_matrix_x(angle):
angle = np.asarray(angle)
c = cos(angle)
s = sin(angle)
R = np.zeros(angle.shape + (3, 3))
R[..., 0, 0] = 1.
R[..., 1, 1] = c
R[..., 1, 2] = -s
R[..., 2, 1] = s
R[..., 2, 2] = c
return R
@jit
def rotation_matrix_y(angle):
angle = np.asarray(angle)
c = cos(angle)
s = sin(angle)
a1 = 2
a2 = 0
R = np.zeros(angle.shape + (3, 3))
R[..., 1, 1] = 1.
R[..., 2, 2] = c
R[..., 2, 0] = -s
R[..., 0, 2] = s
R[..., 0, 0] = c
return R
@jit
def rotation_matrix_z(angle):
angle = np.asarray(angle)
c = cos(angle)
s = sin(angle)
a1 = 0
a2 = 1
R = np.zeros(angle.shape + (3, 3))
R[..., 2, 2] = 1.
R[..., 0, 0] = c
R[..., 0, 1] = -s
R[..., 1, 0] = s
R[..., 1, 1] = c
return R
def _test_rotation_matrix(angle, axis):
angle = np.asarray(angle)
expected = rotation_matrix(angle, axis)
result = rotation_matrix_vec(angle, axis)
assert np.allclose(expected, result),f'exp={expected}\nres={result}'
if axis==0:
expected = rotation_matrix_x(angle)
elif axis==1:
expected = rotation_matrix_y(angle)
elif axis==2:
expected = rotation_matrix_z(angle)
assert np.allclose(expected, result), f'exp={expected}\nres={result}'
def test_rotation_matrix_x():
_test_rotation_matrix(0.218,0)
def test_rotation_matrix_y():
_test_rotation_matrix(0.218, 1)
def test_rotation_matrix_z():
_test_rotation_matrix(0.218, 2)
test_rotation_matrix_x()
test_rotation_matrix_y()
test_rotation_matrix_z() |
Progress:
This is more clearly explained in the docstring of the function https://github.com/astropy/astropy/blob/0eb1bf2ab367c3203ba7d98e385da906880b8d3d/astropy/coordinates/matrix_utilities.py#L51-L53
I'm used to the right-hand rule and pre-multiplication of column vectors. We should clarify this in the docs.
But around Y, they give different results:
and if I change the sign as you suggested initially, everything works:
And I absolutely trust Astropy to be correct on this one, plus it's very clear that we have a typo here and that our tests are dumb. So:
|
We are using the Astropy version in 2 cases: poliastro/src/poliastro/frames/ecliptic.py Lines 93 to 107 in 0e14e52
This one is okay, because the rotation axis is not (x, y, z), but a custom one. We don't support that (yet?). poliastro/src/poliastro/frames/fixed.py Line 92 in 95cf3ff
(both This is probably because we copy-pasted part of this code from Astropy itself. The signs look correct. |
Fix #1360 vectorize rotation_matrix and fix axis=1
It has to be faster since I far as I can tell, numba cannot do specialization by itself (that would require interprocedural optimization). But we have to define "really much faster". I will try to test it with my code for GTOC11, but that code is too massive to become a test (or even to share it). |
🐞 Problem
Perhaps I'm missing some detail but the implementation of
rotation_matrix
looks strange and it doesn't match the one in astropy (but the one of astropy also looks strange).According to Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations
the rotation for axis=1 should be
instead of
poliastro/src/poliastro/core/util.py
Line 33 in 18a390d
Moreover, the version in astropy:
https://github.com/astropy/astropy/blob/39cb148a4bd69368f6f30b9abfe32112a42f58e6/astropy/coordinates/matrix_utilities.py#L92-L99
Actually creates:
which probably is due to the fact that "the rotation sense is counterclockwise looking down the + axis (e.g. positive rotations obey left-hand-rule)." But I don't understand the matrix generated by poliastro.
The text was updated successfully, but these errors were encountered: