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

ENH: Sensors #583

Merged
merged 83 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
e403872
ENH: sensors class
MateusStano Apr 1, 2024
6cd3598
ENH: add accelerometer and gyroscope classes
MateusStano Apr 1, 2024
3b81777
ENH: euler to quaternions tool function
MateusStano Apr 1, 2024
4c8aa13
ENH: add sensors to other classes
MateusStano Apr 1, 2024
09e0d75
ENH: prints and sensors draw
MateusStano Apr 1, 2024
38866f4
ENH: add sensors to parachutes
MateusStano Apr 1, 2024
b386e38
TST: add sensors fixtures
MateusStano Apr 1, 2024
6ff2dde
TST: add sensors tests
MateusStano Apr 1, 2024
5ae01b9
DEV: sensors testing notebook
MateusStano Apr 1, 2024
1dd5781
ENH: add option to add noise to each axis seperatly
MateusStano Apr 2, 2024
943542a
MNT: run isort
MateusStano Apr 2, 2024
28ebc46
ENH: calculate u_dot only once for all sensors
MateusStano Apr 2, 2024
378bb54
MNT: change `measured_values` to `measured_data`
MateusStano Apr 2, 2024
d8440f2
TST: unite gyro and accel tests
MateusStano Apr 2, 2024
99f445f
Update rocketpy/plots/rocket_plots.py
MateusStano Apr 11, 2024
4db26f0
ENH: normalize quaternions for transform matrix
MateusStano Apr 12, 2024
4356af7
TST: lower tolerances
MateusStano Apr 12, 2024
5c37f06
BUG: handle zero norm differently due to numpy.float64
MateusStano Apr 15, 2024
8d64998
MNT: quantization typos
MateusStano Apr 15, 2024
28da8f3
DOCS: mention Hadamard product
MateusStano Apr 15, 2024
cef72a0
DOC: complete sensors class docstrings
MateusStano Apr 15, 2024
5f8f1f4
Squashed commit of the following:
MateusStano Apr 29, 2024
9a6b052
MNT: fix component repr for sensors
MateusStano Apr 30, 2024
7953cb0
ENH: add _attatched_rockets to sensors
MateusStano Apr 30, 2024
5a37553
ENH: add sensors saving methods
MateusStano Apr 30, 2024
09ea252
MNT: simplify sensors repr
MateusStano Apr 30, 2024
9851392
ENH: use _save_data in accel and gyro
MateusStano Apr 30, 2024
0f81bc3
ENH: improve accelerometer export
MateusStano Apr 30, 2024
a740fc2
ENH: imrpove gyroscope export
MateusStano Apr 30, 2024
cf6c26d
ENH: add sensor initialization
MateusStano Apr 30, 2024
2a14f1d
ENH: speed up measure call
MateusStano Apr 30, 2024
216523c
ENH: add final sensor cache
MateusStano Apr 30, 2024
09288d4
ENH: add export sensor data
MateusStano Apr 30, 2024
aa6fcdf
BUG: wrong initialization order
MateusStano Apr 30, 2024
bf6b083
TST: test for new measured_data and exports
MateusStano Apr 30, 2024
5f86223
ENH: abstract noise printings
MateusStano Apr 30, 2024
4dcc26b
MNT: run isort
MateusStano May 1, 2024
32898c5
Merge branch 'develop' into enh/sensors
MateusStano May 1, 2024
f7332d8
Merge branch 'develop' into enh/sensors
Gui-FernandesBR May 2, 2024
2131ee9
ENH: add noise variance
MateusStano May 2, 2024
b2da0c3
BUG: fix prints for noise variance
MateusStano May 2, 2024
123d033
TST: add variances
MateusStano May 2, 2024
7d0e6f3
Merge branch 'enh/sensors' of https://github.com/RocketPy-Team/Rocket…
MateusStano May 2, 2024
00f0f3a
Update rocketpy/rocket/rocket.py
MateusStano May 3, 2024
de2d8bd
MNT: remove .type attribute
MateusStano May 6, 2024
fec6725
DOC: fix units of transformation_euler_anges
MateusStano May 6, 2024
ce2a63d
DOC: add examples to transformation_euler_angles
MateusStano May 6, 2024
f2656c5
DOC: mention Euler parameters in docs
MateusStano May 6, 2024
41bf9e9
TST: test_euler_to_quaternions
MateusStano May 6, 2024
11873cd
TST: fix doc tests
MateusStano May 6, 2024
b795031
MNT: privatize components attributes and improve docs
MateusStano May 6, 2024
968f55a
TST: add index to var names
MateusStano May 6, 2024
d827b8e
MNT: return temp drift directly
MateusStano May 6, 2024
0b0f201
ENH: improve export methods
MateusStano May 6, 2024
bad3b07
DOC: typos
MateusStano May 6, 2024
0891c6c
MNT: improve flight init
MateusStano May 6, 2024
fa1b6a8
Fix code style issues with Black
lint-action May 6, 2024
b7439f4
Merge branch 'enh/sensors' of https://github.com/RocketPy-Team/Rocket…
MateusStano May 6, 2024
8c71432
TST: overall improvements on sensor testing
MateusStano May 6, 2024
5885992
ENH: change format of sensor_data dict
MateusStano May 6, 2024
02cad05
MNT: improve flight init
MateusStano May 6, 2024
69f17cd
TST: increase rel tolerances
MateusStano May 6, 2024
6894f58
Merge branch 'enh/sensors' of https://github.com/RocketPy-Team/Rocket…
MateusStano May 6, 2024
8d96e58
TST: fix export sensor data test
MateusStano May 6, 2024
ec4e25e
TST: fix doctests
MateusStano May 6, 2024
adb2dbb
TST: fix doctests
MateusStano May 6, 2024
e0812a2
ENH: change units in prints
MateusStano May 9, 2024
6ec4c0b
ENH: add .round to Matrix and remove broken doctests
MateusStano May 10, 2024
78e67f2
TST: test transfromation euler angles
MateusStano May 10, 2024
d49b5c4
TST: imrpove test_euler_to_quaternions
MateusStano May 10, 2024
57b41ab
DEV: update sensors_testing notebook
Gui-FernandesBR May 15, 2024
7a2e50a
MNT: minor fixes - typos and isort
Gui-FernandesBR May 15, 2024
a57308d
MNT: remove aft double underscore from __units
MateusStano May 16, 2024
11a8ab6
DOC: remove .type
MateusStano May 16, 2024
2984065
MNT: remove .units underscores
MateusStano May 16, 2024
82f79e7
MNT: remove repetition from sensors prints
MateusStano May 16, 2024
5016283
ENH: normalize_quaternions in tools.py
MateusStano May 16, 2024
6806912
BUG: remove Matrix circuklar import in tools
MateusStano May 16, 2024
b99b301
MNT: remove unused variable
MateusStano May 16, 2024
75f8d9e
ENH: explicit for loop variables
MateusStano May 16, 2024
c1864b2
Merge branch 'enh/sensors' of https://github.com/RocketPy-Team/Rocket…
MateusStano May 16, 2024
a20d31d
DOC: improve orientation docs
MateusStano May 16, 2024
ce1d179
ENH: x_position to x_offset
MateusStano May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
484 changes: 484 additions & 0 deletions docs/notebooks/sensors_testing.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions rocketpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@
Tail,
TrapezoidalFins,
)
from .sensors import Accelerometer, Gyroscope, Sensors
from .simulation import Flight
53 changes: 51 additions & 2 deletions rocketpy/control/controller.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from inspect import signature

from ..prints.controller_prints import _ControllerPrints


Expand Down Expand Up @@ -52,6 +54,10 @@
the controller function can interact with. The objects are
listed in the same order as they are provided in the
`interactive_objects`.
7. `sensors` (list): A list of sensors that are attached to the
rocket. The most recent measurements of the sensors are provided
with the ``sensor.measurement`` attribute. The sensors are
listed in the same order as they are added to the rocket

This function will be called during the simulation at the specified
sampling rate. The function should evaluate and change the interactive
Expand All @@ -78,7 +84,7 @@
None
"""
self.interactive_objects = interactive_objects
self.controller_function = controller_function
self.controller_function = self.__init_controller_function(controller_function)
self.sampling_rate = sampling_rate
self.name = name
self.prints = _ControllerPrints(self)
Expand All @@ -88,7 +94,44 @@
else:
self.observed_variables = []

def __call__(self, time, state_vector, state_history):
def __init_controller_function(self, controller_function):
"""Checks number of arguments of the controller function and initializes
it with the correct number of arguments. This is a workaround to allow
the controller function to receive sensors without breaking changes"""
sig = signature(controller_function)
if len(sig.parameters) == 6:

def new_controller_function(
time,
sampling_rate,
state_vector,
state_history,
observed_variables,
interactive_objects,
sensors,
):
return controller_function(
time,
sampling_rate,
state_vector,
state_history,
observed_variables,
interactive_objects,
)

elif len(sig.parameters) == 7:
new_controller_function = controller_function

Check warning on line 123 in rocketpy/control/controller.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/control/controller.py#L122-L123

Added lines #L122 - L123 were not covered by tests
else:
raise ValueError(

Check warning on line 125 in rocketpy/control/controller.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/control/controller.py#L125

Added line #L125 was not covered by tests
"The controller function must have 6 or 7 arguments. "
"The arguments must be in the following order: "
"(time, sampling_rate, state_vector, state_history, "
"observed_variables, interactive_objects, sensors)."
"Sensors argument is optional."
MateusStano marked this conversation as resolved.
Show resolved Hide resolved
)
return new_controller_function
Comment on lines +101 to +132
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, as far as I remember, the controller function is still private.

Can't we simply change the expected behavior of controller function to always receive 7 arguments?

Of course current applications would breake, but the class was still private...

I'm just not 100% sure about the air brakes...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would change it for the airbrakes too, so its technically a breaking change. I do think having the sensors argument be optional can be good since not all rockets will have sensors.

One thing I just thought of, maybe we should have all this arguments be passed as key word arguments in the user's controller_function. The user's controller functions would then have to receive **kwargs and just unpack it as needed:

def controller_function(**kwargs):
    time = kwargs["time"]    
    sensors = kwargs["sensors"]

    ...

This would allow us to never need to care about how many params we are passing and in what order.

What do you think of this? It would be a breaking change, but only for the air brakes usage

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your proposal is really good.

Why would it be a breaking change? Couldn't you individually handle the case where the key "sensors" is not present in the kwargs dict?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be a breaking change because any controller_function defined by a user would need to define its parameters differently


def __call__(self, time, state_vector, state_history, sensors):
"""Call the controller function. This is used by the simulation class.

Parameters
Expand All @@ -104,6 +147,11 @@
history is a list of every state vector of every step of the
simulation. The state history is a list of lists, where each
sublist is a state vector and is ordered from oldest to newest.
sensors : list
A list of sensors that are attached to the rocket. The most recent
measurements of the sensors are provided with the
``sensor.measurement`` attribute. The sensors are listed in the same
order as they are added to the rocket.

Returns
-------
Expand All @@ -116,6 +164,7 @@
state_history,
self.observed_variables,
self.interactive_objects,
sensors,
)
if observed_variables is not None:
self.observed_variables.append(observed_variables)
Expand Down
126 changes: 114 additions & 12 deletions rocketpy/mathutils/vector_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from functools import cached_property
from itertools import product

from rocketpy.tools import euler_to_quaternions, normalize_quaternions


class Vector:
"""Pure python basic R3 vector class designed for simple operations.
Expand Down Expand Up @@ -152,7 +154,10 @@
@cached_property
def unit_vector(self):
"""R3 vector with the same direction of self, but normalized."""
return self / abs(self)
try:
return self / abs(self)
except ZeroDivisionError:
return self

Check warning on line 160 in rocketpy/mathutils/vector_matrix.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/mathutils/vector_matrix.py#L159-L160

Added lines #L159 - L160 were not covered by tests

@cached_property
def cross_matrix(self):
Expand Down Expand Up @@ -238,6 +243,30 @@
]
)

def __and__(self, other):
"""Element wise multiplication between two R3 vectors. Also known as
Hadamard product.

Parameters
----------
other : Vector
R3 vector to be multiplied with self.

Returns
-------
Vector
R3 vector resulting from the element wise multiplication between
self and other.

Examples
--------
>>> v = Vector([1, 7, 3])
>>> u = Vector([2, 5, 6])
>>> (v & u)
Vector(2, 35, 18)
"""
return Vector([self.x * other[0], self.y * other[1], self.z * other[2]])

def __matmul__(self, other):
"""Dot product between two R3 vectors."""
return self.x * other.x + self.y * other.y + self.z * other.z
Expand Down Expand Up @@ -920,6 +949,47 @@
"""
return self.__matmul__(other)

def round(self, decimals=0):
"""Round all the values matrix to a given number of decimals.

phmbressan marked this conversation as resolved.
Show resolved Hide resolved
Parameters
----------
decimals : int, optional
Number of decimal places to round to. Defaults to 0.

Returns
-------
Matrix
The rounded matrix.

Examples
--------
>>> M = Matrix([[1.1234, 2.3456, 3.4567], [4.5678, 5.6789, 6.7890], [7.8901, 8.9012, 9.0123]])
>>> M.round(2)
Matrix([1.12, 2.35, 3.46],
[4.57, 5.68, 6.79],
[7.89, 8.9, 9.01])
"""
return Matrix(
[
[
round(self.xx, decimals),
round(self.xy, decimals),
round(self.xz, decimals),
],
[
round(self.yx, decimals),
round(self.yy, decimals),
round(self.yz, decimals),
],
[
round(self.zx, decimals),
round(self.zy, decimals),
round(self.zz, decimals),
],
]
)

def __str__(self):
return (
f"[{self.xx}, {self.xy}, {self.xz}]\n"
Expand Down Expand Up @@ -955,34 +1025,66 @@
The quaternion representing the rotation from frame A to frame B.
Example: (cos(phi/2), 0, 0, sin(phi/2)) represents a rotation of
phi around the z-axis.
Note: the quaternion must be normalized.

Returns
-------
Matrix
The transformation matrix from frame B to frame A.
"""
q_w, q_x, q_y, q_z = quaternion
# normalize quaternion
q_w, q_x, q_y, q_z = normalize_quaternions(quaternion)

# pre-compute common terms
q_x2 = q_x**2
q_y2 = q_y**2
q_z2 = q_z**2
q_wx = q_w * q_x
q_wy = q_w * q_y
q_wz = q_w * q_z
q_xy = q_x * q_y
q_xz = q_x * q_z
q_yz = q_y * q_z
return Matrix(
[
[
1 - 2 * (q_y**2 + q_z**2),
2 * (q_x * q_y - q_w * q_z),
2 * (q_x * q_z + q_w * q_y),
1 - 2 * (q_y2 + q_z2),
2 * (q_xy - q_wz),
2 * (q_xz + q_wy),
],
[
2 * (q_x * q_y + q_w * q_z),
1 - 2 * (q_x**2 + q_z**2),
2 * (q_y * q_z - q_w * q_x),
2 * (q_xy + q_wz),
1 - 2 * (q_x2 + q_z2),
2 * (q_yz - q_wx),
],
[
2 * (q_x * q_z - q_w * q_y),
2 * (q_y * q_z + q_w * q_x),
1 - 2 * (q_x**2 + q_y**2),
2 * (q_xz - q_wy),
2 * (q_yz + q_wx),
1 - 2 * (q_x2 + q_y2),
],
]
)

@staticmethod
def transformation_euler_angles(roll, pitch, yaw):
phmbressan marked this conversation as resolved.
Show resolved Hide resolved
"""Returns the transformation Matrix from frame B to frame A, where B
is rotated by the Euler angles roll, pitch and yaw with respect to A.

Parameters
----------
roll : float
The roll angle in degrees.
pitch : float
The pitch angle in degrees.
yaw : float
The yaw angle in degrees.

Returns
-------
Matrix
The transformation matrix from frame B to frame A.
"""
return Matrix.transformation(euler_to_quaternions(roll, pitch, yaw))


if __name__ == "__main__":
import doctest
Expand Down
61 changes: 57 additions & 4 deletions rocketpy/plots/rocket_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import matplotlib.pyplot as plt
import numpy as np

from rocketpy.mathutils.vector_matrix import Vector
from rocketpy.motors import EmptyMotor, HybridMotor, LiquidMotor, SolidMotor
from rocketpy.rocket.aero_surface import Fins, NoseCone, Tail

Expand Down Expand Up @@ -168,7 +169,7 @@

return None

def draw(self, vis_args=None):
def draw(self, vis_args=None, plane="xz"):
"""Draws the rocket in a matplotlib figure.

Parameters
Expand All @@ -188,6 +189,9 @@
}
A full list of color names can be found at:
https://matplotlib.org/stable/gallery/color/named_colors
plane : str, optional
Plane in which the rocket will be drawn. Default is 'xz'. Other
options is 'yz'. Used only for sensors representation.
Comment on lines +192 to +194
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would detail a bit more where these axes are defined (e.g. the x axis is the one defined as reference in the sensor definition). Perhaps even link the docs page.

A future enhancement would be the correct angular position of the rail buttons, even though we don't have that information right now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""
if vis_args is None:
vis_args = {
Expand All @@ -201,10 +205,8 @@
"line_width": 1.0,
}

# Create the figure and axis
_, ax = plt.subplots(figsize=(8, 6), facecolor="#EEEEEE")
fig, ax = plt.subplots(figsize=(8, 6), facecolor=vis_args["background"])
ax.set_aspect("equal")
ax.set_facecolor(vis_args["background"])
ax.grid(True, linestyle="--", linewidth=0.5)

csys = self.rocket._csys
Expand All @@ -216,6 +218,7 @@
self._draw_motor(last_radius, last_x, ax, vis_args)
self._draw_rail_buttons(ax, vis_args)
self._draw_center_of_mass_and_pressure(ax)
self._draw_sensor(ax, self.rocket.sensors, plane, vis_args)

plt.title("Rocket Representation")
plt.xlim()
Expand Down Expand Up @@ -552,6 +555,56 @@
cp, 0, label="Static Center of Pressure", color="red", s=10, zorder=10
)

def _draw_sensor(self, ax, sensors, plane, vis_args):
"""Draw the sensor as a small thick line at the position of the sensor,
with a vector pointing in the direction normal of the sensor. Get the
normal vector from the sensor orientation matrix."""
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
for i, sensor_pos in enumerate(sensors):
sensor = sensor_pos[0]
pos = sensor_pos[1]
if plane == "xz":

Check warning on line 566 in rocketpy/plots/rocket_plots.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/plots/rocket_plots.py#L564-L566

Added lines #L564 - L566 were not covered by tests
# z position of the sensor is the x position in the plot
x_pos = pos[2]
normal_x = sensor.normal_vector.z

Check warning on line 569 in rocketpy/plots/rocket_plots.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/plots/rocket_plots.py#L568-L569

Added lines #L568 - L569 were not covered by tests
# x position of the sensor is the y position in the plot
y_pos = pos[0]
normal_y = sensor.normal_vector.x
elif plane == "yz":

Check warning on line 573 in rocketpy/plots/rocket_plots.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/plots/rocket_plots.py#L571-L573

Added lines #L571 - L573 were not covered by tests
# z position of the sensor is the x position in the plot
x_pos = pos[2]
normal_x = sensor.normal_vector.z

Check warning on line 576 in rocketpy/plots/rocket_plots.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/plots/rocket_plots.py#L575-L576

Added lines #L575 - L576 were not covered by tests
# y position of the sensor is the y position in the plot
y_pos = pos[1]
normal_y = sensor.normal_vector.y

Check warning on line 579 in rocketpy/plots/rocket_plots.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/plots/rocket_plots.py#L578-L579

Added lines #L578 - L579 were not covered by tests
else:
raise ValueError("Plane must be 'xz' or 'yz'.")

Check warning on line 581 in rocketpy/plots/rocket_plots.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/plots/rocket_plots.py#L581

Added line #L581 was not covered by tests

# line length is 2/5 of the rocket radius
line_length = self.rocket.radius / 2.5

Check warning on line 584 in rocketpy/plots/rocket_plots.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/plots/rocket_plots.py#L584

Added line #L584 was not covered by tests

ax.plot(

Check warning on line 586 in rocketpy/plots/rocket_plots.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/plots/rocket_plots.py#L586

Added line #L586 was not covered by tests
[x_pos, x_pos],
[y_pos + line_length, y_pos - line_length],
linewidth=2,
color=colors[(i + 1) % len(colors)],
zorder=10,
label=sensor.name,
)
ax.quiver(

Check warning on line 594 in rocketpy/plots/rocket_plots.py

View check run for this annotation

Codecov / codecov/patch

rocketpy/plots/rocket_plots.py#L594

Added line #L594 was not covered by tests
x_pos,
y_pos,
normal_x,
normal_y,
color=colors[(i + 1) % len(colors)],
scale_units="xy",
angles="xy",
minshaft=2,
headwidth=2,
headlength=4,
zorder=10,
)

def all(self):
"""Prints out all graphs available about the Rocket. It simply calls
all the other plotter methods in this class.
Expand Down
Loading
Loading