Skip to content

F-16 Fighting Falcon — Nonlinear Longitudinal Dynamics

The General Dynamics F-16 Fighting Falcon is an American multirole lightweight 4th-generation fighter. This module provides a nonlinear longitudinal flight-channel model implemented in pure Python/NumPy. The aerodynamic coefficients are interpolated with cubic splines from wind-tunnel tables, providing high-fidelity dynamics across a wide range of angles of attack and control deflections. A Gymnasium environment is included for training control agents.

F-16 Model

  • Quick start

    Run the environment or the model in just a few lines of code.

    See example

  • Model API

    Python class documentation for the nonlinear F-16 longitudinal dynamics.

    Go to API

  • Gymnasium environment

    Ready-to-use environment for training control agents.

    Explore

  • Theory

    Nonlinear state equations and aerodynamic table structure.

    Learn more

Performance specs (reference)

Parameter Value
Variant F-16A Block 10
Wingspan, m 9.45
Aircraft length (with pitot boom), m 15.03
Aircraft height, m 5.09
Wing area, m² 27.87
Sweep angle, ° 40.0
Normal takeoff weight, kg 11467

Control object structure

Unlike the linear F-16 model, which uses constant-coefficient matrices \(A\) and \(B\), the nonlinear model computes aerodynamic forces and moments from tabular data at every time step. The equations of motion are integrated numerically (Euler or RK4).

The state vector and control input:

\[ x = \begin{bmatrix} \alpha & \omega_z & \delta_{\text{stab}} & \dot{\delta}_{\text{stab}} \end{bmatrix}^{\top}, \quad u = \delta_{\text{stab,act}} \]
  • \(\alpha\): angle of attack, rad
  • \(\omega_z\): pitch angular velocity, rad/s
  • \(\delta_{\text{stab}}\): stabilator deflection, rad
  • \(\dot{\delta}_{\text{stab}}\): stabilator deflection rate, rad/s
  • \(\delta_{\text{stab,act}}\): stabilator command (control input), rad
Parameter Symbol Value
Aircraft mass \(m\) 9295.44 kg
Wing area \(S\) 27.87 m²
Mean aerodynamic chord \(b_A\) 3.45 m
Pitch inertia \(J_z\) 75673.6 kg m²
Stabilator time constant \(T_{\text{stab}}\) 0.03 s
Stabilator damping ratio \(\xi_{\text{stab}}\) 0.707
Altitude \(H\) 3000 m
Airspeed \(V\) 150 m/s
Gravity \(g\) 9.80665 m/s²

Units

Inside the model, all angles and angular rates are in radians. The Gymnasium environment accepts actions in degrees (range \(\pm 25°\)) for compatibility with existing agents and converts to radians internally.

Mathematical model

The model solves the following system of nonlinear ODEs at each time step:

\[ \dot{\alpha} = \omega_z - \frac{R_y - mg}{mV} \]
\[ \dot{\omega}_z = \frac{M_{Rz}}{J_z} \]

where the aerodynamic force and moment are:

\[ R_y = qS \cdot C_y(\alpha, \beta, \delta_{\text{stab}}, \delta_{\text{lef}}, \omega_z, V, b_A, \delta_{\text{sb}}) \]
\[ M_{Rz} = qS b_A \cdot m_z(\alpha, \beta, \delta_{\text{stab}}, \delta_{\text{lef}}, \omega_z, V, b_A, \delta_{\text{sb}}) + x_{\text{cg}} \cdot R_y \]

The dynamic pressure \(q\) is computed from the ISA atmosphere model:

\[ q = \frac{1}{2} \rho(H) V^2, \qquad \rho = \rho_0 \left(\frac{T_0 - LH}{T_0}\right)^{\frac{g}{LR} - 1} \]

Aerodynamic tables

The coefficients \(C_y\) and \(m_z\) are not constant — they are multi-dimensional functions interpolated from wind-tunnel data stored as .npz files. The tables are interpolated using cubic splines (csaps).

Actuator model

The stabilator is modelled as a second-order system with position and rate limiting:

\[ \ddot{\delta}_{\text{stab}} = \frac{-2 T_{\text{stab}} \xi_{\text{stab}} \dot{\delta}_{\text{stab}} - \delta_{\text{stab}} + \delta_{\text{stab,act}}}{T_{\text{stab}}^2} \]

Actuator limits

The model uses the following default control limits:

  • Maximum stabilator deflection: \(\pm 25°\)
  • Maximum stabilator rate: \(\pm 60°/\text{s}\)

Integration methods

Two numerical integrators are available:

  • Euler (default) — first-order forward Euler.
  • RK4 — fourth-order Runge-Kutta, provides higher accuracy at the same time step.

Data source

  1. Stevens & Lewis, "Aircraft Control and Simulation".
  2. Wind-tunnel aerodynamic tables for F-16A, stored as NumPy .npz archives.

Quick start

import gymnasium as gym
import numpy as np

from tensoraerospace.envs import NonlinearLongitudinalF16
from tensoraerospace.utils import generate_time_period
from tensoraerospace.signals.standard import sinusoid

dt = 0.01
tp = generate_time_period(tn=20, dt=dt)
number_time_steps = len(tp)
reference_signal = sinusoid(
    degree=3, tp=tp, frequency=0.1, output_rad=True
).reshape(1, -1)

env = gym.make(
    'NonlinearLongitudinalF16-v0',
    number_time_steps=number_time_steps,
    initial_state=np.array([0.0, 0.0]),
    reference_signal=reference_signal,
    dt=dt,
    integrator="euler",
)

state, info = env.reset()
for _ in range(number_time_steps - 1):
    action = np.array([0.0])  # degrees
    state, reward, terminated, truncated, info = env.step(action)
    if terminated or truncated:
        break
import numpy as np
from tensoraerospace.aerospacemodel.f16.nonlinear.longitudinal import LongitudinalF16

dt = 0.01
number_time_steps = 200

# State: [alpha, wz, stab, dstab] (rad)
x0 = np.array([0.0, 0.0, 0.0, 0.0])

model = LongitudinalF16(
    x0=x0,
    selected_state_output=["alpha", "wz"],
    dt=dt,
    integrator="rk4",
)

for t in range(number_time_steps - 1):
    u = np.array([np.radians(-2.0)])  # stabilator command (rad)
    state_next = model.run_step(u)

Python API

LongitudinalF16(x0, selected_state_output=None, t0=0, dt=0.01, integrator='euler')

Bases: ModelBase

F-16 in isolated longitudinal channel.

Action: stab_act (elevator command, rad). State: alpha, wz, stab, dstab (rad / rad·s⁻¹).

current_state property

Most recent state as a flat 1-D ndarray (alpha, wz, stab, dstab).

NonlinearLongitudinalF16(initial_state, reference_signal, number_time_steps, tracking_states=None, state_space=None, control_space=None, output_space=None, reward_func=None, use_reward=True, dt=0.01, integrator='euler', control_bias=0.0, feedforward_fn=None, airspeed=200.0, render_mode=None, chart_states=('alpha', 'wz', 'stab'), trail_length=None, initial_pitch=0.0, damage_profile=None, damage_observable=False, damage_event_callback=None)

Bases: Env

Gymnasium env over the pure-numpy nonlinear F-16 longitudinal model.

Parameters:

Name Type Description Default
initial_state ndarray

Initial state vector. Either the full 4-element model state [alpha, wz, stab, dstab] or a shorter vector matching state_space (missing components are zero-filled). Values are interpreted in radians (matching the underlying model).

required
reference_signal ndarray

Reference trajectory of shape (1, T), radians.

required
number_time_steps int

Episode length.

required
tracking_states list[str] | None

Names of tracked states. Defaults to ["alpha"].

None
state_space list[str] | None

Subset of model states to expose as the observation. Observations are returned in radians. Defaults to ["alpha", "wz"].

None
control_space list[str] | None

Names of control channels. Defaults to ["stab"].

None
output_space list[str] | None

Compatibility alias of state_space.

None
reward_func Callable[[ndarray, ndarray, int], ndarray | float] | None

Custom reward callable (state, ref_signal, ts) -> float.

None
use_reward bool

If False, reward is fixed at 1.0 each step.

True
dt float

Discretisation step (s). Defaults to 0.01.

0.01
integrator Literal['euler', 'rk4']

"euler" (default, matches matlab) or "rk4".

'euler'
control_bias float

Constant offset (degrees) added to every action before clipping and conversion to radians. Use this to operate the agent in "delta around trim" space when the trim control is non-zero (e.g., on the nonlinear F-16, trim elevator is ~-4.45°). Default 0.

0.0
feedforward_fn Callable[[int, ndarray], float] | None

Optional callable (time_step, reference_signal) ->ff_action_deg. Whenever set, the env adds the returned offset (degrees) to the agent's action before clipping. Use this to inject a precomputed feedforward map (e.g., trim elevator as a function of reference angle of attack) so a reactive agent like IHDP only has to learn the small disturbance correction instead of the full reference-tracking trajectory. time_step is the current step index; reference_signal is the same array given at construction time. The callable can return a scalar or an array of length len(control_space).

None

Action units: by convention IHDP and most existing tensoraerospace agents were tuned with the linear F-16 env, whose elevator action is interpreted in degrees with magnitude limit 25 and rate limit 60. This env keeps the same convention so those agent settings transfer: action is in degrees, range [-25, 25], and is converted to radians internally before being handed to the underlying numpy model.

default_reward(state, ref_signal, ts) staticmethod

Tracking-error reward with a small angular-rate penalty.

F16LongParameters(m=9295.44, S=27.87, bA=3.45, Jz=75673.6, Tstab=0.03, Xistab=0.707, maxabsstab=(lambda: math.radians(25))(), maxabsdstab=(lambda: math.radians(60))(), lef=0.0, sb=0.0, g=9.80665, Oy=3000.0, V=150.0, damage_state=None, damage_geometry=None) dataclass