Parameters#
This section defines the parameter dataclass which is used for storing all the design data corresponding to the airplane. This class will be initialized before the start of every new analysis in the trade study. Since the wing planform is going to change, the drag build-up should also be performed from scratch. Below code block defines a function for performing drag buildup based on given parameters:
import numpy as np
def perform_drag_buildup(parameters):
"""
Method to perform drag build up based on given parameters
"""
M = parameters.cruise_mach_number
# functions
compute_Re = lambda l: parameters.rho_cruise * parameters.cruise_speed * l / parameters.dyn_viscosity # reynolds number
compute_cf_laminar = lambda re: 1.328/re**0.5 # Cf for laminar flow
compute_cf_turbulent = lambda re: 0.455/np.log10(re)**2.58 # Cf for turbulent flow
compute_ff = lambda xc, tc, sweep: (1 + 0.6*tc/xc + 100*tc**4) * (1.34*M**0.18*np.cos(sweep)**0.28) # form factor
compute_CDO = lambda Cf, ff, Q, Swet: Cf * ff * Q * Swet / S_wing # CD0 component
# Wing Parameters
S_wing = parameters.S
tc_wing = parameters.tc_avg
xc_wing = 0.3
sweep_wing = np.radians(parameters.sweep_qc) # rad
Swet_wing = 1.7 * S_wing # sq ft
Q_wing = 1.0
# HT parameters
S_ht = parameters.Sht
tc_ht = parameters.tc_ht
xc_ht = 0.3
sweep_ht = np.radians(parameters.sweep_qc_ht) # rad
Swet_ht = 2.1 * S_ht # sq ft
Q_ht = 1.04
# VT parameters
S_vt = parameters.Svt
tc_vt = parameters.tc_vt
xc_vt = 0.3
sweep_vt = np.radians(parameters.sweep_qc_vt) # rad
Swet_vt = 2.1 * S_vt # sq ft
Q_vt = 1.04
# Fuselage parameters
Swet_fuselage = 380 # sq ft
Q_fuselage = 1.0
fuselage_max_frontal_area = parameters.fuselage_max_frontal_area # sq ft
upsweep = np.radians(parameters.fuselage_upsweep) # rad
# Wing CD0
Cf_wing = compute_cf_turbulent(compute_Re(parameters.mac))
ff_wing = compute_ff(xc_wing, tc_wing, sweep_wing)
CD0_sf_wing = compute_CDO(Cf_wing, ff_wing, Q_wing, Swet_wing)
# HT CD0
Cf_ht = compute_cf_turbulent(compute_Re(parameters.mac_ht))
ff_ht = compute_ff(xc_ht, tc_ht, sweep_ht)
CD0_sf_ht = compute_CDO(Cf_ht, ff_ht, Q_ht, Swet_ht)
# VT CD0
Cf_vt = compute_cf_turbulent(compute_Re(parameters.mac_vt))
ff_vt = compute_ff(xc_vt, tc_vt, sweep_vt)
CD0_sf_vt = compute_CDO(Cf_vt, ff_vt, Q_vt, Swet_vt)
# fuselage CD0
Cf_fuselage_laminar = compute_cf_laminar(compute_Re(parameters.fuselage_lenth))
Cf_fuselage_turbulent = compute_cf_turbulent(compute_Re(parameters.fuselage_lenth))
Cf_fuselage = Cf_fuselage_laminar*0.1 + Cf_fuselage_turbulent*0.9 # 10% laminar + 90% turbulent
ff_fuselage = 1.301388 # fixed since fuselage is not changing
CD0_sf_fuselage = compute_CDO(Cf_fuselage, ff_fuselage, Q_fuselage, Swet_fuselage)
# Compute final CD0 without misc
CD0_sf = CD0_sf_fuselage + CD0_sf_wing + CD0_sf_ht + CD0_sf_vt
# Miscellaneous
CDmisc = 0
D_by_q_upsweep = 3.83 * upsweep**2.5 * fuselage_max_frontal_area
CDmisc += D_by_q_upsweep / S_wing
D_by_q_windshield = 0.07
CDmisc += D_by_q_windshield * 0.3 * fuselage_max_frontal_area / S_wing
# Final CD0
return (CD0_sf + CDmisc) * 1.05
Below code block defines the data class for storing all the aircraft parameters:
from dataclasses import dataclass
@dataclass
class AircraftParameters:
"""
Data class defining aircraft parameters
Note: Ensure values are provided in correct units
"""
# parameters
A: int
S: float
P_takeoff: float
# Mission details
range: int = 6076.11549 * 1200 # ft
endurance: float = 45*60 # sec
cruise_speed: float = 200 * 1.68781 # ft/s
cruise_altitude: float = 8000 # ft
loiter_altitude: float = 4000 # ft
oei_altitude: float = 5000 # ft
# Wing parameters
taper_ratio_wing: float = 0.4
tc_root: float = 0.18
tc_tip: float = 0.09
tc_avg: float = 0.16
sweep_qc: float = 0.0
# fuselage
Sf: float = 380.12 # sq ft, fuselage wetted area
Lf: float = 27 # ft, fuselage structural length without nose and tail cap
D: float = 1.5/12 # ft, structural depth
Lt: float = 16 # ft, tail moment arm
fuselage_lenth: float = 31.6 # ft
fuselage_upsweep: float = 10.0 # deg
fuselage_max_frontal_area: float = 24 # sq ft
# H-Tail
Aht: float = 5.5
taper_ratio_ht: float = 0.75
sweep_qc_ht: float = 10 # deg
volume_coeff_ht: float = 0.8
moment_arm_ht: float = 15 # ft
tc_ht = 0.12 # from airfoil
# V-Tail
Avt: float = 1.3
taper_ratio_vt: float = 0.55
sweep_qc_vt: float = 30 # deg
volume_coeff_vt: float = 0.065
moment_arm_vt: float = 17 # ft
tc_vt = 0.12 # from airfoil
vtail_factor: float = 0.0
# Propulsion
num_engines: int = 2
num_fuel_tanks: int = 2
prop_eff_cruise: float = 0.8
prop_eff_loiter: float = 0.7
Cbhp: float = 0.4 # lbs/hp-hr
install_loss_factor: float = 0.92 # 8% loss
num_blades: int = 3
blade_loading: float = 3.8 # lbs/sq ft
# landing gear
Ngear: float = 3 # from Table 11.5
Nl: float = 1.5 * Ngear
Lm: float = 3.5 # ft
# Takeoff
mu: float = 0.04
mu_brakes: float = 0.5
t_rotation: float = 1 # sec
# landing
gamma_approach: float = 3 #deg
t_free_roll: float = 1 # sec
# Aerodynamics
CLmax = 1.5
CLmax_takeoff = 1.8
CLmax_landing = 2.2
# Weight fractions
W1_W0: float = 0.985 # engine start, warmup, and takeoff
W2_W1: float = 0.99 # climb
W4_W3: float = 0.992 # descent
W5_W4: float = 0.99 # climb
W7_W6: float = 0.992 # descent
W8_W7: float = 0.992 # landing
# Other parameters
Nz: float = 1.5 * 3.5 # limit load factor is set to 3.5
avgas_density: float = 6.01 # lbs/gallon
num_passengers: float = 6 # including crew
Kh: float = 0.05 # factor used in hydraulics weight estimation
g: float = 32.174 # ft/s^2
h_obs: float = 50 # ft
PI: float = 3.14159
def __post_init__(self):
"""
Method to compute additional quantities based on the define data
"""
# Flight conditions, slugs/cu ft
self.rho_sea_level, _, _ = self.isa_atmosphere(0) # slugs/cu ft
self.rho_cruise, speed_sound, self.dyn_viscosity = self.isa_atmosphere(self.cruise_altitude)
self.cruise_mach_number = self.cruise_speed / speed_sound
self.rho_loiter, _, _ = self.isa_atmosphere(self.loiter_altitude) # slugs/cu ft
self.rho_oei_alt, _, _ = self.isa_atmosphere(self.oei_altitude)
# Compute wing planform
taper_ratio = self.taper_ratio_wing
self.b = (self.A * self.S)**0.5
self.wing_root_chord = 2*self.S / self.b / (1 + taper_ratio) # ft
self.wing_tip_chord = taper_ratio * self.wing_root_chord # ft
self.mac = 2/3 * self.wing_root_chord * (1 + taper_ratio + taper_ratio**2) / (1 + taper_ratio) # ft
self.wing_wetted_area = 1.7 * self.S # sq ft
tau = self.tc_tip / self.tc_root
self.fuel_tank_volume = 0.54 * self.S**2 * self.tc_root * \
(1 + self.taper_ratio_wing * tau**0.5 + tau * self.taper_ratio_wing**2) \
/ self.b / (1 + self.taper_ratio_wing)**2 * 7.48051948 # US gallons, Torenbeek equation
# Powerplant - rubber engine sizing
p_req = self.P_takeoff/self.num_engines
self.engine_weight = 5.47*(p_req)**0.78 # lbs
self.engine_len = 0.32*(p_req)**0.424 # ft
self.engine_width = 2.7 # ft
self.engine_height = 2.0 # ft
self.prop_dia = ( 4 * p_req / self.PI / self.num_blades / self.blade_loading)**0.5 # ft
self.prop_area = self.PI * self.prop_dia**2 / 4
sigma = self.rho_cruise / self.rho_sea_level
self.P_cruise = self.P_takeoff * (sigma - (1 - sigma)/7.75)
# HT Tail
self.Sht = self.volume_coeff_ht * self.S * self.mac / self.moment_arm_ht
self.bht = (self.Aht * self.Sht)**0.5
self.root_chord_ht = 2 * self.Sht / self.bht / (1 + self.taper_ratio_ht) # ft
self.tip_chord_ht = self.taper_ratio_ht * self.root_chord_ht # ft
self.mac_ht = 2 * self.root_chord_ht * (1 + self.taper_ratio_ht + self.taper_ratio_ht**2) / 3 / (1 + self.taper_ratio_ht) # ft
self.wetted_area_ht = 2 * self.Sht
# VT Tail
self.Svt = self.volume_coeff_vt * self.S * self.b / self.moment_arm_vt
self.bvt = (self.Avt * self.Svt)**0.5
self.root_chord_vt = 2 * self.Svt / self.bvt / (1 + self.taper_ratio_vt) # ft
self.tip_chord_vt = self.taper_ratio_vt * self.root_chord_vt # ft
self.mac_vt = 2 * self.root_chord_vt * (1 + self.taper_ratio_vt + self.taper_ratio_vt**2) / 3 / (1 + self.taper_ratio_vt) # ft
self.wetted_area_vt = 2 * self.Svt
# Aerodynamics
self.e = 1.78 * (1 - 0.045*self.A**0.68) - 0.64
self.e_takeoff = self.e - 0.05
self.e_landing = self.e - 0.1
self.CD0 = perform_drag_buildup(self)
self.CD0_takeoff = self.CD0 + 0.024011
self.CD0_landing = self.CD0 + 0.048074
def isa_atmosphere(self, altitude):
"""
Method to compute density, speed of sound, and dynamic viscosity
at given altitude (in feet) using the International Standard Atmosphere (ISA) model
Parameters
----------
altitude_ft : float
Altitude in feet.
Returns
-------
rho : float
Air density [slug/ft³]
a : float
Speed of sound [ft/s]
mu : float
Dynamic viscosity [slug/(ft·s)]
"""
# --- Constants ---
g = self.g # ft/s²
R = 1716.59 # ft·lbf/(slug·°R)
T0 = 518.67 # Rankine (sea level = 15°C)
p0 = 2116.22 # lbf/ft²
lapse_rate = -0.00356616 # °R/ft (≈ -6.5 K/km)
gamma = 1.4
# --- Temperature and pressure ---
if altitude <= 36089: # Troposphere
T = T0 + lapse_rate * altitude
p = p0 * (T / T0) ** (-g / (lapse_rate * R))
else: # Lower stratosphere
T_trop = T0 + lapse_rate * 36089
p_trop = p0 * (T_trop / T0) ** (-g / (lapse_rate * R))
p = p_trop * np.exp(-g * (altitude - 36089) / (R * T_trop))
T = T_trop
# --- Density (slug/ft³) ---
rho = p / (R * T)
# --- Speed of sound (ft/s) ---
a = np.sqrt(gamma * R * T)
# --- Dynamic viscosity using Sutherland's law ---
# Reference values in FPS units:
# μ0 = 3.737e-7 slug/(ft·s) at T0 = 518.67 R
# Sutherland constant = 198.72 R
mu_ref = 3.737e-7 # slug/(ft·s)
T_ref = 518.67 # Rankine
S = 198.72 # Rankine
mu = mu_ref * (T / T_ref) ** 1.5 * (T_ref + S) / (T + S)
return rho, a, mu
Note the post initialization method which computes further quantites based on the defined data. While initializing this class, the aspect ratio, wing reference area, and maximum takeoff power are required. Rest all parameters are optional and can be set during initialization. Below code block shows how one can instantiate the above class:
A = 8
S = 134 # sq ft
P_takeoff = 595 # hp
aircraft = AircraftParameters(A, S, P_takeoff)
print(f"Aspect ratio: {aircraft.A}")
print(f"Wing span: {aircraft.b:.0f} ft")
print(f"Zero-lift drag coefficient: {aircraft.CD0:.4f}")
Aspect ratio: 8
Wing span: 33 ft
Zero-lift drag coefficient: 0.0334
This concludes the parameter module, next section outlines the fuel estimation module.