Fixed-wing
A six-degree-of-freedom aircraft with the full non-linear aerodynamic coefficient build-up from
R. W. Beard, T. W. McLain, Small Unmanned Aircraft: Theory and Practice, Princeton University Press, 2012 — Chapter 4 and Appendix E.
Source: uav_sim/vehicles/fixed_wing/
Quick start
from uav_sim.vehicles.fixed_wing import create_fixed_wing, FixedWingPreset
aircraft = create_fixed_wing(FixedWingPreset.AEROSONDE)
controls = aircraft.reset_trimmed(airspeed=35.0, altitude=200.0)
for _ in range(6000):
aircraft.step(controls, 0.005)
print(aircraft.state[2]) # 200.0
print(aircraft.airspeed) # 35.0Thirty seconds of open-loop flight holding altitude to the metre. That is the acceptance test for the whole model: if any force or moment is inconsistent, trim is not an equilibrium and the aircraft wanders.
State and control
state = [x, y, z, φ, θ, ψ, u, v, w, p, q, r]
control = [elevator, aileron, rudder, throttle]Position is world ENU; velocity [u, v, w] is body FLU; rates are body-frame. Surfaces are radians, clamped to ±30°; throttle is [0, 1].
Storing velocity in the body frame is deliberate — angle of attack is atan2(-w, u), so the aerodynamics want it there and would otherwise rotate it back on every evaluation.
Derived quantities
aircraft.airspeed # true airspeed Va [m/s]
aircraft.alpha # angle of attack [rad]
aircraft.beta # sideslip [rad]
aircraft.pitch_up # pitch, aerospace sign convention
aircraft.flight_path_angle # climb angle [rad]
aircraft.load_factor # lift / weight — the "g" being pulled
aircraft.is_stalled() # |alpha| past the stall boundary
aircraft.velocity # world-frame velocity
aircraft.last_wrench # lift, drag, thrust from the last stepThe aerodynamic model
Forces and moments are built from dimensionless coefficients evaluated at the current flow condition. The model is written in the textbook's Forward-Right-Down frame and converted at the boundary — see Frames and conventions.
Lift, with stall
| α | C_L |
|---|---|
| 0° | 0.23 |
| 10° | 1.21 |
| 20° | 2.18 |
| 27° | 1.58 |
| 45° | 0.71 |
Lift peaks near 20° and falls away — the aircraft can genuinely stall, and recover.
Drag, from the induced-drag polar
This is where the Oswald efficiency
Moments
Three of these terms are what make the aircraft an aircraft rather than a brick with a lift force:
| Derivative | Sign | What it does |
|---|---|---|
| negative | Static pitch stability. Pitch up, get a nose-down moment back. | |
| negative | Pitch damping. Without it the short-period mode never settles. | |
| positive | Weathercock stability. The nose swings into the relative wind. | |
| negative | Roll damping. | |
| negative | Dihedral effect — sideslip produces roll. |
The rate terms are non-dimensionalised by
Propulsion
Momentum theory: the propeller accelerates air from
Thrust therefore falls off with airspeed, which is what makes level flight settle at a finite speed for a given throttle instead of accelerating forever.
Verified behaviour
Each of these has a test in tests/test_fixed_wing_aero.py:
Pitch damping 1 rad/s disturbance: 2.92° swing -> 0.19° after 8 s
Static stability +0.1 rad alpha -> +19.6 rad/s² restoring (nose-down)
Weathercock 8.1° sideslip -> 7.4 rad/s² yaw into the wind
Side force 8.1° sideslip -> 4.5 m/s² lateral acceleration
Stall CL peaks at 2.18 near 20°, falls to 0.54 by 35°
Trim residual < 1e-11 across the speed envelope
Open-loop trim 200 m held to 0.00 m over 30 sEvery parameter is load-bearing
There is a parametrised test asserting that perturbing Cm0, Cma or e_oswald changes the dynamics. It exists because the previous model declared all three and read none of them — leaving an aircraft with no static pitch stability and no induced drag that still looked complete in the source.
Custom coefficients
from dataclasses import replace
from uav_sim.vehicles.fixed_wing import AeroCoefficients, FixedWing, FixedWingParams
unstable = replace(AeroCoefficients(), Cma=-0.2) # nearly neutral in pitch
aircraft = FixedWing(FixedWingParams(coeffs=unstable, mass=10.0))Making an aircraft unstable is a good way to see what the derivatives buy you. Set Cmq = 0.0 and watch the short period ring forever.
Envelope
params = aircraft.fw_params
params.stall_airspeed # derived from the model's own CL_max
params.aspect_ratio # b^2 / S
params.wing_loading() # N/m^2
params.max_lift_coefficientstall_airspeed is computed as
See also
- Trim and equilibrium — solving for steady flight
- Airframe presets — the four supplied aircraft
- Autopilots — altitude, airspeed and course hold
- Fixed-wing flight simulation