Engineering Objective
The eningeering objective is to develop a high fidelity simulation for accurately predicting the apogee and trajectory of LRA's competition rocket.
2025-2026 School Year Rocket
Using RocketPy
I used RocketPy to build a full-flight numerical model of the vehicle, including propulsion, mass properties, aerodynamic geometry, recovery systems, launch-rail conditions, and atmospheric effects.
RocketPy integrates the vehicle dynamics through flight, allowing the same model to be used for trajectory prediction, stability analysis, parachute deployment, and later airbrake-controller development.
Each physical subsystem is defined independently and assembled into a single rocket model before being passed into RocketPy's flight solver.
olympus = Rocket(
radius=7.858125 / 100,
mass=23473 / 1000,
inertia=(26.97, 26.97, 0.1265294793),
power_off_drag="DragOff.csv",
power_on_drag="DragOn.csv",
center_of_mass_without_motor=178 / 100,
coordinate_system_orientation="nose_to_tail",
)
olympus.add_motor(
aerotechN2000,
position=346 / 100
)
The simulation includes the actual vehicle dimensions and aerodynamic components, including the nose cone, four aft fins, canards, rail buttons, and tail geometry.
nose_cone = olympus.add_nose(
length=89.37625 / 100,
kind="lvhaack",
position=0
)
fin_set = olympus.add_trapezoidal_fins(
n=4,
root_chord=30.48 / 100,
tip_chord=7.7 / 100,
span=14.5288 / 100,
position=311.4675 / 100,
sweep_length=np.tan(np.radians(40)) * 0.15
)
Drogue, pilot, and main parachutes are incorporated into the same flight model, allowing deployment events and descent rates to be evaluated as part of the complete trajectory.
drogue = olympus.add_parachute(
name="drogue",
cd_s=1.55,
trigger="apogee",
sampling_rate=105
)
main = olympus.add_parachute(
name="main",
cd_s=2.2,
trigger=305,
sampling_rate=105
)
Once the environment and vehicle are defined, RocketPy numerically propagates the complete flight from launch rail departure through powered ascent, coast, apogee, and recovery.
test_flight = Flight(
rocket=olympus,
environment=env,
rail_length=5.2,
inclination=84,
heading=0
)
test_flight.prints.maximum_values()
test_flight.plots.trajectory_3d()
test_flight.prints.apogee_conditions()
Active Airbrake Deployment
To target a 10,000 ft apogee, I developed an active airbrake controller that continuously estimates the rocket's projected apogee during ascent. If the predicted altitude exceeds the target, the controller increases airbrake deployment to generate additional drag and remove energy from the vehicle.
Deployment is based on the current altitude, vertical velocity, atmospheric density, vehicle mass, Mach number, and the drag coefficient associated with the current airbrake extension.
Rather than using only the vacuum estimate
v² / 2g, the controller includes an approximate drag term.
This produces a more realistic prediction of how much additional
altitude the vehicle will gain before vertical velocity reaches zero.
Airbrake drag changes with both Mach number and deployment level. I use a two-dimensional interpolation table to calculate the drag coefficient for the vehicle's current flight condition and airbrake extension.
current_mach = v_mag / speed_of_sound
mach_clamped = np.clip(
current_mach,
mach_coords[0],
mach_coords[-1]
)
ext_clamped = np.clip(
air_brakes.deployment_level,
ext_coords[0],
ext_coords[-1]
)
Cd = float(
interp_cd([[ext_clamped, mach_clamped]])
)
A PD controller converts projected-apogee error into an airbrake deployment command. If the rocket is predicted to overshoot, the controller commands greater deployment and therefore greater aerodynamic drag.
error = h_target - projected_apogee
dt = time - pd_state["prev_time"]
d_error = (
(error - pd_state["prev_error"]) / dt
if dt > 0 else 0.0
)
raw_deployment = -(
kp * error +
kd * d_error
)
Deployment is limited between fully retracted and fully extended, while a rate limiter prevents unrealistically fast changes in airbrake position.
max_delta = max_rate * max(dt, 1e-6)
deployment = np.clip(
raw_deployment,
pd_state["prev_level"] - max_delta,
pd_state["prev_level"] + max_delta
)
deployment = np.clip(
deployment,
0.0,
1.0
)
air_brakes.deployment_level = deployment
2026–2027 School Year Rocket
Coming Soon!