-
-
Notifications
You must be signed in to change notification settings - Fork 237
ENH: Sensors #583
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
Changes from all commits
e403872
6cd3598
3b81777
4c8aa13
09e0d75
38866f4
b386e38
6ff2dde
5ae01b9
1dd5781
943542a
28ebc46
378bb54
d8440f2
99f445f
4db26f0
4356af7
5c37f06
8d64998
28da8f3
cef72a0
5f8f1f4
9a6b052
7953cb0
5a37553
09ea252
9851392
0f81bc3
a740fc2
cf6c26d
2a14f1d
216523c
09288d4
aa6fcdf
bf6b083
5f86223
4dcc26b
32898c5
f7332d8
2131ee9
b2da0c3
123d033
7d0e6f3
00f0f3a
de2d8bd
fec6725
ce2a63d
f2656c5
41bf9e9
11873cd
b795031
968f55a
d827b8e
0b0f201
bad3b07
0891c6c
fa1b6a8
b7439f4
8c71432
5885992
02cad05
69f17cd
6894f58
8d96e58
ec4e25e
adb2dbb
e0812a2
6ec4c0b
78e67f2
d49b5c4
57b41ab
7a2e50a
a57308d
11a8ab6
2984065
82f79e7
5016283
6806912
b99b301
75f8d9e
c1864b2
a20d31d
ce1d179
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,4 +37,5 @@ | |
| Tail, | ||
| TrapezoidalFins, | ||
| ) | ||
| from .sensors import Accelerometer, Gyroscope, Sensors | ||
| from .simulation import Flight | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| from inspect import signature | ||
|
|
||
| from ..prints.controller_prints import _ControllerPrints | ||
|
|
||
|
|
||
|
|
@@ -52,6 +54,10 @@ def __init__( | |
| 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 | ||
|
|
@@ -78,7 +84,7 @@ def __init__( | |
| 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) | ||
|
|
@@ -88,7 +94,44 @@ def __init__( | |
| 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 | ||
| else: | ||
| raise ValueError( | ||
| "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." | ||
| ) | ||
| return new_controller_function | ||
|
Comment on lines
+101
to
+132
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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...
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
@@ -104,6 +147,11 @@ def __call__(self, time, state_vector, state_history): | |
| 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 | ||
| ------- | ||
|
|
@@ -116,6 +164,7 @@ def __call__(self, time, state_vector, state_history): | |
| state_history, | ||
| self.observed_variables, | ||
| self.interactive_objects, | ||
| sensors, | ||
| ) | ||
| if observed_variables is not None: | ||
| self.observed_variables.append(observed_variables) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
||
|
|
@@ -168,7 +169,7 @@ def thrust_to_weight(self): | |
|
|
||
| return None | ||
|
|
||
| def draw(self, vis_args=None): | ||
| def draw(self, vis_args=None, plane="xz"): | ||
| """Draws the rocket in a matplotlib figure. | ||
|
|
||
| Parameters | ||
|
|
@@ -188,6 +189,9 @@ def draw(self, vis_args=None): | |
| } | ||
| 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| """ | ||
| if vis_args is None: | ||
| vis_args = { | ||
|
|
@@ -201,10 +205,8 @@ def draw(self, vis_args=None): | |
| "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 | ||
|
|
@@ -216,6 +218,7 @@ def draw(self, vis_args=None): | |
| 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() | ||
|
|
@@ -552,6 +555,56 @@ def _draw_center_of_mass_and_pressure(self, ax): | |
| 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": | ||
| # z position of the sensor is the x position in the plot | ||
| x_pos = pos[2] | ||
| normal_x = sensor.normal_vector.z | ||
| # 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": | ||
| # z position of the sensor is the x position in the plot | ||
| x_pos = pos[2] | ||
| normal_x = sensor.normal_vector.z | ||
| # y position of the sensor is the y position in the plot | ||
| y_pos = pos[1] | ||
| normal_y = sensor.normal_vector.y | ||
| else: | ||
| raise ValueError("Plane must be 'xz' or 'yz'.") | ||
|
|
||
| # line length is 2/5 of the rocket radius | ||
| line_length = self.rocket.radius / 2.5 | ||
|
|
||
| ax.plot( | ||
| [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( | ||
| 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. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.