Parameters

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.