|
| 1 | +Acceleration-Based Parachute Triggers |
| 2 | +====================================== |
| 3 | + |
| 4 | +RocketPy supports advanced parachute deployment logic using acceleration data |
| 5 | +from simulated IMU (Inertial Measurement Unit) sensors. This enables realistic |
| 6 | +avionics algorithms that mimic real-world flight computers. |
| 7 | + |
| 8 | +Overview |
| 9 | +-------- |
| 10 | + |
| 11 | +Traditional parachute triggers rely on altitude and velocity. Acceleration-based |
| 12 | +triggers provide additional capabilities: |
| 13 | + |
| 14 | +- **Motor burnout detection**: Detect thrust termination via sudden deceleration |
| 15 | +- **Apogee detection**: Use acceleration and velocity together for precise apogee |
| 16 | +- **Freefall detection**: Identify ballistic coasting phases |
| 17 | +- **Liftoff detection**: Confirm motor ignition via high acceleration |
| 18 | + |
| 19 | +These triggers can optionally include sensor noise to simulate realistic IMU |
| 20 | +behavior, making simulations more representative of actual flight conditions. |
| 21 | + |
| 22 | +Built-in Triggers |
| 23 | +----------------- |
| 24 | + |
| 25 | +RocketPy provides four built-in acceleration-based triggers: |
| 26 | + |
| 27 | +Motor Burnout Detection |
| 28 | +~~~~~~~~~~~~~~~~~~~~~~~~ |
| 29 | + |
| 30 | +Detects when the motor stops producing thrust by monitoring sudden drops in |
| 31 | +acceleration magnitude. |
| 32 | + |
| 33 | +.. code-block:: python |
| 34 | +
|
| 35 | + from rocketpy import Parachute |
| 36 | +
|
| 37 | + drogue = Parachute( |
| 38 | + name="Drogue", |
| 39 | + cd_s=1.0, |
| 40 | + trigger="burnout", # Built-in trigger |
| 41 | + sampling_rate=100, |
| 42 | + lag=1.5 |
| 43 | + ) |
| 44 | +
|
| 45 | +**Detection criteria:** |
| 46 | +- Vertical acceleration < -8.0 m/s² (end of thrust phase), OR |
| 47 | +- Total acceleration magnitude < 2.0 m/s² (coasting detected) |
| 48 | +- Rocket must be above 5m altitude and ascending (prevents false triggers at launch) |
| 49 | + |
| 50 | +Apogee Detection |
| 51 | +~~~~~~~~~~~~~~~~ |
| 52 | + |
| 53 | +Detects apogee using both near-zero vertical velocity and negative vertical |
| 54 | +acceleration. |
| 55 | + |
| 56 | +.. code-block:: python |
| 57 | +
|
| 58 | + main = Parachute( |
| 59 | + name="Main", |
| 60 | + cd_s=10.0, |
| 61 | + trigger="apogee_acc", # Acceleration-based apogee |
| 62 | + sampling_rate=100, |
| 63 | + lag=0.5 |
| 64 | + ) |
| 65 | +
|
| 66 | +**Detection criteria:** |
| 67 | +- Absolute vertical velocity < 1.0 m/s |
| 68 | +- Vertical acceleration < -0.1 m/s² (downward) |
| 69 | + |
| 70 | +Freefall Detection |
| 71 | +~~~~~~~~~~~~~~~~~~ |
| 72 | + |
| 73 | +Detects free-fall by monitoring very low total acceleration (near gravitational |
| 74 | +acceleration only). |
| 75 | + |
| 76 | +.. code-block:: python |
| 77 | +
|
| 78 | + drogue = Parachute( |
| 79 | + name="Drogue", |
| 80 | + cd_s=1.0, |
| 81 | + trigger="freefall", |
| 82 | + sampling_rate=100, |
| 83 | + lag=1.0 |
| 84 | + ) |
| 85 | +
|
| 86 | +**Detection criteria:** |
| 87 | +- Total acceleration magnitude < 11.5 m/s² |
| 88 | +- Rocket descending (vz < -0.2 m/s) |
| 89 | +- Altitude > 5m (prevents ground-level false triggers) |
| 90 | + |
| 91 | +Liftoff Detection |
| 92 | +~~~~~~~~~~~~~~~~~ |
| 93 | + |
| 94 | +Detects motor ignition via high total acceleration. |
| 95 | + |
| 96 | +.. code-block:: python |
| 97 | +
|
| 98 | + test_parachute = Parachute( |
| 99 | + name="Test", |
| 100 | + cd_s=0.5, |
| 101 | + trigger="liftoff", |
| 102 | + sampling_rate=100, |
| 103 | + lag=0.0 |
| 104 | + ) |
| 105 | +
|
| 106 | +**Detection criteria:** |
| 107 | +- Total acceleration magnitude > 15.0 m/s² |
| 108 | + |
| 109 | +Custom Triggers |
| 110 | +--------------- |
| 111 | + |
| 112 | +You can create custom triggers that use acceleration data: |
| 113 | + |
| 114 | +.. code-block:: python |
| 115 | +
|
| 116 | + def custom_trigger(pressure, height, state_vector, u_dot): |
| 117 | + """ |
| 118 | + Custom trigger using acceleration data. |
| 119 | +
|
| 120 | + Parameters |
| 121 | + ---------- |
| 122 | + pressure : float |
| 123 | + Atmospheric pressure in Pa (with optional noise) |
| 124 | + height : float |
| 125 | + Height above ground level in meters (with optional noise) |
| 126 | + state_vector : array |
| 127 | + [x, y, z, vx, vy, vz, e0, e1, e2, e3, wx, wy, wz] |
| 128 | + u_dot : array |
| 129 | + Derivative: [vx, vy, vz, ax, ay, az, e0_dot, ...] |
| 130 | + Accelerations are at indices [3:6] |
| 131 | +
|
| 132 | + Returns |
| 133 | + ------- |
| 134 | + bool |
| 135 | + True to trigger parachute deployment |
| 136 | + """ |
| 137 | + # Extract acceleration components (m/s²) |
| 138 | + ax = u_dot[3] |
| 139 | + ay = u_dot[4] |
| 140 | + az = u_dot[5] |
| 141 | +
|
| 142 | + # Calculate total acceleration magnitude |
| 143 | + total_acc = (ax**2 + ay**2 + az**2)**0.5 |
| 144 | +
|
| 145 | + # Custom logic: deploy if total acceleration < 5 m/s² while descending |
| 146 | + vz = state_vector[5] |
| 147 | + return total_acc < 5.0 and vz < -10.0 |
| 148 | +
|
| 149 | + # Use custom trigger |
| 150 | + parachute = Parachute( |
| 151 | + name="Custom", |
| 152 | + cd_s=2.0, |
| 153 | + trigger=custom_trigger, |
| 154 | + sampling_rate=100, |
| 155 | + lag=1.0 |
| 156 | + ) |
| 157 | +
|
| 158 | +Sensor and Acceleration Triggers |
| 159 | +--------------------------------- |
| 160 | + |
| 161 | +Triggers can also accept sensor data alongside acceleration: |
| 162 | + |
| 163 | +.. code-block:: python |
| 164 | +
|
| 165 | + def advanced_trigger(pressure, height, state_vector, sensors, u_dot): |
| 166 | + """ |
| 167 | + Advanced trigger using both sensors and acceleration. |
| 168 | +
|
| 169 | + Parameters |
| 170 | + ---------- |
| 171 | + sensors : list |
| 172 | + List of sensor objects attached to the rocket |
| 173 | + u_dot : array |
| 174 | + State derivative including accelerations |
| 175 | +
|
| 176 | + Returns |
| 177 | + ------- |
| 178 | + bool |
| 179 | + """ |
| 180 | + # Access sensor measurements |
| 181 | + if len(sensors) > 0: |
| 182 | + imu_reading = sensors[0].measurement |
| 183 | +
|
| 184 | + # Use acceleration data |
| 185 | + az = u_dot[5] |
| 186 | +
|
| 187 | + # Combine sensor and acceleration logic |
| 188 | + return az < -5.0 and imu_reading > threshold |
| 189 | +
|
| 190 | + parachute = Parachute( |
| 191 | + name="Advanced", |
| 192 | + cd_s=1.5, |
| 193 | + trigger=advanced_trigger, |
| 194 | + sampling_rate=100 |
| 195 | + ) |
| 196 | +
|
| 197 | +Adding Acceleration Noise |
| 198 | +-------------------------- |
| 199 | + |
| 200 | +To simulate realistic IMU behavior, you can add noise to acceleration data: |
| 201 | + |
| 202 | +.. code-block:: python |
| 203 | +
|
| 204 | + from rocketpy import Flight |
| 205 | +
|
| 206 | + flight = Flight( |
| 207 | + rocket=my_rocket, |
| 208 | + environment=env, |
| 209 | + rail_length=5.2, |
| 210 | + inclination=85, |
| 211 | + heading=0, |
| 212 | + acceleration_noise_function=lambda: np.random.normal(0, 0.5, 3) |
| 213 | + ) |
| 214 | +
|
| 215 | +The ``acceleration_noise_function`` returns a 3-element array ``[noise_x, noise_y, noise_z]`` |
| 216 | +that is added to the acceleration components before passing to the trigger function. |
| 217 | + |
| 218 | +**Example with time-correlated noise:** |
| 219 | + |
| 220 | +.. code-block:: python |
| 221 | +
|
| 222 | + class NoiseGenerator: |
| 223 | + def __init__(self, stddev=0.5, correlation=0.9): |
| 224 | + self.stddev = stddev |
| 225 | + self.correlation = correlation |
| 226 | + self.last_noise = np.zeros(3) |
| 227 | +
|
| 228 | + def __call__(self): |
| 229 | + # Time-correlated noise (AR(1) process) |
| 230 | + white_noise = np.random.normal(0, self.stddev, 3) |
| 231 | + self.last_noise = (self.correlation * self.last_noise + |
| 232 | + np.sqrt(1 - self.correlation**2) * white_noise) |
| 233 | + return self.last_noise |
| 234 | +
|
| 235 | + flight = Flight( |
| 236 | + rocket=my_rocket, |
| 237 | + environment=env, |
| 238 | + rail_length=5.2, |
| 239 | + inclination=85, |
| 240 | + heading=0, |
| 241 | + acceleration_noise_function=NoiseGenerator(stddev=0.3, correlation=0.95) |
| 242 | + ) |
| 243 | +
|
| 244 | +Performance Considerations |
| 245 | +-------------------------- |
| 246 | + |
| 247 | +Computing acceleration (``u_dot``) requires evaluating the equations of motion, |
| 248 | +which adds computational cost. RocketPy optimizes this by: |
| 249 | + |
| 250 | +1. **Lazy evaluation**: ``u_dot`` is only computed if the trigger actually needs it |
| 251 | +2. **Metadata detection**: The wrapper inspects trigger signatures to determine requirements |
| 252 | +3. **Caching**: Derivative evaluations are reused when possible |
| 253 | + |
| 254 | +**Trigger signature detection:** |
| 255 | + |
| 256 | +- 3 parameters ``(p, h, y)``: Legacy trigger, no ``u_dot`` computed |
| 257 | +- 4 parameters with ``u_dot``: Only acceleration computed |
| 258 | +- 4 parameters with ``sensors``: Only sensors passed |
| 259 | +- 5 parameters: Both sensors and acceleration provided |
| 260 | + |
| 261 | +Best Practices |
| 262 | +-------------- |
| 263 | + |
| 264 | +1. **Choose appropriate sampling rates**: 50-200 Hz is typical for flight computers |
| 265 | +2. **Add realistic noise**: Real IMUs have noise; simulate it for validation |
| 266 | +3. **Test edge cases**: Verify triggers work at low altitudes, high speeds, etc. |
| 267 | +4. **Use built-in triggers**: They handle edge cases (NaN, Inf) automatically |
| 268 | +5. **Document custom triggers**: Include detection criteria in docstrings |
| 269 | + |
| 270 | +Example: Complete Dual-Deploy System |
| 271 | +------------------------------------- |
| 272 | + |
| 273 | +.. code-block:: python |
| 274 | +
|
| 275 | + from rocketpy import Rocket, Parachute, Flight, Environment |
| 276 | + import numpy as np |
| 277 | +
|
| 278 | + # Environment and rocket setup |
| 279 | + env = Environment(latitude=32.99, longitude=-106.97, elevation=1400) |
| 280 | + env.set_atmospheric_model(type="standard_atmosphere") |
| 281 | +
|
| 282 | + my_rocket = Rocket(...) # Define your rocket |
| 283 | +
|
| 284 | + # Drogue parachute: Deploy at motor burnout |
| 285 | + drogue = Parachute( |
| 286 | + name="Drogue", |
| 287 | + cd_s=1.0, |
| 288 | + trigger="burnout", |
| 289 | + sampling_rate=100, |
| 290 | + lag=1.5, |
| 291 | + noise=(0, 8.3, 0.5) # Pressure sensor noise |
| 292 | + ) |
| 293 | + my_rocket.add_parachute(drogue) |
| 294 | +
|
| 295 | + # Main parachute: Deploy at 800m using custom trigger |
| 296 | + def main_deploy_trigger(pressure, height, state_vector, u_dot): |
| 297 | + """Deploy main at 800m while descending with positive vertical acceleration.""" |
| 298 | + vz = state_vector[5] |
| 299 | + az = u_dot[5] |
| 300 | + return height < 800 and vz < -5 and az > -15 |
| 301 | +
|
| 302 | + main = Parachute( |
| 303 | + name="Main", |
| 304 | + cd_s=10.0, |
| 305 | + trigger=main_deploy_trigger, |
| 306 | + sampling_rate=100, |
| 307 | + lag=0.5, |
| 308 | + noise=(0, 8.3, 0.5) |
| 309 | + ) |
| 310 | + my_rocket.add_parachute(main) |
| 311 | +
|
| 312 | + # Flight with IMU noise simulation |
| 313 | + flight = Flight( |
| 314 | + rocket=my_rocket, |
| 315 | + environment=env, |
| 316 | + rail_length=5.2, |
| 317 | + inclination=85, |
| 318 | + heading=0, |
| 319 | + acceleration_noise_function=lambda: np.random.normal(0, 0.3, 3) |
| 320 | + ) |
| 321 | +
|
| 322 | + flight.all_info() |
| 323 | +
|
| 324 | +See Also |
| 325 | +-------- |
| 326 | + |
| 327 | +- :doc:`Parachute Class Reference </reference/rocket/parachute>` |
| 328 | +- :doc:`Flight Simulation </user/flight>` |
| 329 | +- :doc:`Sensors and Controllers </user/sensors>` |
0 commit comments