Skip to content

Commit a259940

Browse files
committed
mnt: formatting
1 parent 486cf2e commit a259940

1 file changed

Lines changed: 67 additions & 52 deletions

File tree

rocketpy/simulation/events.py

Lines changed: 67 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,33 @@
11
import inspect
2-
from typing import get_type_hints
32
import warnings
3+
from typing import get_type_hints
4+
5+
46
class Event:
57
# TODO: should "sensors" arg of the trigger function be a dictionary instead
6-
# of a list? It would be more intuitive to access the sensors by name
8+
# of a list? It would be more intuitive to access the sensors by name
79
def __init__(self, trigger, action, name, event_context=None):
810
"""Initializes an Event object.
911
1012
Parameters
1113
----------
1214
trigger : function
13-
A function that must return a boolean value. The event will be
14-
triggered when this function returns True. The function should be
15+
A function that must return a boolean value. The event will be
16+
triggered when this function returns True. The function should be
1517
defined with the following signature: trigger(**kwargs), where
1618
kwargs is a dictionary containing the keys:
1719
1820
- `"time"` (float): The current simulation time in seconds.
1921
- `"state"` (list): The state vector of the simulation, structured
2022
as `[x, y, z, vx, vy, vz, e0, e1, e2, e3, wx, wy, wz]`.
21-
- `"state_dot"` (list): The time derivative of the state vector,
23+
- `"state_dot"` (list): The time derivative of the state vector,
2224
structured as `[vx, vy, vz, ax, ay, az, e0_dot, e1_dot, e2_dot, e3_dot, wx_dot, wy_dot, wz_dot]`.
2325
- `"sampling_rate"` (float or None): The sampling rate of the
2426
event, in seconds. If None, the event will be checked for
2527
triggering at every time step of the simulation. If a float
26-
value is provided, the event will only be checked for
28+
value is provided, the event will only be checked for
2729
triggering at that specific time interval.
28-
- `"sensors"` (list): A list of sensors that are attached to the
30+
- `"sensors"` (list): A list of sensors that are attached to the
2931
rocket. The most recent measurements of the sensors are provided
3032
with the ``sensor.measurement`` attribute. The sensors are
3133
listed in the same order as they are added to the rocket.
@@ -37,59 +39,59 @@ def __init__(self, trigger, action, name, event_context=None):
3739
- `"phase_index"` (int): The index of the current flight phase.
3840
- `"node_index"` (int): The index of the current node in the
3941
current flight phase.
40-
- Any additional custom key-value pairs provided via the
42+
- Any additional custom key-value pairs provided via the
4143
`event_context` parameter (see below).
42-
44+
4345
action : function
4446
A function that will be executed when the event is triggered. The
45-
function should be defined with the following signature:
47+
function should be defined with the following signature:
4648
action(**kwargs), where kwargs is a dictionary containing the same
4749
keys as the trigger function. The action function can also modify
4850
the state of the simulation by returning a dictionary with the keys:
4951
- `"state"` (list): A new state vector to replace the current state
5052
vector. The structure of the state vector is the same as the
5153
one provided in the trigger function.
52-
- `"disable_event"` (bool): If True, the event will not be
53-
checked for triggering again after being triggered, making
54+
- `"disable_event"` (bool): If True, the event will not be
55+
checked for triggering again after being triggered, making
5456
it a one-time event. Defaults to True.
5557
- `"new_events"` (list): A list of new Event objects to be added
5658
to the simulation when the event is triggered. This can be
5759
used to create events that spawn new events when they are
5860
triggered, such as a parachute deployment event that spawns
5961
a new event to check for the parachute deployment after a
6062
certain time delay.
61-
- `"remove_events"` (list): A list of Event objects to be
62-
removed from the simulation when the event is triggered. This
63-
can be used to create events that remove other events when
64-
they are triggered, such as a parachute deployment event that
63+
- `"remove_events"` (list): A list of Event objects to be
64+
removed from the simulation when the event is triggered. This
65+
can be used to create events that remove other events when
66+
they are triggered, such as a parachute deployment event that
6567
removes the apogee event when it is triggered.
66-
- Any other key-value pairs defined in `event_context` will
67-
also be included. These allow you to maintain custom state or
68-
counters across multiple trigger and action calls. Use cases
68+
- Any other key-value pairs defined in `event_context` will
69+
also be included. These allow you to maintain custom state or
70+
counters across multiple trigger and action calls. Use cases
6971
include: tracking the number of times an event has been triggered
70-
(e.g., `{"trigger_count": 0}`), recording the time of the last
71-
trigger (e.g., `{"last_trigger_time": None}`), or any other
72+
(e.g., `{"trigger_count": 0}`), recording the time of the last
73+
trigger (e.g., `{"last_trigger_time": None}`), or any other
7274
custom data your trigger/action functions need to share state.
73-
74-
Example: If you initialize the event with
75-
`event_context={"trigger_count": 0}`, your trigger and action
76-
functions will receive `trigger_count=0` in their kwargs dict.
77-
You can then update this value in the action function by
78-
including it in the returned dictionary (e.g.,
79-
`{"trigger_count": 1}`), and it will be passed to subsequent
75+
76+
Example: If you initialize the event with
77+
`event_context={"trigger_count": 0}`, your trigger and action
78+
functions will receive `trigger_count=0` in their kwargs dict.
79+
You can then update this value in the action function by
80+
including it in the returned dictionary (e.g.,
81+
`{"trigger_count": 1}`), and it will be passed to subsequent
8082
trigger/action calls.
8183
8284
name : str
8385
A name for the event, used for identification purposes.
8486
event_context : dict, optional
85-
A dictionary of custom key-value pairs that will be passed to the
86-
trigger and action functions. This allows you to initialize and
87-
maintain custom state that persists across multiple trigger/action
88-
calls. For example, `event_context={"trigger_count": 0,
89-
"last_trigger_time": None}` can be used to track event state.
90-
When the action function returns a dictionary with updated values
91-
(e.g., `{"trigger_count": 1}`), those values persist and are
92-
passed to subsequent calls. Defaults to an empty dictionary if not
87+
A dictionary of custom key-value pairs that will be passed to the
88+
trigger and action functions. This allows you to initialize and
89+
maintain custom state that persists across multiple trigger/action
90+
calls. For example, `event_context={"trigger_count": 0,
91+
"last_trigger_time": None}` can be used to track event state.
92+
When the action function returns a dictionary with updated values
93+
(e.g., `{"trigger_count": 1}`), those values persist and are
94+
passed to subsequent calls. Defaults to an empty dictionary if not
9395
provided.
9496
"""
9597
self.name = name
@@ -125,31 +127,40 @@ def __verify_trigger(self, trigger):
125127
# 3. Consider allowing signature to be flexible (accepts **kwargs)
126128
# to accommodate user-defined custom event_context keys
127129
# verify if the return type is bool when annotated
128-
return_annotation = get_type_hints(trigger).get('return', None)
130+
return_annotation = get_type_hints(trigger).get("return", None)
129131
if return_annotation is not None and return_annotation is not bool:
130-
raise ValueError(f"Trigger function {self.name} must return a boolean value.")
132+
raise ValueError(
133+
f"Trigger function {self.name} must return a boolean value."
134+
)
131135
# verify if the trigger function accepts **kwargs and therefore can
132136
# receive standard event arguments plus custom event_context keys
133137
s = inspect.signature(trigger)
134-
if not any(p.kind == inspect.Parameter.VAR_KEYWORD for p in s.parameters.values()):
138+
if not any(
139+
p.kind == inspect.Parameter.VAR_KEYWORD for p in s.parameters.values()
140+
):
135141
raise ValueError(
136142
f"Trigger function {self.name} must accept **kwargs to receive event context "
137143
f"and simulation state."
138144
)
139-
if any(p.kind == inspect.Parameter.POSITIONAL_ONLY for p in s.parameters.values()):
145+
if any(
146+
p.kind == inspect.Parameter.POSITIONAL_ONLY for p in s.parameters.values()
147+
):
140148
raise ValueError(
141149
f"Trigger function {self.name} must accept keyword arguments; "
142150
"positional-only parameters are not supported."
143151
)
152+
144153
# Helper function to generate dummy values based on type annotations
145154
# of parameters, allowing to test the function without real values
146155
def _placeholder_for_parameter(parameter):
147156
annotation = parameter.annotation
148157
if annotation is inspect.Parameter.empty:
149-
warnings.warn(f"Trigger function {self.name}: Test with parameters skipped due "
150-
f"to missing type annotation for parameter '{parameter.name}'. \n"
158+
warnings.warn(
159+
f"Trigger function {self.name}: Test with parameters skipped due "
160+
f"to missing type annotation for parameter '{parameter.name}'. \n"
151161
f"Is highly recommended that parameters have type annotations "
152-
f"(var: type). Parameter '{parameter.name}' has no annotation.")
162+
f"(var: type). Parameter '{parameter.name}' has no annotation."
163+
)
153164
skip_test = True
154165
return None, skip_test
155166
if annotation in (int, float):
@@ -164,6 +175,7 @@ def _placeholder_for_parameter(parameter):
164175
if origin in (list, tuple, set, dict):
165176
return origin(), False
166177
return None, False
178+
167179
# Build a dictionary with dummy values to test if function accepts **kwargs
168180
# Include an unexpected argument to validate the function doesn't complain
169181
test_kwargs = {"unexpected_kwarg": 123}
@@ -178,10 +190,14 @@ def _placeholder_for_parameter(parameter):
178190
annotation = parameter.annotation
179191
if annotation in (list, tuple, set, dict):
180192
skip_test = True
181-
elif hasattr(annotation, "__origin__") and getattr(annotation, "__origin__", None) in (list, tuple, set, dict):
193+
elif hasattr(annotation, "__origin__") and getattr(
194+
annotation, "__origin__", None
195+
) in (list, tuple, set, dict):
182196
skip_test = True
183197
else:
184-
test_kwargs[name], skip_test = _placeholder_for_parameter(parameter)
198+
test_kwargs[name], skip_test = _placeholder_for_parameter(
199+
parameter
200+
)
185201
# Execute the trigger function with test values to validate compatibility
186202
# If TypeError occurs, the function doesn't properly accept **kwargs
187203
if not skip_test:
@@ -274,21 +290,20 @@ def __call__(self, *args, **kwds):
274290
pass
275291

276292

277-
278293
# TODO: add a parameter to the Event class that specify whether the event should
279-
# be triggered only once, or if it can be triggered multiple times. Also, add a
294+
# be triggered only once, or if it can be triggered multiple times. Also, add a
280295
# way to stop the event from continuously triggering on command inside the action
281-
# function, such as a "disable" method that can be called inside the action
296+
# function, such as a "disable" method that can be called inside the action
282297
# function to prevent the event from being triggered again.
283298

284299
# TODO: add a parameter to the Event class that specify whether the event should
285300
# be a discrete event, meaning that it should only be checked for triggering at
286301
# specific time intervals (e.g. every 0.1 seconds) instead of at every time step
287-
# of the simulation. This would be useful for parachute events. This should be
302+
# of the simulation. This would be useful for parachute events. This should be
288303
# done by adding a "sampling_rate" parameter to the Event class, that is none by
289304
# default (meaning that the event is checked at every time step), but if it is
290-
# set to a float value, the event will only be checked for triggering at that
291-
# specific time interval. The flight class should be able to differentiate
305+
# set to a float value, the event will only be checked for triggering at that
306+
# specific time interval. The flight class should be able to differentiate
292307
# between the discrete and continuous events (we will handle this later)
293308

294309

@@ -302,4 +317,4 @@ def __call__(self, *args, **kwds):
302317
# - Respect the disable_event flag and sampling_rate to control when events
303318
# are checked for triggering
304319
# - Handle the sampling_rate logic: only check events at the specified intervals,
305-
# not at every simulation time step
320+
# not at every simulation time step

0 commit comments

Comments
 (0)