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