Full Aircraft Configuration with Control Surfaces
Level: Advanced
This notebook demonstrates a complete aircraft configuration including:
Wing with ailerons and flaps
Fuselage
Horizontal tail with elevator
Vertical tail with rudder
We use the OpenVSP Python API (with VLM fallback) and study control surface effectiveness through deflection parameter sweeps.
Topics covered
Complete aircraft geometry creation in OpenVSP
Control surface definition (ailerons, flaps, elevator, rudder)
Longitudinal stability analysis (CL-alpha, Cm-alpha)
Control surface deflection effects on aerodynamic coefficients
Lateral-directional analysis
References
OpenVSP Python API: https://openvsp.org/pyapi_docs/latest/
Raymer, D.P., Aircraft Design: A Conceptual Approach, AIAA, 2018
Nelson, R.C., Flight Stability and Automatic Control, McGraw-Hill, 1998
[ ]:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath('.')), 'src'))
from aerodemo.openvsp_utils import (
check_openvsp, init_vsp, add_wing, add_fuselage,
add_horizontal_tail, add_vertical_tail, run_vspaero
)
from aerodemo.vlm import WingGeometry, VortexLatticeMethod
HAS_VSP = check_openvsp()
print(f"OpenVSP available: {HAS_VSP}")
if not HAS_VSP:
print("NOTE: Running in VLM fallback mode (OpenVSP not installed).")
print(" Control surface effects will be modeled analytically.")
plt.rcParams.update({'figure.dpi': 100, 'axes.grid': True, 'grid.alpha': 0.3, 'font.size': 11})
1. Complete Aircraft Configuration
We design a conventional low-wing transport/trainer aircraft.
Control surface layout
╔═══ Elevator ═══╗
╔══ Aileron ══╗ ╔══╗ ╔══════╗
────────┤ WING ├──┤ F├────────┤ H.T. ├──
╚═════════════╝ ╚══╝ ╚══════╝
│
┌────┴────┐
│ V.T. │
│ (Rudder)│
└─────────┘
Surface |
Type |
Chord fraction |
Span fraction |
|---|---|---|---|
Aileron |
Roll control |
25% |
60–95% semispan |
Flap |
High-lift |
30% |
15–60% semispan |
Elevator |
Pitch control |
35% |
Full h-tail span |
Rudder |
Yaw control |
35% |
Full v-tail height |
[ ]:
# Full aircraft configuration
aircraft = {
# Wing
'wing_span': 28.0,
'wing_root_chord': 4.5,
'wing_tip_chord': 1.8,
'wing_sweep': 20.0,
'wing_dihedral': 5.0,
'wing_x': 10.0,
'wing_z': -1.2,
# Fuselage
'fuse_length': 30.0,
'fuse_diameter': 3.2,
# Horizontal tail
'htail_span': 10.0,
'htail_root_chord': 2.0,
'htail_tip_chord': 1.0,
'htail_x': 26.0,
'htail_z': 0.5,
# Vertical tail
'vtail_height': 4.5,
'vtail_root_chord': 3.0,
'vtail_tip_chord': 1.5,
'vtail_x': 25.0,
'vtail_z': 0.0,
}
# Reference areas and lengths
S_w = aircraft['wing_span'] * (aircraft['wing_root_chord'] + aircraft['wing_tip_chord']) / 2
b_w = aircraft['wing_span']
AR_w = b_w**2 / S_w
lam_w = aircraft['wing_tip_chord'] / aircraft['wing_root_chord']
MAC_w = (2/3) * aircraft['wing_root_chord'] * (1 + lam_w + lam_w**2) / (1 + lam_w)
S_h = aircraft['htail_span'] * (aircraft['htail_root_chord'] + aircraft['htail_tip_chord']) / 2
VH = S_h * (aircraft['htail_x'] - aircraft['wing_x']) / (S_w * MAC_w) # horizontal tail volume
S_v = aircraft['vtail_height'] * (aircraft['vtail_root_chord'] + aircraft['vtail_tip_chord']) / 2
VV = S_v * (aircraft['vtail_x'] - aircraft['wing_x']) / (S_w * b_w) # vertical tail volume
print("Full Aircraft Configuration:")
print(f"\nWing:")
print(f" Span b = {b_w:.1f} m, S_w = {S_w:.1f} m², AR = {AR_w:.2f}, λ = {lam_w:.3f}")
print(f" MAC = {MAC_w:.3f} m, Sweep = {aircraft['wing_sweep']:.1f}°")
print(f"\nHorizontal Tail:")
print(f" S_HT = {S_h:.2f} m², Volume ratio V_H = {VH:.3f}")
print(f"\nVertical Tail:")
print(f" S_VT = {S_v:.2f} m², Volume ratio V_V = {VV:.3f}")
print(f"\nFuselage: L={aircraft['fuse_length']:.1f} m, D={aircraft['fuse_diameter']:.1f} m")
2. Build OpenVSP Model
[ ]:
if HAS_VSP:
init_vsp("FullAircraft")
fuse_id = add_fuselage(
length=aircraft['fuse_length'],
max_diameter=aircraft['fuse_diameter'],
name="Fuselage"
)
wing_id = add_wing(
span=aircraft['wing_span'],
root_chord=aircraft['wing_root_chord'],
tip_chord=aircraft['wing_tip_chord'],
sweep_deg=aircraft['wing_sweep'],
dihedral_deg=aircraft['wing_dihedral'],
x_offset=aircraft['wing_x'],
z_offset=aircraft['wing_z'],
name="Wing"
)
htail_id = add_horizontal_tail(
span=aircraft['htail_span'],
root_chord=aircraft['htail_root_chord'],
tip_chord=aircraft['htail_tip_chord'],
x_offset=aircraft['htail_x'],
z_offset=aircraft['htail_z'],
name="HTail"
)
vtail_id = add_vertical_tail(
height=aircraft['vtail_height'],
root_chord=aircraft['vtail_root_chord'],
tip_chord=aircraft['vtail_tip_chord'],
x_offset=aircraft['vtail_x'],
z_offset=aircraft['vtail_z'],
name="VTail"
)
print("Full aircraft VSP model created:")
print(f" Fuselage ID: {fuse_id}")
print(f" Wing ID: {wing_id}")
print(f" H-Tail ID: {htail_id}")
print(f" V-Tail ID: {vtail_id}")
else:
print("VSP model creation skipped (OpenVSP not installed).")
3. Longitudinal Stability Analysis
For longitudinal stability, the key parameter is:
The pitching moment coefficient about the CG depends on the tail volume ratio and the downwash factor at the tail:
where \(\varepsilon\) is the downwash angle at the horizontal tail.
[ ]:
# Analytical longitudinal stability estimate
alpha_range = np.linspace(-4, 14, 19)
# Wing aerodynamics (VLM)
wing_geom = WingGeometry(
span=aircraft['wing_span'],
root_chord=aircraft['wing_root_chord'],
tip_chord=aircraft['wing_tip_chord'],
sweep_angle=aircraft['wing_sweep'],
dihedral=aircraft['wing_dihedral'],
n_spanwise=14,
n_chordwise=5,
)
vlm_wing = VortexLatticeMethod(wing_geom)
wing_sweep_result = vlm_wing.sweep_alpha(alpha_range)
# H-tail aerodynamics (VLM)
htail_geom = WingGeometry(
span=aircraft['htail_span'],
root_chord=aircraft['htail_root_chord'],
tip_chord=aircraft['htail_tip_chord'],
n_spanwise=8,
n_chordwise=3,
)
vlm_htail = VortexLatticeMethod(htail_geom)
htail_sweep = vlm_htail.sweep_alpha(alpha_range)
# Combined aerodynamics (simplified superposition)
# Fuselage contribution (approximate destabilizing moment)
k_fuse = 0.05 # fuselage moment coefficient per radian (destabilizing)
CL_total = wing_sweep_result['CL'] + S_h / S_w * 0.9 * htail_sweep['CL']
CDi_total = wing_sweep_result['CDi'] + (S_h / S_w)**2 * htail_sweep['CDi']
# Pitching moment (simplified): Cm = Cm_ac + CL * (x_ac - x_cg) / MAC
x_ac_wing = aircraft['wing_x'] + 0.25 * MAC_w # aerodynamic center
x_cg = aircraft['wing_x'] + 0.30 * MAC_w # CG at 30% MAC
l_ht = aircraft['htail_x'] - x_cg # moment arm
# Static margin (positive = stable)
x_np_frac = (x_ac_wing + S_h / S_w * l_ht) / MAC_w - aircraft['wing_x'] / MAC_w
x_cg_frac = 0.30 # 30% MAC
SM = x_np_frac - x_cg_frac
print(f"Neutral point: x_NP/MAC = {x_np_frac:.3f}")
print(f"CG location: x_CG/MAC = {x_cg_frac:.3f}")
print(f"Static margin: SM = {SM*100:.1f}% MAC ({'Stable' if SM>0 else 'UNSTABLE'})")
alpha_rad = np.deg2rad(alpha_range)
Cm_alpha = (CL_total[-1] - CL_total[0]) / (alpha_rad[-1] - alpha_rad[0]) * (x_cg_frac - x_np_frac)
Cm = Cm_alpha * alpha_rad + 0.05 # offset for trim at ~3 deg
print(f"\nCm_alpha = {Cm_alpha:.4f} /rad ({'Stable' if Cm_alpha < 0 else 'UNSTABLE'})")
[ ]:
fig = plt.figure(figsize=(15, 10))
gs = gridspec.GridSpec(2, 3, figure=fig)
ax1 = fig.add_subplot(gs[0, 0])
ax1.plot(alpha_range, CL_total, 'bo-', linewidth=2, markersize=6, label='Total (wing+tail)')
ax1.plot(alpha_range, wing_sweep_result['CL'], 'g--', linewidth=1.5, label='Wing only')
ax1.plot(alpha_range, S_h/S_w * 0.9 * htail_sweep['CL'], 'r-.', linewidth=1.5, label='HT contribution')
ax1.axhline(0, color='k', linewidth=0.5)
ax1.set_xlabel('α [deg]')
ax1.set_ylabel('$C_L$')
ax1.set_title('Lift Contributions', fontweight='bold')
ax1.legend(fontsize=9)
ax2 = fig.add_subplot(gs[0, 1])
ax2.plot(alpha_range, Cm, 'mo-', linewidth=2, markersize=6)
ax2.axhline(0, color='k', linewidth=1)
ax2.set_xlabel('α [deg]')
ax2.set_ylabel('$C_m$')
ax2.set_title(f'Pitching Moment (SM={SM*100:.1f}%)', fontweight='bold')
color_sm = 'green' if SM > 0 else 'red'
ax2.text(0.05, 0.95, f'Statically {"Stable" if SM>0 else "Unstable"}',
transform=ax2.transAxes, color=color_sm, fontsize=10, fontweight='bold',
va='top')
ax3 = fig.add_subplot(gs[0, 2])
ax3.plot(CDi_total, CL_total, 'rs-', linewidth=2, markersize=6, label='Total')
ax3.plot(wing_sweep_result['CDi'], wing_sweep_result['CL'], 'g--',
linewidth=1.5, label='Wing only')
ax3.set_xlabel('$C_{Di}$')
ax3.set_ylabel('$C_L$')
ax3.set_title('Drag Polar', fontweight='bold')
ax3.legend()
# Control surface effects (analytical model)
ax4 = fig.add_subplot(gs[1, 0])
delta_e_range = np.linspace(-25, 25, 11) # elevator deflection [deg]
# Elevator effectiveness: dCm/d(delta_e) = -tau * C_L_alpha_HT * V_H
tau_e = 0.6 # flap effectiveness factor (depends on chord ratio)
a_ht = 2 * np.pi / (1 + 2 * np.pi / (np.pi * 8.0)) # h-tail lift slope
dCm_de = -tau_e * a_ht * VH * np.deg2rad(1) # per degree
Cm_delta_e = dCm_de * delta_e_range
ax4.plot(delta_e_range, Cm_delta_e, 'co-', linewidth=2, markersize=6)
ax4.axhline(0, color='k', linewidth=0.5)
ax4.set_xlabel('Elevator deflection δ_e [deg]')
ax4.set_ylabel('ΔCm')
ax4.set_title('Elevator Effectiveness', fontweight='bold')
ax4.text(0.05, 0.05, f'dCm/dδ_e = {dCm_de*180/np.pi:.4f}/deg',
transform=ax4.transAxes, fontsize=9)
ax5 = fig.add_subplot(gs[1, 1])
delta_a_range = np.linspace(-25, 25, 11) # aileron deflection [deg]
# Rolling moment coefficient: Cl_delta_a
tau_a = 0.55 # aileron effectiveness
eta1, eta2 = 0.60, 0.95 # span fraction limits of aileron
Cl_da = 2 * np.pi / (1 + 2*np.pi/(np.pi*AR_w)) * tau_a * (eta2 - eta1) / (2*AR_w)
dCl_da_deg = Cl_da * np.deg2rad(1)
Cl_roll = dCl_da_deg * delta_a_range
ax5.plot(delta_a_range, Cl_roll, 'ro-', linewidth=2, markersize=6)
ax5.axhline(0, color='k', linewidth=0.5)
ax5.set_xlabel('Aileron deflection δ_a [deg]')
ax5.set_ylabel('$C_l$ (rolling moment)')
ax5.set_title('Aileron Effectiveness', fontweight='bold')
ax5.text(0.05, 0.95, f'dCl/dδ_a = {dCl_da_deg:.5f}/deg',
transform=ax5.transAxes, fontsize=9, va='top')
ax6 = fig.add_subplot(gs[1, 2])
delta_r_range = np.linspace(-25, 25, 11) # rudder deflection [deg]
tau_r = 0.55
a_vt = 2 * np.pi / (1 + 2*np.pi/(np.pi * aircraft['vtail_height']**2/S_v * 2))
dCn_dr = -tau_r * a_vt * VV * np.deg2rad(1)
Cn_yaw = dCn_dr * delta_r_range
ax6.plot(delta_r_range, Cn_yaw, 'yo-', linewidth=2, markersize=6, color='darkorange')
ax6.axhline(0, color='k', linewidth=0.5)
ax6.set_xlabel('Rudder deflection δ_r [deg]')
ax6.set_ylabel('$C_n$ (yawing moment)')
ax6.set_title('Rudder Effectiveness', fontweight='bold')
ax6.text(0.05, 0.05, f'dCn/dδ_r = {dCn_dr*180/np.pi:.4f}/deg',
transform=ax6.transAxes, fontsize=9)
plt.suptitle('Full Aircraft — Stability and Control Derivatives', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.savefig('full_aircraft_stability.png', bbox_inches='tight', dpi=100)
plt.show()
4. Flap Deflection — High-Lift Analysis
Flaps increase the effective camber, shifting the lift curve up (increasing \(C_{L_{max}}\) and zero-lift CL) without changing the slope significantly.
[ ]:
from aerodemo.naca_airfoil import NACAFourDigit
# Flap effect on airfoil: equivalent to increased camber
# Model: flap deflection shifts zero-lift angle
flap_deflections = [0, 10, 20, 30, 40]
alpha_range_flap = np.linspace(-5, 25, 100)
S_flap_ratio = 0.30 # flap area as fraction of wing area
tau_flap = 0.60 # flap effectiveness factor
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
colors_flap = plt.cm.cool(np.linspace(0, 1, len(flap_deflections)))
ax = axes[0]
for delta_f, col in zip(flap_deflections, colors_flap):
# Simulate flap as increased camber (effective zero-lift shift)
delta_alpha_L0 = -tau_flap * S_flap_ratio * np.deg2rad(delta_f)
CL_flap = 2 * np.pi * (np.deg2rad(alpha_range_flap) - delta_alpha_L0)
alpha_L0_deg = np.rad2deg(delta_alpha_L0)
ax.plot(alpha_range_flap, CL_flap, color=col, linewidth=2,
label=f'δ_f = {delta_f}° (Δα_L0={alpha_L0_deg:.1f}°)')
ax.axhline(0, color='k', linewidth=0.5)
ax.set_xlabel('α [deg]')
ax.set_ylabel('$C_L$')
ax.set_title('Effect of Flap Deflection on Lift Curve', fontweight='bold')
ax.legend(fontsize=9)
ax.set_ylim(-1, 4)
# CL at landing alpha (say 8 deg)
ax = axes[1]
alpha_land = 8.0
CL_landing = [2 * np.pi * (np.deg2rad(alpha_land) + tau_flap * S_flap_ratio * np.deg2rad(df))
for df in flap_deflections]
delta_CL = [cl - CL_landing[0] for cl in CL_landing]
ax.bar(flap_deflections, CL_landing, color=colors_flap, alpha=0.8, width=6)
for i, (df, cl, dcl) in enumerate(zip(flap_deflections, CL_landing, delta_CL)):
ax.text(df, cl + 0.03, f'{cl:.2f}\n(+{dcl:.2f})', ha='center', fontsize=9)
ax.set_xlabel('Flap deflection δ_f [deg]')
ax.set_ylabel(f'$C_L$ at α={alpha_land}°')
ax.set_title(f'Lift Increment from Flaps (α={alpha_land}°)', fontweight='bold')
plt.tight_layout()
plt.savefig('flap_effects.png', bbox_inches='tight', dpi=100)
plt.show()
print("\nFlap Effectiveness Summary:")
print(f" Flap area ratio: S_f/S_w = {S_flap_ratio*100:.0f}%")
print(f" Flap effectiveness: τ_f = {tau_flap:.2f}")
print(f"\n δ_f [°] CL @ α=8° ΔCL")
print(" " + "-" * 30)
for df, cl, dcl in zip(flap_deflections, CL_landing, delta_CL):
print(f" {df:5.0f}° {cl:.4f} {dcl:+.4f}")
5. VSPAero Analysis with OpenVSP (if available)
If OpenVSP is installed, we run a full VSPAero sweep on the complete 3D model.
[ ]:
if HAS_VSP:
print("Running VSPAero sweep on full aircraft model...")
vsp_full = run_vspaero(
alpha_start=-4.0,
alpha_end=14.0,
alpha_npts=10,
mach=0.2,
ref_area=S_w,
ref_span=b_w,
ref_chord=MAC_w,
)
if vsp_full:
print("\nVSPAero Results:")
print(f" {'Alpha':>8} {'CL':>8} {'CDi':>8} {'Cm':>8}")
print(" " + "-" * 36)
for i in range(len(vsp_full['Alpha'])):
cm_val = vsp_full.get('Cm', [0]*10)[i]
print(f" {vsp_full['Alpha'][i]:8.2f} {vsp_full['CL'][i]:8.4f} "
f"{vsp_full['CDi'][i]:8.4f} {cm_val:8.4f}")
else:
print("VSPAero analysis returned no results.")
else:
print("OpenVSP not available. Skipping VSPAero analysis.")
print("Install OpenVSP with: pip install openvsp")
6. Summary
In this advanced notebook we have:
Defined a complete aircraft configuration: wing + fuselage + horizontal tail + vertical tail
Analyzed longitudinal stability: static margin, pitching moment curve
Computed control surface effectiveness:
Elevator: pitch control (dCm/dδ_e)
Aileron: roll control (dCl/dδ_a)
Rudder: yaw control (dCn/dδ_r)
Studied flap deflection effects on lift augmentation
(Optional) Run VSPAero on the full 3D OpenVSP model
Key takeaways
Horizontal tail provides pitch stability — requires negative Cm_alpha
Static margin SM = x_NP - x_CG (positive = stable, typically 5–15% MAC)
Control surfaces change moments by shifting the zero-lift condition
Flaps are primarily a high-lift device — critical for takeoff and landing
Configuration hierarchy
This notebook completes the progression:
01_airfoil→ 2D airfoil aerodynamics02_finite_wing→ 3D wing effects (induced drag, span loading)03_wing_fuselage→ fuselage-wing interaction04_complete_aircraft→ full aircraft stability and control