Skip to content

Commit dca3a84

Browse files
authored
ENH: Fix Orientation Param of Inertial Sensors (#688)
* ENH: use 313 rotation * TST: fix tests * DOC: fix notebook * DEV: changelog * MNT: black on notebooks
1 parent 186cae7 commit dca3a84

File tree

9 files changed

+83
-117
lines changed

9 files changed

+83
-117
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ Attention: The newest changes should be on top -->
3232

3333
### Added
3434

35-
- ENH: Adds Sensors classes [683](https://github.com/RocketPy-Team/RocketPy/pull/683)
35+
- ENH: Fix Orientation Param of Inertial Sensors [#688](https://github.com/RocketPy-Team/RocketPy/pull/688)
36+
- ENH: Adds Sensors classes [#683](https://github.com/RocketPy-Team/RocketPy/pull/683)
3637
- DOC: Cavour Flight Example [#682](https://github.com/RocketPy-Team/RocketPy/pull/682)
3738
- DOC: Halcyon Flight Example [#681](https://github.com/RocketPy-Team/RocketPy/pull/681)
3839
- ENH: Adds GenericMotor.load_from_eng_file() method [#676](https://github.com/RocketPy-Team/RocketPy/pull/676)

docs/notebooks/sensors.ipynb

Lines changed: 26 additions & 31 deletions
Large diffs are not rendered by default.

docs/user/rocket/rocket_axes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _rocket_axes:
2+
13
Rocket Class Axes Definitions
24
=============================
35

rocketpy/mathutils/vector_matrix.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from functools import cached_property
33
from itertools import product
44

5-
from rocketpy.tools import euler321_to_quaternions, normalize_quaternions
5+
from rocketpy.tools import euler313_to_quaternions, normalize_quaternions
66

77

88
class Vector:
@@ -1063,25 +1063,26 @@ def transformation(quaternion):
10631063
)
10641064

10651065
@staticmethod
1066-
def transformation_euler_angles(roll, pitch, yaw):
1066+
def transformation_euler_angles(roll, pitch, roll2):
10671067
"""Returns the transformation Matrix from frame B to frame A, where B
1068-
is rotated by the Euler angles roll, pitch and yaw with respect to A.
1068+
is rotated by the Euler angles roll, pitch and roll2 in an instrinsic
1069+
3-1-3 sequence with respect to A.
10691070
10701071
Parameters
10711072
----------
10721073
roll : float
1073-
The roll angle in degrees.
1074+
The roll angle in radians.
10741075
pitch : float
1075-
The pitch angle in degrees.
1076-
yaw : float
1077-
The yaw angle in degrees.
1076+
The pitch angle in radians.
1077+
roll2 : float
1078+
The roll2 angle in radians.
10781079
10791080
Returns
10801081
-------
10811082
Matrix
10821083
The transformation matrix from frame B to frame A.
10831084
"""
1084-
return Matrix.transformation(euler321_to_quaternions(roll, pitch, yaw))
1085+
return Matrix.transformation(euler313_to_quaternions(roll, pitch, roll2))
10851086

10861087

10871088
if __name__ == "__main__":

rocketpy/sensors/sensor.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ class InertialSensor(Sensor):
308308
name : str
309309
The name of the sensor.
310310
rotation_matrix : Matrix
311-
The rotation matrix of the sensor from the sensor frame to the rocket
311+
The rotation matrix of the sensor from the rocket frame to the sensor
312312
frame of reference.
313313
normal_vector : Vector
314314
The normal vector of the sensor in the rocket frame of reference.
@@ -345,22 +345,21 @@ def __init__(
345345
sampling_rate : float
346346
Sample rate of the sensor
347347
orientation : tuple, list, optional
348-
Orientation of the sensor in the rocket. The orientation can be
349-
given as:
350-
- A list of length 3, where the elements are the Euler angles for
351-
the rotation yaw (ψ), pitch (θ) and roll (φ) in radians. The
352-
standard rotation sequence is z-y-x (3-2-1) is used, meaning the
353-
sensor is first rotated by ψ around the x axis, then by θ around
354-
the new y axis and finally by φ around the new z axis.
348+
Orientation of the sensor in relation to the rocket frame of
349+
reference (Body Axes Coordinate System). See :ref:'rocket_axes' for
350+
more information.
351+
If orientation is not given, the sensor axes will be aligned with
352+
the rocket axis.
353+
The orientation can be given as:
354+
- A list or tuple of length 3, where the elements are the intrisic
355+
rotation angles in radians. The rotation sequence z-x-z (3-1-3) is
356+
used, meaning the sensor is first around the z axis (roll), then
357+
around the new x axis (pitch) and finally around the new z axis
358+
(roll).
355359
- A list of lists (matrix) of shape 3x3, representing the rotation
356360
matrix from the sensor frame to the rocket frame. The sensor frame
357-
of reference is defined as to have z axis along the sensor's normal
358-
vector pointing upwards, x and y axes perpendicular to the z axis
359-
and each other.
360-
The rocket frame of reference is defined as to have z axis
361-
along the rocket's axis of symmetry pointing upwards, x and y axes
362-
perpendicular to the z axis and each other. Default is (0, 0, 0),
363-
meaning the sensor is aligned with the rocket's axis of symmetry.
361+
of reference is defined as being initially aligned with the rocket
362+
frame of reference.
364363
measurement_range : float, tuple, optional
365364
The measurement range of the sensor in the sensor units. If a float,
366365
the same range is applied both for positive and negative values. If
@@ -463,7 +462,7 @@ def __init__(
463462
self.rotation_matrix = Matrix(orientation)
464463
elif len(orientation) == 3: # euler angles
465464
self.rotation_matrix = Matrix.transformation_euler_angles(
466-
*orientation
465+
*np.deg2rad(orientation)
467466
).round(12)
468467
else:
469468
raise ValueError("Invalid orientation format")

rocketpy/tools.py

Lines changed: 11 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,48 +1107,14 @@ def normalize_quaternions(quaternions):
11071107
return q_w / q_norm, q_x / q_norm, q_y / q_norm, q_z / q_norm
11081108

11091109

1110-
def euler321_to_quaternions(psi, theta, phi):
1111-
"""Calculates the quaternions (Euler parameters) from the Euler angles in
1112-
yaw, pitch, and roll sequence (3-2-1).
1113-
1114-
Parameters
1115-
----------
1116-
psi : float
1117-
Euler angle due to roll in degrees, also known as the spin angle.
1118-
theta : float
1119-
Euler angle due to pitch in degrees, also known as the nutation angle.
1120-
phi : float
1121-
Euler angle due to yaw in degrees, also known as the precession angle.
1122-
1123-
Returns
1124-
-------
1125-
tuple[float, float, float, float]
1126-
Tuple containing the Euler parameters e0, e1, e2, e3
1127-
"""
1128-
phi = math.radians(phi)
1129-
theta = math.radians(theta)
1130-
psi = math.radians(psi)
1131-
cr = math.cos(phi / 2)
1132-
sr = math.sin(phi / 2)
1133-
cp = math.cos(theta / 2)
1134-
sp = math.sin(theta / 2)
1135-
cy = math.cos(psi / 2)
1136-
sy = math.sin(psi / 2)
1137-
e0 = cr * cp * cy + sr * sp * sy
1138-
e1 = sr * cp * cy - cr * sp * sy
1139-
e2 = cr * sp * cy + sr * cp * sy
1140-
e3 = cr * cp * sy - sr * sp * cy
1141-
return e0, e1, e2, e3
1142-
1143-
11441110
def euler313_to_quaternions(phi, theta, psi):
11451111
"""Convert 3-1-3 Euler angles to Euler parameters (quaternions).
11461112
11471113
Parameters
11481114
----------
11491115
phi : float
11501116
Rotation angle around the z-axis (in radians). Represents the precession
1151-
angle or the yaw angle.
1117+
angle or the roll angle.
11521118
theta : float
11531119
Rotation angle around the x-axis (in radians). Represents the nutation
11541120
angle or the pitch angle.
@@ -1165,18 +1131,16 @@ def euler313_to_quaternions(phi, theta, psi):
11651131
----------
11661132
https://www.astro.rug.nl/software/kapteyn-beta/_downloads/attitude.pdf
11671133
"""
1168-
e0 = np.cos(phi / 2) * np.cos(theta / 2) * np.cos(psi / 2) - np.sin(
1169-
phi / 2
1170-
) * np.cos(theta / 2) * np.sin(psi / 2)
1171-
e1 = np.cos(phi / 2) * np.cos(psi / 2) * np.sin(theta / 2) + np.sin(
1172-
phi / 2
1173-
) * np.sin(theta / 2) * np.sin(psi / 2)
1174-
e2 = np.cos(phi / 2) * np.sin(theta / 2) * np.sin(psi / 2) - np.sin(
1175-
phi / 2
1176-
) * np.cos(psi / 2) * np.sin(theta / 2)
1177-
e3 = np.cos(phi / 2) * np.cos(theta / 2) * np.sin(psi / 2) + np.cos(
1178-
theta / 2
1179-
) * np.cos(psi / 2) * np.sin(phi / 2)
1134+
cphi = np.cos(phi / 2)
1135+
sphi = np.sin(phi / 2)
1136+
ctheta = np.cos(theta / 2)
1137+
stheta = np.sin(theta / 2)
1138+
cpsi = np.cos(psi / 2)
1139+
spsi = np.sin(psi / 2)
1140+
e0 = cphi * ctheta * cpsi - sphi * ctheta * spsi
1141+
e1 = cphi * cpsi * stheta + sphi * stheta * spsi
1142+
e2 = cphi * stheta * spsi - sphi * cpsi * stheta
1143+
e3 = cphi * ctheta * spsi + ctheta * cpsi * sphi
11801144
return e0, e1, e2, e3
11811145

11821146

tests/unit/test_sensor.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pytest import approx
77

88
from rocketpy.mathutils.vector_matrix import Matrix, Vector
9-
from rocketpy.tools import euler321_to_quaternions
9+
from rocketpy.tools import euler313_to_quaternions
1010

1111
# calisto standard simulation no wind solution index 200
1212
TIME = 3.338513236767685
@@ -71,9 +71,9 @@ def test_rotation_matrix(noisy_rotated_accelerometer):
7171
# values from external source
7272
expected_matrix = np.array(
7373
[
74-
[0.2500000, -0.0580127, 0.9665064],
75-
[0.4330127, 0.8995190, -0.0580127],
76-
[-0.8660254, 0.4330127, 0.2500000],
74+
[-0.125, -0.6495190528383292, 0.7499999999999999],
75+
[0.6495190528383292, -0.625, -0.43301270189221946],
76+
[0.7499999999999999, 0.43301270189221946, 0.5000000000000001],
7777
]
7878
)
7979
rotation_matrix = np.array(noisy_rotated_accelerometer.rotation_matrix.components)
@@ -287,7 +287,9 @@ def test_noisy_rotated_accelerometer(noisy_rotated_accelerometer, example_plain_
287287
[0.005, 0.005, 1],
288288
]
289289
)
290-
sensor_rotation = Matrix.transformation(euler321_to_quaternions(60, 60, 60))
290+
sensor_rotation = Matrix.transformation(
291+
euler313_to_quaternions(*np.deg2rad([60, 60, 60]))
292+
)
291293
total_rotation = sensor_rotation @ cross_axis_sensitivity
292294
rocket_rotation = Matrix.transformation(U[6:10])
293295
# expected measurement without noise
@@ -328,7 +330,9 @@ def test_noisy_rotated_gyroscope(noisy_rotated_gyroscope, example_plain_env):
328330
[0.005, 0.005, 1],
329331
]
330332
)
331-
sensor_rotation = Matrix.transformation(euler321_to_quaternions(-60, -60, -60))
333+
sensor_rotation = Matrix.transformation(
334+
euler313_to_quaternions(*np.deg2rad([-60, -60, -60]))
335+
)
332336
total_rotation = sensor_rotation @ cross_axis_sensitivity
333337
rocket_rotation = Matrix.transformation(U[6:10])
334338
# expected measurement without noise

tests/unit/test_tools.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33

44
from rocketpy.tools import (
55
calculate_cubic_hermite_coefficients,
6-
euler321_to_quaternions,
6+
euler313_to_quaternions,
77
find_roots_cubic_function,
88
)
99

1010

1111
@pytest.mark.parametrize(
1212
"angles, expected_quaternions",
13-
[((0, 0, 0), (1, 0, 0, 0)), ((90, 90, 90), (0.7071068, 0, 0.7071068, 0))],
13+
[((0, 0, 0), (1, 0, 0, 0)), ((90, 90, 90), (0, 0.7071068, 0, 0.7071068))],
1414
)
1515
def test_euler_to_quaternions(angles, expected_quaternions):
16-
q0, q1, q2, q3 = euler321_to_quaternions(*angles)
16+
q0, q1, q2, q3 = euler313_to_quaternions(*np.deg2rad(angles))
1717
assert round(q0, 7) == expected_quaternions[0]
1818
assert round(q1, 7) == expected_quaternions[1]
1919
assert round(q2, 7) == expected_quaternions[2]

tests/unit/test_tools_matrix.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,15 +245,15 @@ def test_matrix_transformation():
245245

246246

247247
def test_matrix_transformation_euler_angles():
248-
phi = 0
249-
theta = 0
250-
psi = 90
251-
matrix = Matrix.transformation_euler_angles(phi, theta, psi)
248+
roll = np.pi / 2
249+
pitch = np.pi / 2
250+
roll2 = np.pi / 2
251+
matrix = Matrix.transformation_euler_angles(roll, pitch, roll2)
252252
matrix = matrix.round(12)
253253
# Check that the matrix is orthogonal
254254
assert matrix @ matrix.transpose == Matrix.identity()
255255
# Check that the matrix rotates the vector correctly
256-
assert matrix @ Vector([0, 0, 1]) == Vector([0, -1, 0])
256+
assert matrix @ Vector([0, 0, 1]) == Vector([1, 0, 0])
257257

258258

259259
def test_matrix_round():

0 commit comments

Comments
 (0)