Note

This page was generated from a Jupyter notebook.

Trim Envelope and Climb Analysis

This notebook demonstrates how to:

  1. Generate a trim envelope across a range of altitudes

  2. Analyze the required throttle and elevator settings

  3. Simulate climbing from 1,000 ft to 20,000 ft using initial trim settings

  4. Simulate climbing using interpolated trim settings from the envelope

Import Required Libraries

[1]:
import jsbsim
import matplotlib.pyplot as plt
import numpy as np
import math

Initialize JSBSim and Set Up Aircraft

[2]:
AIRCRAFT_NAME="A4"

fdm = jsbsim.FGFDMExec(None)
fdm.set_debug_level(0) # Suppress verbose JSBSim console output

fdm.load_model(AIRCRAFT_NAME)


# Set engines running
fdm['propulsion/set-running'] = -1


     JSBSim Flight Dynamics Model v1.3.0 Apr  9 2026 10:00:08
            [JSBSim-ML v2.0]

JSBSim startup beginning ...

Define Envelope Limits and Parameters

[3]:
# Set alpha range for trim solutions
fdm['aero/alpha-max-rad'] = math.radians(12)   # Maximum angle of attack in radians.
fdm['aero/alpha-min-rad'] = math.radians(-4.0) # Minimum angle of attack in radians.

# Set envelope limits
speed = 300         # KCAS
min_alt = 1000      # Set the minimum alt (ft).
max_alt = 20000     # Set the maximum alt (ft).
alt_step = 1000     # Set the altitude step (ft).
gamma = 5           # Set the flight path angle range (deg).

Generate Trim Envelope

Compute trim solutions across the altitude range for a constant speed and flight path angle.

[4]:
# Trim results
results = []  # Initialize an empty list to store the trim results.

# Iterate over a range of altitudes and for each speed a range of flight path angles (gamma)
for alt in range(min_alt, max_alt+1, alt_step):
    # Set the initial conditions
    fdm['ic/h-sl-ft'] = alt         # altitude above sea level (ft)
    fdm['ic/vc-kts'] = speed        # calibrated airspeed (kts)
    fdm['ic/gamma-deg'] = gamma     # flight path angle (deg)

    # Initialize the aircraft with initial conditions
    fdm.run_ic()

    # Trim the aircraft.
    try:
        # 1 means straight flight by using all changeable control variables.
        fdm['simulation/do_simple_trim'] = 1

        results.append((alt, fdm['fcs/throttle-cmd-norm[0]'], fdm['fcs/pitch-trim-cmd-norm']))

    except jsbsim.TrimFailureError:
        pass  # Ignore trim failures

Plot Trim Envelope Results

Display the required throttle and elevator settings as functions of altitude.

[5]:
# Extract the trim results
alt, throttle, elevator = zip(*results)

plt.rcParams["figure.figsize"] = (16, 8)  # Set the figure size for matplotlib plots.

# Plot the trim envelope results, with required thrust and AoA indicated via a colormap
fig, (axThrust, axElevator) = plt.subplots(1, 2)  # Create a figure with two subplots (thrust and AoA)

# Graph data for each of the sub plots (title, ax, data)
graph_data = [ ('Thrust', axThrust, throttle), ('Elevator', axElevator, elevator) ]

for title, ax, data in graph_data:
    ax.plot(alt, data, marker='o')
    ax.set_xlabel('Altitude (ft)')
    ax.set_ylabel(f'{title} (cmd-norm)')
    ax.set_title(f'Climb - {title}')

plt.show()  # Display the plot.
../_images/notebooks_10_trim_envelope_climb_10_0.png

Define Flight Simulation Functions

Function 1: Climb with Initial Trim

This function climbs from 1,000 ft to 20,000 ft using only the initial trim solution at 1,000 ft.

[6]:
def fly_initial_trim(speed, gamma):

    # Set the initial conditions
    fdm['ic/h-sl-ft'] = 1000        # altitude above sea level (ft)
    fdm['ic/vc-kts'] = speed        # calibrated airspeed (kts)
    fdm['ic/gamma-deg'] = gamma     # flight path angle (deg)

    # Initialize the aircraft with initial conditions
    fdm.run_ic()

    # Trim the aircraft.
    fdm['simulation/do_simple_trim'] = 1

    climb_results = []

    while fdm['position/h-sl-ft'] < 20000:
        fdm.run()
        climb_results.append((fdm['position/h-sl-ft'], fdm['velocities/vc-kts'], fdm['flight-path/gamma-deg']))

    alts, speeds, gammas = zip(*climb_results)

    fig, (axSpeed, axGamma) = plt.subplots(1, 2)

    axSpeed.plot(alts, speeds)
    axGamma.plot(alts, gammas)

    axSpeed.set_ylabel('KCAS (kt)')
    axSpeed.set_xlabel('Altitude (ft)')

    axGamma.set_ylabel('Gamma (deg)')
    axGamma.set_xlabel('Altitude (ft)')

    plt.show()

Function 2: Climb with Interpolated Trim

This function climbs from 1,000 ft to 20,000 ft using interpolated throttle and elevator commands from the trim envelope.

[7]:
def fly_interpolated_trim(speed, gamma, alt, throttle, elevator):

    # Set the initial conditions
    fdm['ic/h-sl-ft'] = 1000        # altitude above sea level (ft)
    fdm['ic/vc-kts'] = speed        # calibrated airspeed (kts)
    fdm['ic/gamma-deg'] = gamma     # flight path angle (deg)

    # Initialize the aircraft with initial conditions
    fdm.run_ic()

    # Trim the aircraft.
    fdm['simulation/do_simple_trim'] = 1

    interp_climb_results = []

    while fdm['position/h-sl-ft'] < 20000:
        fdm.run()

        # Interpolate
        throttle_cmd = np.interp(fdm['position/h-sl-ft'], alt, throttle)
        elevator_cmd = np.interp(fdm['position/h-sl-ft'], alt, elevator)

        fdm['fcs/throttle-cmd-norm'] = throttle_cmd
        fdm['fcs/pitch-trim-cmd-norm'] = elevator_cmd

        interp_climb_results.append((fdm['position/h-sl-ft'], fdm['velocities/vc-kts'], fdm['flight-path/gamma-deg']))

    alt, speeds, gammas = zip(*interp_climb_results)

    fig, (axSpeed, axGamma) = plt.subplots(1, 2)

    axSpeed.plot(alt, speeds)
    axGamma.plot(alt, gammas)

    axSpeed.set_ylabel('KCAS (kt)')
    axSpeed.set_xlabel('Altitude (ft)')

    axGamma.set_ylabel('Gamma (deg)')
    axGamma.set_xlabel('Altitude (ft)')

    plt.show()

Execute Climb Simulations

Run the interpolated trim climb simulation. You can uncomment the initial trim function to compare results.

[8]:
# Uncomment the line below to run the initial trim climb
# fly_initial_trim(speed, gamma)

# Run the interpolated trim climb
fly_interpolated_trim(speed, gamma, alt, throttle, elevator)
../_images/notebooks_10_trim_envelope_climb_17_0.png