Skip to content

Proposal: powered descent / retropropulsion module #952

@hahahuy

Description

@hahahuy

Hi, I'd like to contribute a powered descent / retropropulsion module to RocketPy — the ability to simulate a rocket re-igniting its motor (or a dedicated descent motor) after apogee to decelerate before landing, similar to what SpaceX does with Falcon 9 booster recovery.

This is currently missing from RocketPy. With 500+ university teams using the simulator, adding this capability would directly support vehicle recovery research and range safety analysis. I intend to use it for a peer-reviewed paper with a Vietnamese sounding rocket (VNSC-01/VNSC-02) as the worked example.


Proposed Architecture

After reading Flight.py, Rocket.py, Motor.py, and Environment.py, I believe the cleanest integration is a new flight phase inserted into the existing FlightPhases state machine (Option A below), rather than a subclass or a two-leg replay.

New Flight parameters

Flight(
    rocket=...,
    environment=...,
    rail_length=...,
    # ... existing params unchanged ...
    descent_motor=None,          # Motor instance, or None for ballistic descent
    descent_trigger="apogee",    # "apogee" | float (altitude m) | callable(t, y)
    guidance_schedule=None,      # pre-solved thrust schedule: Function(t) or array
)
  • guidance_schedule is intentionally pre-solved offline (e.g. with CasADi) and replayed during integration — no optimizer inside the ODE loop, so Monte Carlo runs stay fast.
  • Flights without descent_motor behave identically to today. Fully backward compatible.

New ODE derivative: u_dot_powered_descent

Structurally identical to u_dot_generalized, except:

  • net_thrust = descent_motor.thrust(t - t_ignition) (time-shifted to descent ignition)
  • Thrust direction: body-frame −Z (retro) or guidance-commanded unit vector
  • Mass: rocket.dry_mass + descent_motor.mass(t - t_ignition)

Phase insertion (inside __check_simulation_events)

After apogee detection (currently flight.py:1113–1159), insert:

self.flight_phases.add_phase(
    t_ignition,
    derivative=self.u_dot_powered_descent,
    callbacks=[self._start_descent_motor],
    clear=True,
)

Minimal file footprint

File Change
rocketpy/simulation/flight.py New params + u_dot_powered_descent + descent trigger
rocketpy/rocket/rocket.py Optional add_descent_motor() helper
tests/test_flight.py Validation cases (3-DOF vacuum, analytical check)
Motor, Environment, math utils No changes

Implementation Plan

  • now: Architecture discussion with maintainers (this issue)
  • 3-DOF vacuum descent — constant thrust, point mass, no atmosphere; validate against Tsiolkovsky + gravity-turn analytical solution; write tests
  • Open draft PR with 3-DOF implementation
  • Atmosphere, 6-DOF, guidance schedule support

And if possible, please help me figure some question out,

  1. Preferred integration point — New Flight arguments (Option A above) vs. a PoweredDescentFlight subclass vs. a composable FlightSegment approach? Subclassing is fragile given the __ private methods; I lean toward Option A.

  2. Motor API — Should we support a separate descent_motor argument on Flight, or is there appetite for multi-burn support directly in Motor (i.e. discontinuous burn windows)?

  3. Thrust direction convention — Body-frame −Z (opposing body axis) or inertial −V (opposing velocity vector)? The latter is correct for a hover-slam but requires a guidance input.

  4. enh/events branch — I see an open enh/events branch on upstream. Will this affect the event/trigger system in ways I should design around now?

  5. equations_of_motion parameter — Would equations_of_motion="powered_descent" be the right way to select this path, consistent with the existing "standard" / "solid_propulsion" options?


Background

I have a background in Aero/CFD, Propulsion, and GNC. The full design note (with class hierarchy analysis, ODE breakdown, and implementation sequence) is available — happy to share or attach.

Happy to hear if the maintainers prefer a different approach entirely before I invest in a deep implementation. Thanks for building such a clean simulator.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions