Skip to content

ENH: acceleration data to trigger parachutes#911

Open
ViniciusCMB wants to merge 22 commits intoRocketPy-Team:developfrom
ViniciusCMB:enh/acceleration-data-to-trigger-parachutes
Open

ENH: acceleration data to trigger parachutes#911
ViniciusCMB wants to merge 22 commits intoRocketPy-Team:developfrom
ViniciusCMB:enh/acceleration-data-to-trigger-parachutes

Conversation

@ViniciusCMB
Copy link
Copy Markdown

@ViniciusCMB ViniciusCMB commented Dec 7, 2025

Pull Request: Add Acceleration-Based Parachute Triggers with IMU Sensor Simulation

Pull request type

  • Code changes (features)
  • Code maintenance (refactoring, tests)
  • Documentation update

Checklist

  • Tests for the changes have been added / updated
  • Docs have been reviewed and updated
  • Formatting (black) has been run locally
  • Lint and tests have been run locally
  • CHANGELOG.md has been updated (if relevant)

Why this matters

This is a critical feature for advanced users simulating realistic avionics.
Real flight computers rely heavily on accelerometer signals (IMU) for event
detection (liftoff, burnout, coast/free-fall behavior), while pressure/GPS-only
logic is often noisier or slower in practice.

Until now, parachute triggers primarily relied on pressure/height/state-vector
inputs. This made it difficult to reproduce avionics-style event logic that
depends on acceleration profiles.

What this PR changes

This PR adds first-class support for acceleration-aware trigger callables by
passing state derivatives (u_dot) to user-defined trigger functions.

1) Trigger callable signatures support u_dot

In rocketpy/rocket/parachute.py, callable dispatch supports:

  • 3 args: (pressure, height, state_vector)
  • 4 args: (pressure, height, state_vector, u_dot) or (…, sensors)
  • 5 args: (pressure, height, state_vector, sensors, u_dot)

Dispatch is deterministic, and trigger wrappers expose metadata
(_expects_udot, _expects_sensors) used by Flight.

2) Flight computes u_dot(t, y) inside trigger evaluation when needed

In rocketpy/simulation/flight.py, trigger checks consistently route through
_evaluate_parachute_trigger(...), and u_dot is computed only when required
by trigger metadata.

This follows the architecture discussed in Issue #156: acceleration is not in
the solver state vector and must be obtained from derivative evaluation at the
event-check instant.

3) Docs now include acceleration-based trigger examples

docs/user/parachute_triggers.rst was updated with custom trigger examples for:

  • motor burnout detection
  • acceleration-guided apogee/coast logic
  • free-fall detection
  • liftoff detection

Thresholds are intentionally mission-specific and configurable by users.

4) Final API scope from review

Per review direction, this PR keeps the API focused on custom callables and
does not introduce acceleration-specific built-in trigger strings.

Acceptance criteria coverage (Issue #156)

  • Update Flight to calculate u_dot inside event checking flow
  • Pass acceleration data (full u_dot) to user-defined triggers
  • Add tutorial/docs example for burnout-style acceleration triggering

Files changed

  • rocketpy/rocket/parachute.py
    • callable signature detection/dispatch and trigger metadata
    • u_dot/sensor-aware trigger wrapper behavior
  • rocketpy/simulation/flight.py
    • consistent trigger evaluation path with optional u_dot computation
    • removal of duplicated fallback signature handling
  • docs/user/parachute_triggers.rst
    • acceleration-based custom trigger examples and usage guidance
  • tests/unit/test_parachute_triggers.py
    • coverage for signature behavior and u_dot delivery
  • tests/unit/test_parachute_trigger_acceleration.py
    • removed/merged to align with final callable-focused API

Breaking change

  • Yes
  • No

No breaking change in trigger usage: existing numeric, "apogee", and legacy
3-argument callables remain supported.

Validation performed

  • black run on modified Python files
  • pytest tests/unit/test_parachute_triggers.py -q passing
  • additional local lint/test checks executed during review updates

Related issue

  • RocketPy#156

…Team#XXX)

* ENH: add u_dot parameter computation inside parachute trigger evaluation.

* ENH: add acceleration_noise_function parameter to Flight class for realistic IMU simulation.

* ENH: implement automatic detection of trigger signature to compute derivatives only when needed.

* TST: add unit tests for parachute trigger with acceleration data and noise injection.

* TST: add test runner for trigger acceleration validation without full test suite dependencies.
…tion

This enhancement enables realistic avionics algorithms by providing access to
acceleration data (u_dot) within parachute trigger functions, simulating how
real flight computers use accelerometers (IMU) to detect flight phases.

* ENH: Pass acceleration data (u_dot) to parachute trigger callbacks
  - Flight class now computes and injects u_dot derivatives into triggers
  - Automatic detection of trigger signatures to optimize performance
  - Only calculates u_dot when trigger explicitly requires it

* ENH: Add built-in acceleration-based trigger functions
  - detect_apogee_acceleration: Detects apogee via vz ≈ 0 and az < 0
  - detect_motor_burnout: Detects motor shutdown by acceleration drop
  - detect_freefall: Detects free-fall condition (low total acceleration)
  - detect_liftoff: Detects liftoff by high total acceleration
  - altitude_trigger_factory: Factory for altitude-based triggers

* ENH: Implement optional accelerometer noise injection
  - New parameter acceleration_noise_function in Flight.__init__
  - Simulates MEMS accelerometer noise (typical 0.1-0.3 m/s²)
  - Applied transparently to all acceleration-based triggers

* TST: Add comprehensive unit tests for trigger evaluation
  - Validates u_dot computation and noise injection
  - Tests backward compatibility with legacy 3-parameter triggers
  - Ensures performance optimization skips u_dot for non-accelerating triggers

Notes
-----
- Triggers can now use signatures: (p, h, y) or (p, h, y, u_dot/acc/acceleration)
- Built-in string triggers: apogee, apogee_acc, burnout, freefall, liftoff
- Performance: u_dot computation doubles physics evaluations at trigger points
- Fully backward compatible with existing altitude and custom triggers
@ViniciusCMB ViniciusCMB requested a review from a team as a code owner December 7, 2025 19:32
@Gui-FernandesBR Gui-FernandesBR changed the base branch from master to develop December 8, 2025 03:08
Allow parachute triggers to accept (p, h, y, sensors, u_dot); Flight passes sensors+u_dot, computing u_dot on demand with noise; numeric/apogee legacy triggers carry metadata.\n\nTests: pytest tests/unit/test_parachute_trigger_acceleration.py -v\nLint: black rocketpy tests && ruff check rocketpy tests
@ViniciusCMB
Copy link
Copy Markdown
Author

Fixed the linting/test errors reported by the CI workflow in the latest commit.

@Gui-FernandesBR
Copy link
Copy Markdown
Member

@ViniciusCMB how do you compare your implementation against #854, is there any overlap we should be worried about?

@codecov
Copy link
Copy Markdown

codecov bot commented Dec 8, 2025

Codecov Report

❌ Patch coverage is 75.47170% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.03%. Comparing base (9cf3dd4) to head (6a9795d).
⚠️ Report is 49 commits behind head on develop.

Files with missing lines Patch % Lines
rocketpy/rocket/parachute.py 74.41% 11 Missing ⚠️
rocketpy/simulation/flight.py 80.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #911      +/-   ##
===========================================
+ Coverage    80.27%   81.03%   +0.76%     
===========================================
  Files          104      107       +3     
  Lines        12769    13942    +1173     
===========================================
+ Hits         10250    11298    +1048     
- Misses        2519     2644     +125     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ViniciusCMB
Copy link
Copy Markdown
Author

@ViniciusCMB how do you compare your implementation against #854, is there any overlap we should be worried about?

I have reviewed the definition of Controllers and analyzed the __simulate loop in flight.py.

My Conclusion: While Controllers allow for dynamic logic, the RocketPy simulation engine iterates over them in separate loops:

Impact Assessment:

  • Logical Conflict: None. Standard Parachute objects are not instances of Controllers in this context, so the changes do not overlap in logic.
  • Textual Conflict: Likely. Since these loops are adjacent in flight.py, git will probably flag a merge conflict, but it should be trivial to resolve.

However, since both modify the critical __simulate method and #854 involves significant changes to the Controller architecture (10 files), I cannot guarantee zero friction without running an integration test merging both branches.

I am available to rebase and test my branch on top of #854 once it is merged to ensure stability.

@Gui-FernandesBR
Copy link
Copy Markdown
Member

@ViniciusCMB I have merged the #854 so you can rebase your work on top of develop branch again.

Pls fix conflicts and linters so we can proceed with the review.

@Gui-FernandesBR Gui-FernandesBR linked an issue Dec 9, 2025 that may be closed by this pull request
3 tasks
ViniciusCMB and others added 8 commits December 9, 2025 10:40
…iggers

- TestTriggerSignatures: Validates 3, 4, and 5-parameter trigger signatures
- TestBuiltInTriggers: Tests all built-in triggers (apogee_acc, burnout, freefall, liftoff)
- TestParachuteInitialization: Verifies parachute creation with different trigger types
- TestEdgeCases: Covers NaN/Inf handling, low altitude, and error conditions

Tests use AAA pattern (Arrange, Act, Assert) and follow NumPy docstring style.
All edge cases for realistic avionics simulation are covered.

Fixes codecov patch coverage from 42.85% to >85%.
… triggers

- Created docs/user/parachute_triggers.rst with complete guide
- Enhanced Parachute class docstring with trigger signature details
- Added parachute_triggers.rst to docs/user/index.rst

Documentation covers:
- Built-in triggers (burnout, apogee_acc, freefall, liftoff)
- Custom trigger examples (3, 4, and 5-parameter signatures)
- IMU noise simulation
- Performance considerations
- Best practices and complete dual-deploy example
- Reorder imports: standard library before third-party
- Prefix unused arguments with underscore
- Add broad-exception-caught disable for test runner
Direct calls to parachute.triggerfunc() were missing the u_dot parameter,
causing TypeError in unit tests. Added u_dot=None to non-overshoot and
overshoot trigger evaluation paths where u_dot is not computed.

Fixes test failures in:
- tests/unit/simulation/test_flight.py
- tests/unit/test_utilities.py
- tests/unit/test_rail_buttons_bending_moments.py
- tests/unit/test_flight_data_exporter.py
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do you really need try/except blocks here?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds acceleration-based parachute triggers with IMU sensor simulation to RocketPy, enabling realistic avionics algorithms that mimic real-world flight computers. The implementation allows users to trigger parachute deployment based on accelerometer data (motor burnout, apogee, freefall, liftoff) while maintaining full backward compatibility with existing altitude and velocity-based triggers.

Key Changes:

  • Four built-in acceleration-based trigger functions with edge case handling (NaN/Inf protection)
  • Flexible trigger function signatures supporting 3, 4, or 5 parameters for legacy and new use cases
  • Optional IMU noise simulation via acceleration_noise_function parameter in Flight class
  • Performance optimization through lazy evaluation (u_dot computed only when needed)

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 21 comments.

Show a summary per file
File Description
tests/unit/test_parachute_triggers.py New test file with basic trigger tests, but uses non-standard test naming and custom runner
tests/unit/test_parachute_trigger_acceleration.py Comprehensive test suite for acceleration triggers with proper pytest structure and AAA pattern
rocketpy/simulation/flight.py Adds _evaluate_parachute_trigger helper method and acceleration_noise_function parameter; includes new import for inspect module
rocketpy/rocket/parachute.py Implements four built-in trigger functions and flexible wrapper for multi-signature support
docs/user/parachute_triggers.rst Comprehensive documentation with examples, best practices, and performance considerations
docs/user/index.rst Adds parachute triggers documentation to table of contents

@Gui-FernandesBR
Copy link
Copy Markdown
Member

@MateusStano I willtake a look at your comments and try to get back to you in case @ViniciusCMB doesn't do. However, Vinicius, if you are still available to contribute, please take a look at the comments,try to solve all of them and push it back here.

@ViniciusCMB
Copy link
Copy Markdown
Author

@MateusStano I willtake a look at your comments and try to get back to you in case @ViniciusCMB doesn't do. However, Vinicius, if you are still available to contribute, please take a look at the comments,try to solve all of them and push it back here.

Almost done!

@ViniciusCMB ViniciusCMB changed the title Enh/acceleration data to trigger parachutes ENH: acceleration data to trigger parachutes Mar 21, 2026
@ViniciusCMB
Copy link
Copy Markdown
Author

@Gui-FernandesBR, @MateusStano, I was really focused on the original issue and ended up implementing the "liftoff", "burnout", and other triggers as built-ins, even though it doesn't make much sense for the parachute (I believe it might be useful for other deployables, though). As for the noise sensor, I just read about it in the issue and implemented it; I wasn't aware it had already been done. I removed the excessive checks because, at the time, I hadn't fully read the documentation and was worried about avoidable errors showing up.

@Gui-FernandesBR
Copy link
Copy Markdown
Member

@ViniciusCMB you have mark every comment as "resolved" before we start to review again. This makes the review process much easier.


return trigger


Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change

Please remove this function. It is not used currently

Comment on lines +72 to 74
- A string for built-in triggers:
- ``"apogee"``: Apogee detection (velocity-based)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I prefer the previous version of this description. Can you remove these changes?

Comment on lines +84 to 89
.. note::

The function will be called according to the sampling rate specified.
For performance, ``u_dot`` is only computed if the trigger signature
indicates it is needed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No need to explain backend code in this part of the docstring. I suggent removing it

Suggested change
.. note::
The function will be called according to the sampling rate specified.
For performance, ``u_dot`` is only computed if the trigger signature
indicates it is needed.
.. note::
The function will be called according to the sampling rate specified.

Comment on lines +590 to 603
simulation_mode : str, optional
Simulation mode to use. Can be "6 DOF" for 6 degrees of freedom or
"3 DOF" for 3 degrees of freedom. Default is "6 DOF".
weathercock_coeff : float, optional
Proportionality coefficient (rate coefficient) for the alignment rate of the rocket's body axis
with the relative wind direction in 3-DOF simulations, in rad/s. The actual angular velocity
applied to align the rocket is calculated as ``weathercock_coeff * sin(angle)``, where ``angle``
is the angle between the rocket's axis and the wind direction. A higher value means faster alignment
(quasi-static weathercocking). This parameter is only used when simulation_mode is '3 DOF'.
Default is 0.0 to mimic a pure 3-DOF simulation without any weathercocking (fixed attitude).
Set to a positive value to enable quasi-static weathercocking behaviour.


Returns
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please remove the weathercock_coeff, this seems to be a merge error

Suggested change
simulation_mode : str, optional
Simulation mode to use. Can be "6 DOF" for 6 degrees of freedom or
"3 DOF" for 3 degrees of freedom. Default is "6 DOF".
weathercock_coeff : float, optional
Proportionality coefficient (rate coefficient) for the alignment rate of the rocket's body axis
with the relative wind direction in 3-DOF simulations, in rad/s. The actual angular velocity
applied to align the rocket is calculated as ``weathercock_coeff * sin(angle)``, where ``angle``
is the angle between the rocket's axis and the wind direction. A higher value means faster alignment
(quasi-static weathercocking). This parameter is only used when simulation_mode is '3 DOF'.
Default is 0.0 to mimic a pure 3-DOF simulation without any weathercocking (fixed attitude).
Set to a positive value to enable quasi-static weathercocking behaviour.
Returns
simulation_mode : str, optional
Simulation mode to use. Can be "6 DOF" for 6 degrees of freedom or
"3 DOF" for 3 degrees of freedom. Default is "6 DOF".
Returns

Comment on lines +374 to +376
# Special case: "apogee" (legacy support)
if isinstance(trigger, str) and trigger.lower() == "apogee":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is not legacy support, it is a valid feature

Suggested change
# Special case: "apogee" (legacy support)
if isinstance(trigger, str) and trigger.lower() == "apogee":
# Special case: "apogee"
if isinstance(trigger, str) and trigger.lower() == "apogee":

Comment on lines +92 to +100
Apogee by Acceleration (Custom Example)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

def apogee_acc_trigger(_pressure, _height, state_vector, u_dot):
vz = state_vector[5]
az = u_dot[5]
return abs(vz) < 1.0 and az < -0.1
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add some description of what is the logic of the trigger here.

Comment on lines +102 to +111
Free-fall (Custom Example)
~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

def freefall_trigger(_pressure, height, state_vector, u_dot):
ax, ay, az = u_dot[3], u_dot[4], u_dot[5]
total_acc = (ax**2 + ay**2 + az**2) ** 0.5
vz = state_vector[5]
return height > 5.0 and vz < -0.2 and total_acc < 11.5
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add some description of what is the logic of the trigger here.

Comment on lines +113 to +165
Liftoff by Acceleration (Custom Example)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

def liftoff_trigger(_pressure, _height, _state_vector, u_dot):
ax, ay, az = u_dot[3], u_dot[4], u_dot[5]
total_acc = (ax**2 + ay**2 + az**2) ** 0.5
return total_acc > 15.0

.. code-block:: python

def custom_trigger(pressure, height, state_vector, u_dot):
"""
Custom trigger using acceleration data.

Parameters
----------
pressure : float
Atmospheric pressure in Pa (with optional noise)
height : float
Height above ground level in meters (with optional noise)
state_vector : array
[x, y, z, vx, vy, vz, e0, e1, e2, e3, wx, wy, wz]
u_dot : array
Derivative: [vx, vy, vz, ax, ay, az, e0_dot, ...]
Accelerations are at indices [3:6]

Returns
-------
bool
True to trigger parachute deployment
"""
# Extract acceleration components (m/s²)
ax = u_dot[3]
ay = u_dot[4]
az = u_dot[5]

# Calculate total acceleration magnitude
total_acc = (ax**2 + ay**2 + az**2)**0.5

# Custom logic: deploy if total acceleration < 5 m/s² while descending
vz = state_vector[5]
return total_acc < 5.0 and vz < -10.0

# Use custom trigger
parachute = Parachute(
name="Custom",
cd_s=2.0,
trigger=custom_trigger,
sampling_rate=100,
lag=1.0
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There are two trigger functions defined here... One of them seems to be unused

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also, I don´t really understand the what this trigger is doing that is different from the previous ones?

Comment on lines +167 to +208
Sensor and Acceleration Triggers
---------------------------------

Triggers can also accept sensor data alongside acceleration:

.. code-block:: python

def advanced_trigger(pressure, height, state_vector, sensors, u_dot):
"""
Advanced trigger using both sensors and acceleration.

Parameters
----------
sensors : list
List of sensor objects attached to the rocket
u_dot : array
State derivative including accelerations

Returns
-------
bool
"""
# Access sensor measurements
if len(sensors) > 0:
imu_reading = sensors[0].measurement

# Define threshold for IMU reading (example value)
threshold = 100.0 # Adjust based on sensor units and trigger criteria

# Use acceleration data
az = u_dot[5]

# Combine sensor and acceleration logic
return az < -5.0 and imu_reading > threshold

parachute = Parachute(
name="Advanced",
cd_s=1.5,
trigger=advanced_trigger,
sampling_rate=100
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please make an example more in lines with what would be like using a rocketpy Accelerometer object. We don´t have an IMU sensor object
Also the .measurement result will be a list/vector, so please make it coherent. Users will definetly copy and paste these examples into their own code, so it should be as closely to working when pasting as possible

Comment on lines +242 to +301
Example: Complete Dual-Deploy System
-------------------------------------

.. code-block:: python

from rocketpy import Rocket, Parachute, Flight, Environment
import numpy as np

# Environment and rocket setup
env = Environment(latitude=32.99, longitude=-106.97, elevation=1400)
env.set_atmospheric_model(type="standard_atmosphere")

my_rocket = Rocket(...) # Define your rocket

# Drogue parachute: Deploy using a custom burnout trigger
def drogue_burnout_trigger(_pressure, height, state_vector, u_dot):
if u_dot is None or len(u_dot) < 6:
return False
az = u_dot[5]
vz = state_vector[5] if len(state_vector) > 5 else 0
return height > 10 and vz > 1 and az < -8.0

drogue = Parachute(
name="Drogue",
cd_s=1.0,
trigger=drogue_burnout_trigger,
sampling_rate=100,
lag=1.5,
noise=(0, 8.3, 0.5) # Pressure sensor noise
)
my_rocket.add_parachute(drogue)

# Main parachute: Deploy at 800m using custom trigger
def main_deploy_trigger(pressure, height, state_vector, u_dot):
"""Deploy main at 800m while descending with positive vertical acceleration."""
vz = state_vector[5]
az = u_dot[5]
return height < 800 and vz < -5 and az > -15

main = Parachute(
name="Main",
cd_s=10.0,
trigger=main_deploy_trigger,
sampling_rate=100,
lag=0.5,
noise=(0, 8.3, 0.5)
)
my_rocket.add_parachute(main)

# Flight simulation
flight = Flight(
rocket=my_rocket,
environment=env,
rail_length=5.2,
inclination=85,
heading=0,
)

flight.all_info()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you change this section to be about simulation Drogue parachutes? I think it would make more sense, and make this docs page more complete

We simulate drogue chutes by simply considering the disrefeed chute the equivalent to main here, and the refeed chute the drogue, since in rocketpy only one parachute is considered at a time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENH: Acceleration data to trigger parachutes

4 participants