Source code for fastga.models.aerodynamics.components.figure_digitization

"""
Generic class containing all the digitization needed to compute the aerodynamic
coefficient of the aircraft.
"""
#  This file is part of FAST-OAD_CS23 : A framework for rapid Overall Aircraft Design
#  Copyright (C) 2022  ONERA & ISAE-SUPAERO
#  FAST is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.

import logging
import functools
import os.path as pth
from typing import List

import numpy as np
import openmdao.api as om
import pandas as pd
from scipy import interpolate

from . import resources

DELTA_CD_PLAIN_FLAP = "delta_drag_plain_flap.csv"
K_PLAIN_FLAP = "k_plain_flap.csv"
CL_DELTA_TH_PLAIN_FLAP = "cl_delta_th_plain_flap.csv"
K_CL_DELTA_PLAIN_FLAP = "k_cl_delta_plain_flap.csv"
K_SINGLE_SLOT = "k_single_slot.csv"
BASE_INCREMENT_CL_MAX = "base_increment.csv"
K1 = "k1.csv"
K2 = "k2.csv"
K3 = "k3.csv"
KB_FLAPS = "kb_flaps.csv"
A_DELTA_AIRFOIL = "a_delta_airfoil.csv"
K_A_DELTA = "k_a_delta.csv"
K_P_FLAPS = "k_p.csv"
DELTA_CM_DELTA_CL_REF = "delta_cm_delta_cl_ref.csv"
K_DELTA = "k_delta.csv"
K_AR_FUSELAGE = "k_ar_fuselage.csv"
K_VH = "k_vh.csv"
K_CH_ALPHA = "k_ch_alpha.csv"
CH_ALPHA_TH = "ch_alpha_th.csv"
K_CH_DELTA = "k_ch_delta.csv"
CH_DELTA_TH = "ch_delta_th.csv"
K_FUS = "k_fus.csv"
CL_BETA_SWEEP = "cl_beta_sweep_contribution.csv"
K_M_LAMBDA = "sweep_compressibility_correction.csv"
K_FUSELAGE = "cl_beta_fuselage_correction.csv"
CL_BETA_AR = "cl_beta_ar_contribution.csv"
CL_BETA_GAMMA = "cl_beta_dihedral_contribution.csv"
K_M_GAMMA = "dihedral_compressibility_correction.csv"
K_TWIST = "twist_correction.csv"
K_ROLL_DAMPING = "cl_p_roll_damping_parameter.csv"
K_CDI_ROLL_DAMPING = "cl_p_cdi_roll_damping.csv"
CL_R_LIFT_PART_A = "cl_r_lift_effect_part_a.csv"
CL_R_LIFT_PART_B = "cl_r_lift_effect_part_b.csv"
CL_R_TWIST_EFFECT = "cl_r_twist_effect.csv"
CN_DELTA_A_K_A = "cn_delta_a_correlation_cst.csv"
CN_P_TWIST = "cn_p_twist_contribution.csv"
CN_R_LIFT_EFFECT = "cn_r_lift_effect.csv"
CN_R_DRAG_EFFECT = "cn_r_drag_effect.csv"

_LOGGER = logging.getLogger(__name__)


[docs]class FigureDigitization(om.ExplicitComponent): """Provides lift and drag increments due to high-lift devices.""" def __init__(self, **kwargs): super().__init__(**kwargs) self.phase = None
[docs] @staticmethod @functools.lru_cache(maxsize=128) def delta_cd_plain_flap(chord_ratio, control_deflection) -> float: """ Roskam data to account for the profile drag increment due to the deployment of plain flap (figure 4.44). :param chord_ratio: control surface over lifting surface ratio. :param control_deflection: control surface deflection, in deg. :return delta_cd_flap: profile drag increment due to the deployment of flaps. """ file = pth.join(resources.__path__[0], DELTA_CD_PLAIN_FLAP) db = pd.read_csv(file) x_15, y_15 = filter_nans(db, ["DELTA_F_15_X", "DELTA_F_15_Y"]) x_60, y_60 = filter_nans(db, ["DELTA_F_60_X", "DELTA_F_60_Y"]) if chord_ratio != np.clip( chord_ratio, min(min(x_15), min(x_60)), max(max(x_15), max(x_60)) ): _LOGGER.warning("Chord ratio outside of the range in Roskam's book, value clipped") x_value_00 = 0.0 x_value_15 = np.interp(np.clip(float(chord_ratio), min(x_15), max(x_15)), x_15, y_15) x_value_60 = np.interp(np.clip(float(chord_ratio), min(x_60), max(x_60)), x_60, y_60) if control_deflection != np.clip(control_deflection, 0.0, 60.0): _LOGGER.warning( "Control surface deflection outside of the range in Roskam's book, value clipped" ) delta_cd_flap = float( np.polyval( np.polyfit([0.0, 15.0, 60.0], [x_value_00, x_value_15, x_value_60], 2), np.clip(control_deflection, 0.0, 60.0), ) ) return delta_cd_flap
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k_prime_plain_flap(flap_angle, chord_ratio): """ Roskam data to estimate the correction factor to estimate non-linear lift behaviour of plain flap (figure 8.13). :param flap_angle: the flap angle (in °). :param chord_ratio: flap chord over wing chord ratio. :return k_prime: correction factor to estimate non-linear lift behaviour of plain flap. """ file = pth.join(resources.__path__[0], K_PLAIN_FLAP) db = pd.read_csv(file) x_10, y_10 = filter_nans(db, ["X_10", "Y_10"]) x_15, y_15 = filter_nans(db, ["X_15", "Y_15"]) x_25, y_25 = filter_nans(db, ["X_25", "Y_25"]) x_30, y_30 = filter_nans(db, ["X_30", "Y_30"]) x_40, y_40 = filter_nans(db, ["X_40", "Y_40"]) x_50, y_50 = filter_nans(db, ["X_50", "Y_50"]) if ( (flap_angle != np.clip(flap_angle, min(x_10), max(x_10))) or (flap_angle != np.clip(flap_angle, min(x_15), max(x_15))) or (flap_angle != np.clip(flap_angle, min(x_25), max(x_25))) or (flap_angle != np.clip(flap_angle, min(x_30), max(x_30))) or (flap_angle != np.clip(flap_angle, min(x_40), max(x_40))) or (flap_angle != np.clip(flap_angle, min(x_50), max(x_50))) ): _LOGGER.warning("Flap angle value outside of the range in Roskam's book, value clipped") k_chord = [ float(np.interp(np.clip(flap_angle, min(x_10), max(x_10)), x_10, y_10)), float(np.interp(np.clip(flap_angle, min(x_15), max(x_15)), x_15, y_15)), float(np.interp(np.clip(flap_angle, min(x_25), max(x_25)), x_25, y_25)), float(np.interp(np.clip(flap_angle, min(x_30), max(x_30)), x_30, y_30)), float(np.interp(np.clip(flap_angle, min(x_40), max(x_40)), x_40, y_40)), float(np.interp(np.clip(flap_angle, min(x_50), max(x_50)), x_50, y_50)), ] if chord_ratio != np.clip(chord_ratio, 0.1, 0.5): _LOGGER.warning( "Chord ratio value outside of the range in Roskam's book, value clipped" ) k_prime = float( np.interp(np.clip(chord_ratio, 0.1, 0.5), [0.1, 0.15, 0.25, 0.3, 0.4, 0.5], k_chord) ) return k_prime
[docs] @staticmethod @functools.lru_cache(maxsize=128) def cl_delta_theory_plain_flap(thickness, chord_ratio): """ Roskam data to estimate the theoretical airfoil lift effectiveness of a plain flap ( figure 8.14). :param thickness: the airfoil thickness. :param chord_ratio: flap chord over wing chord ratio. :return cl_delta: theoretical airfoil lift effectiveness of the plain flap. """ file = pth.join(resources.__path__[0], CL_DELTA_TH_PLAIN_FLAP) db = pd.read_csv(file) x_0, y_0 = filter_nans(db, ["X_0", "Y_0"]) x_04, y_04 = filter_nans(db, ["X_04", "Y_04"]) x_10, y_10 = filter_nans(db, ["X_10", "Y_10"]) x_15, y_15 = filter_nans(db, ["X_15", "Y_15"]) if ( (chord_ratio != np.clip(chord_ratio, min(x_0), max(x_0))) or (chord_ratio != np.clip(chord_ratio, min(x_04), max(x_04))) or (chord_ratio != np.clip(chord_ratio, min(x_10), max(x_10))) or (chord_ratio != np.clip(chord_ratio, min(x_15), max(x_15))) ): _LOGGER.warning( "Chord ratio value outside of the range in Roskam's book, value clipped" ) cld_t = [ float(np.interp(np.clip(chord_ratio, min(x_0), max(x_0)), x_0, y_0)), float(np.interp(np.clip(chord_ratio, min(x_04), max(x_04)), x_04, y_04)), float(np.interp(np.clip(chord_ratio, min(x_10), max(x_10)), x_10, y_10)), float(np.interp(np.clip(chord_ratio, min(x_15), max(x_15)), x_15, y_15)), ] if thickness != np.clip(thickness, 0.0, 0.15): _LOGGER.warning( "Thickness ratio value outside of the range in Roskam's book, value clipped" ) cl_delta_th = np.interp(np.clip(thickness, 0.0, 0.15), [0.0, 0.04, 0.1, 0.15], cld_t) return cl_delta_th
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k_cl_delta_plain_flap(thickness_ratio, airfoil_lift_coefficient, chord_ratio): """ Roskam data to estimate the correction factor to estimate difference from theoretical plain flap lift (figure 8.15). :param thickness_ratio: airfoil thickness ratio. :param airfoil_lift_coefficient: the lift coefficient of the airfoil, in rad**-1. :param chord_ratio: flap chord over wing chord ratio. :return k_cl_delta: correction factor to account for difference from theoretical plain flap lift. """ file = pth.join(resources.__path__[0], K_CL_DELTA_PLAIN_FLAP) db = pd.read_csv(file) # Figure 10.64 b cl_alpha_th = 6.3 + np.clip(thickness_ratio, 0.0, 0.2) / 0.2 * (7.3 - 6.3) k_cl_alpha_data = filter_nans(db, ["K_CL_ALPHA"])[0] k_cl_delta_min_data = filter_nans(db, ["K_CL_DELTA_MIN"])[0] k_cl_delta_max_data = filter_nans(db, ["K_CL_DELTA_MAX"])[0] if float(airfoil_lift_coefficient / cl_alpha_th) != np.clip( float(airfoil_lift_coefficient / cl_alpha_th), min(k_cl_alpha_data), max(k_cl_alpha_data), ): _LOGGER.warning( "Airfoil lift slope ratio value outside of the range in Roskam's book, " "value clipped" ) k_cl_alpha = np.clip( float(airfoil_lift_coefficient / cl_alpha_th), min(k_cl_alpha_data), max(k_cl_alpha_data), ) k_cl_delta_min = np.interp(k_cl_alpha, k_cl_alpha_data, k_cl_delta_min_data) k_cl_delta_max = np.interp(k_cl_alpha, k_cl_alpha_data, k_cl_delta_max_data) if chord_ratio != np.clip(chord_ratio, 0.05, 0.5): _LOGGER.warning( "Chord ratio value outside of the range in Roskam's book, value clipped" ) chord_ratio = np.clip(chord_ratio, 0.05, 0.5) k_cl_delta = np.interp(chord_ratio, [0.05, 0.5], [k_cl_delta_min, k_cl_delta_max]) return k_cl_delta
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k_prime_single_slotted(flap_angle, chord_ratio): """ Roskam data to estimate the lift effectiveness of a single slotted flap (figure 8.17), noted here k_prime to match the notation of the plain flap but is written alpha_delta in the book. :param flap_angle: the control surface deflection angle (in °). :param chord_ratio: control surface chord over lifting surface chord ratio. :return k_prime: lift effectiveness factor of a single slotted flap. """ file = pth.join(resources.__path__[0], K_SINGLE_SLOT) db = pd.read_csv(file) x_15, y_15 = filter_nans(db, ["X_15", "Y_15"]) x_20, y_20 = filter_nans(db, ["X_20", "Y_20"]) x_25, y_25 = filter_nans(db, ["X_25", "Y_25"]) x_30, y_30 = filter_nans(db, ["X_30", "Y_30"]) x_40, y_40 = filter_nans(db, ["X_40", "Y_40"]) if ( (float(flap_angle) != np.clip(float(flap_angle), min(x_15), max(x_15))) or (float(flap_angle) != np.clip(float(flap_angle), min(x_20), max(x_20))) or (float(flap_angle) != np.clip(float(flap_angle), min(x_25), max(x_25))) or (float(flap_angle) != np.clip(float(flap_angle), min(x_30), max(x_30))) or (float(flap_angle) != np.clip(float(flap_angle), min(x_40), max(x_40))) ): _LOGGER.warning("Flap angle value outside of the range in Roskam's book, value clipped") k_chord = [ np.interp(np.clip(float(flap_angle), min(x_15), max(x_15)), x_15, y_15), np.interp(np.clip(float(flap_angle), min(x_20), max(x_15)), x_20, y_20), np.interp(np.clip(float(flap_angle), min(x_25), max(x_25)), x_25, y_25), np.interp(np.clip(float(flap_angle), min(x_30), max(x_30)), x_30, y_30), np.interp(np.clip(float(flap_angle), min(x_40), max(x_40)), x_40, y_40), ] if float(chord_ratio) != np.clip(float(chord_ratio), 0.15, 0.4): _LOGGER.warning( "Chord ratio value outside of the range in Roskam's book, value clipped" ) k_prime = float( np.interp(np.clip(float(chord_ratio), 0.15, 0.4), [0.15, 0.20, 0.25, 0.3, 0.4], k_chord) ) return k_prime
[docs] @staticmethod @functools.lru_cache(maxsize=128) def base_max_lift_increment(thickness_ratio: float, flap_type: float) -> float: """ Roskam data to estimate base lift increment used in the computation of flap delta_cl_max (figure 8.31). :param thickness_ratio: thickness ratio f the lifting surface, in %. :param flap_type: type of flap used as described in Roskam, for now can be 0.0 for plain, 1.0 for single_slot. :return: delta_cl_base. """ file = pth.join(resources.__path__[0], BASE_INCREMENT_CL_MAX) db = pd.read_csv(file) x_plain, y_plain = filter_nans(db, ["X_PLAIN_FLAP", "Y_PLAIN_FLAP"]) x_single_slot, y_single_slot = filter_nans(db, ["X_SINGLE_SLOT", "Y_SINGLE_SLOT"]) if flap_type == 0.0: if thickness_ratio != np.clip(thickness_ratio, min(x_plain), max(x_plain)): _LOGGER.warning( "Thickness ratio value outside of the range in Roskam's book, value clipped" ) delta_cl_max_base = float( np.interp(np.clip(thickness_ratio, min(x_plain), max(x_plain)), x_plain, y_plain) ) elif flap_type == 1.0: if thickness_ratio != np.clip(thickness_ratio, min(x_single_slot), max(x_single_slot)): _LOGGER.warning( "Thickness ratio value outside of the range in Roskam's book, value clipped" ) delta_cl_max_base = float( np.interp( np.clip(thickness_ratio, min(x_single_slot), max(x_single_slot)), x_single_slot, y_single_slot, ) ) else: _LOGGER.warning("Flap type not recognized, used plain flap instead") if thickness_ratio != np.clip(thickness_ratio, min(x_plain), max(x_plain)): _LOGGER.warning( "Thickness ratio value outside of the range in Roskam's book, value clipped" ) delta_cl_max_base = float( np.interp(np.clip(thickness_ratio, min(x_plain), max(x_plain)), x_plain, y_plain) ) return delta_cl_max_base
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k1_max_lift(chord_ratio, flap_type) -> float: """ Roskam data to correct the base lift increment to account for chord ratio difference wrt to the reference flap configuration (figure 8.32). :param chord_ratio: ration of the chord of the control surface over that of the whole surface, in %. :param flap_type: type of flap used as described in Roskam, for now can be 0.0 for plain, 1.0 for single_slot. :return k1: correction factor to account for chord ratio difference wrt the reference configuration. """ file = pth.join(resources.__path__[0], K1) db = pd.read_csv(file) if flap_type == 1.0 or flap_type == 0.0: x, y = filter_nans(db, ["X_PLAIN_SINGLE_SPLIT", "Y_PLAIN_SINGLE_SPLIT"]) else: _LOGGER.warning("Flap type not recognized, used plain flap instead") x, y = filter_nans(db, ["X_PLAIN_SINGLE_SPLIT", "Y_PLAIN_SINGLE_SPLIT"]) if float(chord_ratio) != np.clip(float(chord_ratio), min(x), max(x)): _LOGGER.warning( "Chord ratio value outside of the range in Roskam's book, value clipped" ) k1 = float(np.interp(np.clip(float(chord_ratio), min(x), max(x)), x, y)) return k1
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k2_max_lift(angle, flap_type) -> float: """ Roskam data to correct the base lift increment to account for the control surface deflection angle difference wrt to the reference flap configuration (figure 8.33). :param angle: control surface deflection angle, in °. :param flap_type: type of flap used as described in Roskam, for now can be 0.0 for plain, 1.0 for single_slot. :return k2: correction factor to account for the control surface deflection angle wrt the reference configuration. """ file = pth.join(resources.__path__[0], K2) db = pd.read_csv(file) x_plain, y_plain = filter_nans(db, ["X_PLAIN_FLAP", "Y_PLAIN_FLAP"]) x_single_slot, y_single_slot = filter_nans(db, ["X_SINGLE_SLOT", "Y_SINGLE_SLOT"]) if flap_type == 0.0: if angle != np.clip(angle, min(x_plain), max(x_plain)): _LOGGER.warning( "Control surface deflection value outside of the range in Roskam's book, " "value clipped" ) k2 = float(np.interp(np.clip(angle, min(x_plain), max(x_plain)), x_plain, y_plain)) elif flap_type == 1.0: if angle != np.clip(angle, min(x_single_slot), max(x_single_slot)): _LOGGER.warning( "Control surface deflection value outside of the range in Roskam's book, " "value clipped" ) k2 = float( np.interp( np.clip(angle, min(x_single_slot), max(x_single_slot)), x_single_slot, y_single_slot, ) ) else: _LOGGER.warning("Flap type not recognized, used plain flap instead") if angle != np.clip(angle, min(x_plain), max(x_plain)): _LOGGER.warning( "Control surface deflection value outside of the range in Roskam's book, " "value clipped" ) k2 = float(np.interp(np.clip(angle, min(x_plain), max(x_plain)), x_plain, y_plain)) return k2
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k3_max_lift(angle, flap_type) -> float: """ Roskam data for flap motion correction factor (figure 8.34). :param angle: control surface deflection angle, in °. :param flap_type: type of flap used as described in Roskam, for now can be 0.0 for plain, 1.0 for single_slot. :return k3: correction factor to account flap motion correction. """ file = pth.join(resources.__path__[0], K3) db = pd.read_csv(file) if flap_type == 0.0: k3 = 1.0 elif flap_type == 1.0: x, y = filter_nans(db, ["X_SINGLE_SLOT", "Y_SINGLE_SLOT"]) reference_angle = 45.0 if float(angle / reference_angle) != np.clip( float(angle / reference_angle), min(x), max(x) ): _LOGGER.warning( "Control surface deflection value outside of the range in Roskam's book, " "value clipped, reference value is %f", reference_angle, ) k3 = float(np.interp(np.clip(float(angle / reference_angle), min(x), max(x)), x, y)) else: _LOGGER.warning("Flap type not recognized, used plain flap instead") k3 = 1.0 return k3
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k_b_flaps(eta_in: float, eta_out: float, taper_ratio: float) -> float: """ Roskam data to estimate the flap span factor Kb (figure 8.52) This factor accounts for a finite flap contribution to the 3D lift increase, depending on its position and size and the taper ratio of the wing. :param eta_in: position along the wing span of the start of the flaps divided by span. :param eta_out: position along the wing span of the end of the flaps divided by span. :param taper_ratio: taper ration of the surface. :return: kb factor contribution to 3D lift. """ eta_in = float(eta_in) eta_out = float(eta_out) if taper_ratio != np.clip(taper_ratio, 0.0, 1.0): _LOGGER.warning( "Taper ratio value outside of the range in Roskam's book, value clipped" ) taper_ratio = np.clip(taper_ratio, 0.0, 1.0) file = pth.join(resources.__path__[0], KB_FLAPS) db = pd.read_csv(file) x_0, y_0 = filter_nans(db, ["X_0", "Y_0"]) x_05, y_05 = filter_nans(db, ["X_0.5", "Y_0.5"]) x_1, y_1 = filter_nans(db, ["X_1", "Y_1"]) if ( (eta_in != np.clip(eta_in, min(x_0), max(x_0))) or (eta_in != np.clip(eta_in, min(x_05), max(x_05))) or (eta_in != np.clip(eta_in, min(x_1), max(x_1))) ): _LOGGER.warning( "Flap inward position ratio value outside of the range in Roskam's book, " "value clipped" ) k_eta = [ float(np.interp(np.clip(eta_in, min(x_0), max(x_0)), x_0, y_0)), float(np.interp(np.clip(eta_in, min(x_05), max(x_05)), x_05, y_05)), float(np.interp(np.clip(eta_in, min(x_1), max(x_1)), x_1, y_1)), ] kb_in = np.interp(taper_ratio, [0.0, 0.5, 1.0], k_eta) if ( (eta_out != np.clip(eta_out, min(x_0), max(x_0))) or (eta_out != np.clip(eta_out, min(x_05), max(x_05))) or (eta_out != np.clip(eta_out, min(x_1), max(x_1))) ): _LOGGER.warning( "Flap inward position ratio value outside of the range in Roskam's book, " "value clipped" ) k_eta = [ float(np.interp(np.clip(eta_out, min(x_0), max(x_0)), x_0, y_0)), float(np.interp(np.clip(eta_out, min(x_05), max(x_05)), x_05, y_05)), float(np.interp(np.clip(eta_out, min(x_1), max(x_1)), x_1, y_1)), ] kb_out = np.interp(taper_ratio, [0.0, 0.5, 1.0], k_eta) return float(kb_out - kb_in)
[docs] @staticmethod @functools.lru_cache(maxsize=128) def a_delta_airfoil(chord_ratio) -> float: """ Roskam data to estimate the two-dimensional flap effectiveness factor (figure 8.53a) This factor can be then used in the computation of the 3D flap effectiveness factor which is often the coefficient of interest. :param chord_ratio: ration of the chord of the control surface over that of the whole surface. :return: kb factor contribution to 3D lift. """ file = pth.join(resources.__path__[0], A_DELTA_AIRFOIL) db = pd.read_csv(file) a_delta = interpolate_database(db, "X", "Y", chord_ratio) if chord_ratio != np.clip(chord_ratio, 0.0, 1.0): _LOGGER.warning( "Chord ratio value outside of the range in Roskam's book, value clipped" ) return a_delta
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k_a_delta(a_delta_airfoil, aspect_ratio) -> float: """ Roskam data to estimate the two-dimensional to three-dimensional control surface lift effectiveness parameter (figure 8.53b). :param a_delta_airfoil: control surface two-dimensional flap effectiveness factor. :param aspect_ratio: aspect ratio of the fixed surface. :return k_a_delta: two-dimensional to three-dimensional control surface lift effectiveness parameter. """ file = pth.join(resources.__path__[0], K_A_DELTA) db = pd.read_csv(file) if float(aspect_ratio) != np.clip(float(aspect_ratio), 0.0, 10.0): _LOGGER.warning( "Aspect ratio value outside of the range in Roskam's book, value clipped" ) y1 = interpolate_database(db, "X_01", "Y_01", aspect_ratio) y2 = interpolate_database(db, "X_02", "Y_02", aspect_ratio) y3 = interpolate_database(db, "X_03", "Y_03", aspect_ratio) y4 = interpolate_database(db, "X_04", "Y_04", aspect_ratio) y5 = interpolate_database(db, "X_05", "Y_05", aspect_ratio) y6 = interpolate_database(db, "X_06", "Y_06", aspect_ratio) y7 = interpolate_database(db, "X_07", "Y_07", aspect_ratio) y8 = interpolate_database(db, "X_08", "Y_08", aspect_ratio) y9 = interpolate_database(db, "X_09", "Y_09", aspect_ratio) y10 = interpolate_database(db, "X_10", "Y_10", aspect_ratio) x = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] y = [y1, y2, y3, y4, y5, y6, y7, y8, y9, y10] if a_delta_airfoil != np.clip(a_delta_airfoil, 0.0, 1.0): _LOGGER.warning( "Control surface effectiveness ratio value outside of the range in " "Roskam's book, value clipped" ) k_a_delta = float(np.interp(np.clip(a_delta_airfoil, 0.1, 1.0), x, y)) return k_a_delta
[docs] @staticmethod @functools.lru_cache(maxsize=128) def x_cp_c_prime(flap_chord_ratio: float) -> float: """ Roskam data to estimate the location of the center of pressure due to Incremental Flap Load (figure 8.91). :param flap_chord_ratio: ratio of the control surface chord over the lifting surface chord. :return x_cp_c_prime: location of center of pressure due to flap deployment. """ # Graph is simple so no csv is read, rather, a direct formula is used. if flap_chord_ratio != np.clip(flap_chord_ratio, 0.0, 1.0): _LOGGER.warning("Chord ratio outside of the range in Roskam's book, value clipped") x_cp_c_prime = float( np.interp(np.clip(flap_chord_ratio, 0.0, 1.0), [0.0, 1.0], [0.5, 0.25]) ) return x_cp_c_prime
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k_p_flaps(taper_ratio, eta_in, eta_out) -> float: """ Roskam data to account for the partial span flaps factor on the pitch moment coefficient (figure 8.105). :param taper_ratio: lifting surface taper ratio. :param eta_in: start of the control surface, in percent of the lifting surface span. :param eta_out: end of the control surface, in percent of the lifting surface span. :return k_p: partial span factor. """ file = pth.join(resources.__path__[0], K_P_FLAPS) db = pd.read_csv(file) eta_in_1_0 = interpolate_database(db, "taper_1_0_X", "taper_1_0_Y", eta_in) eta_out_1_0 = interpolate_database(db, "taper_1_0_X", "taper_1_0_Y", eta_out) eta_in_0_5 = interpolate_database(db, "taper_0_5_X", "taper_0_5_Y", eta_in) eta_out_0_5 = interpolate_database(db, "taper_0_5_X", "taper_0_5_Y", eta_out) eta_in_0_333 = interpolate_database(db, "taper_0_333_X", "taper_0_333_Y", eta_in) eta_out_0_333 = interpolate_database(db, "taper_0_333_X", "taper_0_333_Y", eta_out) eta_in_0_25 = interpolate_database(db, "taper_0_25_X", "taper_0_25_Y", eta_in) eta_out_0_25 = interpolate_database(db, "taper_0_25_X", "taper_0_25_Y", eta_out) taper_array = [0.25, 0.333, 0.5, 1.0] eta_in_array = [eta_in_0_25, eta_in_0_333, eta_in_0_5, eta_in_1_0] eta_out_array = [eta_out_0_25, eta_out_0_333, eta_out_0_5, eta_out_1_0] if taper_ratio != np.clip(taper_ratio, 0.25, 1.0): _LOGGER.warning("Taper ratio outside of the range in Roskam's book, value clipped") k_p = float( np.interp(np.clip(taper_ratio, 0.25, 1.0), taper_array, eta_out_array) - np.interp(np.clip(taper_ratio, 0.25, 1.0), taper_array, eta_in_array) ) return k_p
[docs] @staticmethod @functools.lru_cache(maxsize=128) def pitch_to_reference_lift(thickness_ratio: float, chord_ratio: float) -> float: """ Roskam data to account for the ratio between the pitch moment coefficient and the reference lift coefficient increment (figure 8.106). :param thickness_ratio: thickness to chord ratio of the lifting surface. :param chord_ratio: chord ratio of the control surface over the lifting surface. :return delta_cm_delta_cl_ref: ration between the pitching moment and the reference lift coefficient. """ if chord_ratio != np.clip(chord_ratio, 0.05, 0.4): _LOGGER.warning("Chord ratio outside of the range in Roskam's book, value clipped") file = pth.join(resources.__path__[0], DELTA_CM_DELTA_CL_REF) db = pd.read_csv(file) k_21 = interpolate_database(db, "TOC_21_X", "TOC_21_Y", chord_ratio) k_18 = interpolate_database(db, "TOC_18_X", "TOC_18_Y", chord_ratio) k_15 = interpolate_database(db, "TOC_15_X", "TOC_15_Y", chord_ratio) k_12 = interpolate_database(db, "TOC_12_X", "TOC_12_Y", chord_ratio) k_09 = interpolate_database(db, "TOC_09_X", "TOC_09_Y", chord_ratio) k_06 = interpolate_database(db, "TOC_06_X", "TOC_06_Y", chord_ratio) k_03 = interpolate_database(db, "TOC_03_X", "TOC_03_Y", chord_ratio) toc_array = [0.03, 0.06, 0.09, 0.12, 0.15, 0.18, 0.21] k_array = [k_03, k_06, k_09, k_12, k_15, k_18, k_21] if thickness_ratio != np.clip(thickness_ratio, 0.03, 0.4): _LOGGER.warning( "Thickness to chord ratio outside of the range in Roskam's book, " "value clipped" ) k = float(np.interp(np.clip(thickness_ratio, 0.03, 0.4), toc_array, k_array)) return k
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k_delta_flaps(taper_ratio: float, eta_in: float, eta_out: float) -> float: """ Roskam data to estimate the conversion factor which accounts for partial span flaps on a swept wing (c_prime/c = 1.0) (figure 8.107). :param taper_ratio: lifting surface taper ratio. :param eta_in: start of the control surface, in percent of the lifting surface span. :param eta_out: end of the control surface, in percent of the lifting surface span. :return delta_k: partial span factor. """ file = pth.join(resources.__path__[0], K_DELTA) db = pd.read_csv(file) eta_in_1_0 = interpolate_database(db, "X_1_0", "Y_1_0", eta_in) eta_in_0_5 = interpolate_database(db, "X_0_5", "Y_0_5", eta_in) eta_in_0_333 = interpolate_database(db, "X_0_333", "Y_0_333", eta_in) eta_in_0_2 = interpolate_database(db, "X_0_2", "Y_0_2", eta_in) eta_out_1_0 = interpolate_database(db, "X_1_0", "Y_1_0", eta_out) eta_out_0_5 = interpolate_database(db, "X_0_5", "Y_0_5", eta_out) eta_out_0_333 = interpolate_database(db, "X_0_333", "Y_0_333", eta_out) eta_out_0_2 = interpolate_database(db, "X_0_2", "Y_0_2", eta_out) taper_array = [0.2, 0.333, 0.5, 1.0] eta_in_array = [eta_in_0_2, eta_in_0_333, eta_in_0_5, eta_in_1_0] eta_out_array = [eta_out_0_2, eta_out_0_333, eta_out_0_5, eta_out_1_0] if taper_ratio != np.clip(taper_ratio, 0.2, 1.0): _LOGGER.warning("Taper ratio outside of the range in Roskam's book, value clipped") k_delta_in = np.interp(np.clip(taper_ratio, 0.2, 1.0), taper_array, eta_in_array) k_delta_out = np.interp(np.clip(taper_ratio, 0.2, 1.0), taper_array, eta_out_array) k_delta = float(k_delta_out - k_delta_in) return k_delta
[docs] @staticmethod def k_ar_fuselage(taper_ratio, span, avg_fuselage_depth) -> float: """ Roskam data to account for the effect of the fuselage on the VTP effective aspect ratio ( figure 10.14). :param taper_ratio: lifting surface taper ratio. :param span: lifting surface span, in m. :param avg_fuselage_depth: average fuselage depth (diameter if fuselage considered circular), in m. :return k_ar_fuselage: correction factor to account for the end plate effect of the fuselage on effective VTP AR. """ file = pth.join(resources.__path__[0], K_AR_FUSELAGE) db = pd.read_csv(file) x_06, y_06 = filter_nans(db, ["X_06", "Y_06"]) x_10, y_10 = filter_nans(db, ["X_10", "Y_10"]) x_value = span / avg_fuselage_depth if x_value != np.clip(x_value, min(min(x_06), min(x_10)), max(max(x_06), max(x_10))): _LOGGER.warning( "Ratio of span on fuselage depth outside of the range in Roskam's book, " "value clipped" ) y_value_06 = np.interp(np.clip(x_value, min(x_06), max(x_06)), x_06, y_06) y_value_10 = np.interp(np.clip(x_value, min(x_10), max(x_10)), x_10, y_10) if taper_ratio != np.clip(taper_ratio, 0.6, 1.0): _LOGGER.warning("Taper ratio outside of the range in Roskam's book, value clipped") k_ar_fuselage = float( np.interp( np.clip(taper_ratio, 0.6, 1.0), [0.6, 1.0], [float(y_value_06), float(y_value_10)] ) ) return k_ar_fuselage
[docs] @staticmethod def k_vh(area_ratio) -> float: """ Roskam data to estimate the impact of relative area ratio on the effective aspect ratio ( figure 10.16). :param area_ratio: ratio of the horizontal tail area over the vertical tail area. :return k_vh: impact of area ratio on effective aspect ratio. """ file = pth.join(resources.__path__[0], K_VH) db = pd.read_csv(file) x, y = filter_nans(db, ["X", "Y"]) if float(area_ratio) != np.clip(float(area_ratio), min(x), max(x)): _LOGGER.warning("Area ratio value outside of the range in Roskam's book, value clipped") k_vh = float(np.interp(np.clip(float(area_ratio), min(x), max(x)), x, y)) return k_vh
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k_ch_alpha(thickness_ratio, airfoil_lift_coefficient, chord_ratio): """ Roskam data to compute the correction factor to differentiate the 2D control surface hinge moment derivative. due to AOA from the reference (figure 10.63). :param thickness_ratio: airfoil thickness ratio. :param airfoil_lift_coefficient: the lift coefficient of the airfoil, in rad**-1. :param chord_ratio: flap chord over wing chord ratio. :return k_ch_alpha: correction factor for 2D control surface hinge moment derivative due to AOA. """ file = pth.join(resources.__path__[0], K_CH_ALPHA) db = pd.read_csv(file) # Figure 10.64 b if thickness_ratio != np.clip(thickness_ratio, 0.0, 0.2): _LOGGER.warning( "Thickness ratio value outside of the range in Roskam's book, value clipped" ) cl_alpha_th = 6.3 + np.clip(thickness_ratio, 0.0, 0.2) / 0.2 * (7.3 - 6.3) k_cl_alpha_data = filter_nans(db, ["K_CL_ALPHA"])[0] k_ch_alpha_min_data = filter_nans(db, ["K_CH_ALPHA_MIN"])[0] k_ch_alpha_max_data = filter_nans(db, ["K_CH_ALPHA_MAX"])[0] if float(airfoil_lift_coefficient / cl_alpha_th) != np.clip( float(airfoil_lift_coefficient / cl_alpha_th), min(k_cl_alpha_data), max(k_cl_alpha_data), ): _LOGGER.warning( "Airfoil lift coefficient to theoretical lift coefficient ratio value outside of " "the range in Roskam's book, value clipped" ) k_cl_alpha = np.clip( float(airfoil_lift_coefficient / cl_alpha_th), min(k_cl_alpha_data), max(k_cl_alpha_data), ) k_ch_alpha_min = np.interp(k_cl_alpha, k_cl_alpha_data, k_ch_alpha_min_data) k_ch_alpha_max = np.interp(k_cl_alpha, k_cl_alpha_data, k_ch_alpha_max_data) chord_ratio = np.clip(chord_ratio, 0.1, 0.4) k_ch_alpha = float(np.interp(chord_ratio, [0.1, 0.4], [k_ch_alpha_min, k_ch_alpha_max])) return k_ch_alpha
[docs] @staticmethod @functools.lru_cache(maxsize=128) def ch_alpha_th(thickness_ratio, chord_ratio): """ Roskam data to compute the theoretical 2D control surface hinge moment derivative due to AOA (figure 10.63). :param thickness_ratio: airfoil thickness ratio. :param chord_ratio: flap chord over wing chord ratio. :return ch_alpha: theoretical hinge moment derivative due to AOA. """ file = pth.join(resources.__path__[0], CH_ALPHA_TH) db = pd.read_csv(file) thickness_ratio_data = filter_nans(db, ["THICKNESS_RATIO"])[0] ch_alpha_min_data = filter_nans(db, ["CH_ALPHA_MIN"])[0] ch_alpha_max_data = filter_nans(db, ["CH_ALPHA_MAX"])[0] if float(thickness_ratio) != np.clip( float(thickness_ratio), min(thickness_ratio_data), max(thickness_ratio_data) ): _LOGGER.warning( "Thickness ratio value outside of the range in Roskam's book, value clipped" ) ch_alpha_min = np.interp( np.clip(float(thickness_ratio), min(thickness_ratio_data), max(thickness_ratio_data)), thickness_ratio_data, ch_alpha_min_data, ) ch_alpha_max = np.interp( np.clip(float(thickness_ratio), min(thickness_ratio_data), max(thickness_ratio_data)), thickness_ratio_data, ch_alpha_max_data, ) if chord_ratio != np.clip(chord_ratio, 0.1, 0.4): _LOGGER.warning( "Chord ratio value outside of the range in Roskam's book, value clipped" ) chord_ratio = np.clip(chord_ratio, 0.1, 0.4) ch_alpha_th = float(np.interp(chord_ratio, [0.1, 0.4], [ch_alpha_min, ch_alpha_max])) return ch_alpha_th
[docs] @staticmethod @functools.lru_cache(maxsize=128) def k_ch_delta(thickness_ratio, airfoil_lift_coefficient, chord_ratio): """ Roskam data to compute the correction factor to differentiate the 2D control surface hinge moment derivative due to control surface deflection from the reference (figure 10.69 a). :param thickness_ratio: airfoil thickness ratio. :param airfoil_lift_coefficient: the lift coefficient of the airfoil, in rad**-1. :param chord_ratio: control surface chord over lifting surface chord ratio. :return k_ch_delta: hinge moment derivative due to control surface deflection correction factor. """ file = pth.join(resources.__path__[0], K_CH_DELTA) db = pd.read_csv(file) # Figure 10.64 b if thickness_ratio != np.clip(thickness_ratio, 0.0, 0.2): _LOGGER.warning( "Thickness ratio value outside of the range in Roskam's book, value clipped" ) cl_alpha_th = 6.3 + np.clip(thickness_ratio, 0.0, 0.2) / 0.2 * (7.3 - 6.3) k_cl_alpha_data = filter_nans(db, ["K_CL_ALPHA"])[0] k_ch_delta_min_data = filter_nans(db, ["K_CH_DELTA_MIN"])[0] k_ch_delta_avg_data = filter_nans(db, ["K_CH_DELTA_AVG"])[0] k_ch_delta_max_data = filter_nans(db, ["K_CH_DELTA_MAX"])[0] if float(airfoil_lift_coefficient / cl_alpha_th) != np.clip( float(airfoil_lift_coefficient / cl_alpha_th), min(k_cl_alpha_data), max(k_cl_alpha_data), ): _LOGGER.warning( "Airfoil lift coefficient to theoretical lift coefficient ratio value outside of " "the range in Roskam's book, value clipped" ) k_cl_alpha = np.clip( float(airfoil_lift_coefficient / cl_alpha_th), min(k_cl_alpha_data), max(k_cl_alpha_data), ) k_ch_delta_min = float(np.interp(k_cl_alpha, k_cl_alpha_data, k_ch_delta_min_data)) k_ch_delta_avg = float(np.interp(k_cl_alpha, k_cl_alpha_data, k_ch_delta_avg_data)) k_ch_delta_max = float(np.interp(k_cl_alpha, k_cl_alpha_data, k_ch_delta_max_data)) if chord_ratio != np.clip(chord_ratio, 0.1, 0.4): _LOGGER.warning( "Chord ratio value outside of the range in Roskam's book, value clipped" ) chord_ratio = np.clip(chord_ratio, 0.1, 0.4) k_ch_delta = float( np.interp( chord_ratio, [0.1, 0.25, 0.4], [k_ch_delta_min, k_ch_delta_avg, k_ch_delta_max] ) ) return k_ch_delta
[docs] @staticmethod @functools.lru_cache(maxsize=128) def ch_delta_th(thickness_ratio, chord_ratio): """ Roskam data to compute the theoretical 2D control surface hinge moment derivative due to control surface deflection (figure 10.69 b). :param thickness_ratio: airfoil thickness ratio. :param chord_ratio: flap chord over wing chord ratio. :return ch_delta: theoretical hinge moment derivative due to control surface deflection. """ file = pth.join(resources.__path__[0], CH_DELTA_TH) db = pd.read_csv(file) thickness_ratio_data = filter_nans(db, ["THICKNESS_RATIO"])[0] if float(thickness_ratio) != np.clip( float(thickness_ratio), min(thickness_ratio_data), max(thickness_ratio_data) ): _LOGGER.warning( "Thickness ratio value outside of the range in Roskam's book, value clipped" ) ch_delta_min = interpolate_database(db, "THICKNESS_RATIO", "CH_DELTA_MIN", thickness_ratio) ch_delta_max = interpolate_database(db, "THICKNESS_RATIO", "CH_DELTA_MAX", thickness_ratio) if chord_ratio != np.clip(chord_ratio, 0.1, 0.4): _LOGGER.warning( "Chord ratio value outside of the range in Roskam's book, value clipped" ) chord_ratio = np.clip(chord_ratio, 0.1, 0.4) ch_delta_th = float(np.interp(chord_ratio, [0.1, 0.4], [ch_delta_min, ch_delta_max])) return ch_delta_th
[docs] @staticmethod def k_fus(root_quarter_chord_position_ratio) -> float: """ Roskam data to estimate the empirical pitching moment factor K_fus (figure 16.14). :param root_quarter_chord_position_ratio: the position of the root quarter chord of the wing from the nose. divided by the total length of the fuselage. :return k_fus: the empirical pitching moment factor. """ file = pth.join(resources.__path__[0], K_FUS) db = pd.read_csv(file) x, y = filter_nans(db, ["X_0_25_RATIO", "K_FUS"]) if float(root_quarter_chord_position_ratio) != np.clip( float(root_quarter_chord_position_ratio), min(x), max(x) ): _LOGGER.warning( "Position of the root quarter-chord as percent of fuselage length is outside of " "the range in Roskam's book, value clipped" ) k_fus = float(np.interp(np.clip(root_quarter_chord_position_ratio, min(x), max(x)), x, y)) return k_fus
[docs] @staticmethod def cl_beta_sweep_contribution(taper_ratio, aspect_ratio, sweep_50) -> float: """ Roskam data to estimate the contribution to the roll moment of the sweep angle of the lifting surface. (figure 10.20) :param taper_ratio: the taper ratio of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :param sweep_50: the sweep angle at 50 percent of the chord of the lifting surface, in deg :return cl_beta_lambda: the contribution to the roll moment of the sweep angle of the lifting surface. """ file = pth.join(resources.__path__[0], CL_BETA_SWEEP) db = pd.read_csv(file) taper_ratio_data, aspect_ratio_data, sweep_50_data, sweep_contribution = filter_nans( db, ["TAPER_RATIO", "ASPECT_RATIO", "SWEEP_50", "SWEEP_CONTRIBUTION"] ) if float(taper_ratio) != np.clip( float(taper_ratio), min(taper_ratio_data), max(taper_ratio_data) ): _LOGGER.warning("Taper ratio is outside of the range in Roskam's book, value clipped") if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") if float(sweep_50) != np.clip(float(sweep_50), min(sweep_50_data), max(sweep_50_data)): _LOGGER.warning( "Sweep at 50% chord is outside of the range in Roskam's book, " "value clipped" ) # Linear interpolation is preferred, but we put the nearest one as protection cl_beta_lambda = interpolate.griddata( (taper_ratio_data, aspect_ratio_data, sweep_50_data), sweep_contribution, np.array([taper_ratio, aspect_ratio, sweep_50]).T, method="linear", ) if np.isnan(cl_beta_lambda): cl_beta_lambda = interpolate.griddata( (taper_ratio_data, aspect_ratio_data, sweep_50_data), sweep_contribution, np.array([taper_ratio, aspect_ratio, sweep_50]).T, method="nearest", ) return float(cl_beta_lambda)
[docs] @staticmethod def cl_beta_sweep_compressibility_correction(swept_aspect_ratio, swept_mach) -> float: """ Roskam data to estimate the compressibility correction for the sweep angle. (figure 10.21) :param swept_aspect_ratio: the aspect ratio of the lifting surface divided by cos(sweep_50) :param swept_mach: mach number multiplied by cos(sweep_50) :return k_m_lambda: compressibility correction for the sweep angle. """ file = pth.join(resources.__path__[0], K_M_LAMBDA) db = pd.read_csv(file) swept_aspect_ratio_data, swept_mach_data, k_m_lambda_data = filter_nans( db, ["AR_SWEPT", "M_SWEPT", "SWEEP_COMPRESSIBILITY_CORRECTION"] ) if float(swept_aspect_ratio) != np.clip( float(swept_aspect_ratio), min(swept_aspect_ratio_data), max(swept_aspect_ratio_data) ): _LOGGER.warning( "Swept aspect ratio is outside of the range in Roskam's book, value clipped" ) if float(swept_mach) != np.clip( float(swept_mach), min(swept_mach_data), max(swept_mach_data) ): _LOGGER.warning( "Swept mach number is outside of the range in Roskam's book, value clipped" ) k_m_lambda = interpolate.griddata( (swept_aspect_ratio_data, swept_mach_data), k_m_lambda_data, np.array([swept_aspect_ratio, swept_mach]).T, method="linear", ) if np.isnan(k_m_lambda): k_m_lambda = interpolate.griddata( (swept_aspect_ratio_data, swept_mach_data), k_m_lambda_data, np.array([swept_aspect_ratio, swept_mach]).T, method="nearest", ) return float(k_m_lambda)
[docs] @staticmethod def cl_beta_fuselage_correction(swept_aspect_ratio, lf_to_b_ratio) -> float: """ Roskam data to estimate the fuselage correction factor. (figure 10.22) :param swept_aspect_ratio: the aspect ratio of the lifting surface divided by cos(sweep_50) :param lf_to_b_ratio: ratio between the distance from nose to root half chord and the wing span :return k_fuselage: fuselage correction factor. """ file = pth.join(resources.__path__[0], K_FUSELAGE) db = pd.read_csv(file) swept_aspect_ratio_data, lf_to_b_data, k_fuselage_data = filter_nans( db, ["AR_SWEPT", "LF_TO_B_RATIO", "K_FUSELAGE"] ) if float(swept_aspect_ratio) != np.clip( float(swept_aspect_ratio), min(swept_aspect_ratio_data), max(swept_aspect_ratio_data) ): _LOGGER.warning( "Swept aspect ratio is outside of the range in Roskam's book, value clipped" ) if float(lf_to_b_ratio) != np.clip( float(lf_to_b_ratio), min(lf_to_b_data), max(lf_to_b_data) ): _LOGGER.warning( "Ratio between the distance from nose to root half chord and the wing span is " "outside of the range in Roskam's book, value clipped" ) k_fuselage = interpolate.griddata( (swept_aspect_ratio_data, lf_to_b_data), k_fuselage_data, np.array([swept_aspect_ratio, lf_to_b_ratio]).T, method="linear", ) if np.isnan(k_fuselage): k_fuselage = interpolate.griddata( (swept_aspect_ratio_data, lf_to_b_data), k_fuselage_data, np.array([swept_aspect_ratio, lf_to_b_ratio]).T, method="nearest", ) return float(k_fuselage)
[docs] @staticmethod def cl_beta_ar_contribution(taper_ratio, aspect_ratio) -> float: """ Roskam data to estimate the contribution to the roll moment of the aspect ratio of the lifting surface. (figure 10.23) :param taper_ratio: the taper ratio of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :return cl_beta_ar: the contribution to the roll moment of the aspect ratio of the lifting surface. """ file = pth.join(resources.__path__[0], CL_BETA_AR) db = pd.read_csv(file) taper_ratio_data, aspect_ratio_data, ar_contribution = filter_nans( db, ["TAPER_RATIO", "ASPECT_RATIO", "ASPECT_RATIO_CONTRIBUTION"] ) if float(taper_ratio) != np.clip( float(taper_ratio), min(taper_ratio_data), max(taper_ratio_data) ): _LOGGER.warning("Taper ratio is outside of the range in Roskam's book, value clipped") if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") # Linear interpolation is preferred, but we put the nearest one as protection cl_beta_ar = interpolate.griddata( (taper_ratio_data, aspect_ratio_data), ar_contribution, np.array([taper_ratio, aspect_ratio]).T, method="linear", ) if np.isnan(cl_beta_ar): cl_beta_ar = interpolate.griddata( (taper_ratio_data, aspect_ratio_data), ar_contribution, np.array([taper_ratio, aspect_ratio]).T, method="nearest", ) return float(cl_beta_ar)
[docs] @staticmethod def cl_beta_dihedral_contribution(taper_ratio, aspect_ratio, sweep_50) -> float: """ Roskam data to estimate the contribution to the roll moment of the dihedral angle of the lifting surface. (figure 10.24) :param taper_ratio: the taper ratio of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :param sweep_50: the sweep angle at 50 percent of the chord of the lifting surface, in deg :return cl_beta_gamma: the contribution to the roll moment of the dihedral angle of the lifting surface. """ # For this graph, only the absolute value of the sweep angle is necessary sweep_50 = np.abs(sweep_50) file = pth.join(resources.__path__[0], CL_BETA_GAMMA) db = pd.read_csv(file) taper_ratio_data, aspect_ratio_data, sweep_50_data, dihedral_contribution = filter_nans( db, ["TAPER_RATIO", "ASPECT_RATIO", "SWEEP_50", "DIHEDRAL_CONTRIBUTION"] ) if float(taper_ratio) != np.clip( float(taper_ratio), min(taper_ratio_data), max(taper_ratio_data) ): _LOGGER.warning("Taper ratio is outside of the range in Roskam's book, value clipped") if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") if float(sweep_50) != np.clip(float(sweep_50), min(sweep_50_data), max(sweep_50_data)): _LOGGER.warning( "Sweep at 50% chord is outside of the range in Roskam's book, " "value clipped" ) # Linear interpolation is preferred, but we put the nearest one as protection cl_beta_gamma = interpolate.griddata( (taper_ratio_data, aspect_ratio_data, sweep_50_data), dihedral_contribution, np.array([taper_ratio, aspect_ratio, sweep_50]).T, method="linear", ) if np.isnan(cl_beta_gamma): cl_beta_gamma = interpolate.griddata( (taper_ratio_data, aspect_ratio_data, sweep_50_data), dihedral_contribution, np.array([taper_ratio, aspect_ratio, sweep_50]).T, method="nearest", ) return float(cl_beta_gamma)
[docs] @staticmethod def cl_beta_dihedral_compressibility_correction(swept_aspect_ratio, swept_mach) -> float: """ Roskam data to estimate the compressibility correction for the dihedral angle. (figure 10.25) :param swept_aspect_ratio: the aspect ratio of the lifting surface divided by cos(sweep_50) :param swept_mach: mach number multiplied by cos(sweep_50) :return k_m_gamma: compressibility correction for the dihedral angle. """ file = pth.join(resources.__path__[0], K_M_GAMMA) db = pd.read_csv(file) swept_aspect_ratio_data, swept_mach_data, k_m_gamma_data = filter_nans( db, ["AR_SWEPT", "M_SWEPT", "DIHEDRAL_COMPRESSIBILITY_CORRECTION"] ) if float(swept_aspect_ratio) != np.clip( float(swept_aspect_ratio), min(swept_aspect_ratio_data), max(swept_aspect_ratio_data) ): _LOGGER.warning( "Swept aspect ratio is outside of the range in Roskam's book, value clipped" ) if float(swept_mach) != np.clip( float(swept_mach), min(swept_mach_data), max(swept_mach_data) ): _LOGGER.warning( "Swept mach number is outside of the range in Roskam's book, value clipped" ) k_m_gamma = interpolate.griddata( (swept_aspect_ratio_data, swept_mach_data), k_m_gamma_data, np.array([swept_aspect_ratio, swept_mach]).T, method="linear", ) if np.isnan(k_m_gamma): k_m_gamma = interpolate.griddata( (swept_aspect_ratio_data, swept_mach_data), k_m_gamma_data, np.array([swept_aspect_ratio, swept_mach]).T, method="nearest", ) return float(k_m_gamma)
[docs] @staticmethod def cl_beta_twist_correction(taper_ratio, aspect_ratio) -> float: """ Roskam data to estimate the correction due to the twist of the lifting surface. (figure 10.26) :param taper_ratio: the taper ratio of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :return k_epsilon: the factor to take into account the twist of the lifting surface for the computation of the rolling moment """ file = pth.join(resources.__path__[0], K_TWIST) db = pd.read_csv(file) taper_ratio_data, aspect_ratio_data, twist_correction = filter_nans( db, ["TAPER_RATIO", "ASPECT_RATIO", "TWIST_CORRECTION"] ) if float(taper_ratio) != np.clip( float(taper_ratio), min(taper_ratio_data), max(taper_ratio_data) ): _LOGGER.warning("Taper ratio is outside of the range in Roskam's book, value clipped") if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") # Linear interpolation is preferred, but we put the nearest one as protection k_epsilon = interpolate.griddata( (taper_ratio_data, aspect_ratio_data), twist_correction, np.array([taper_ratio, aspect_ratio]).T, method="linear", ) if np.isnan(k_epsilon): k_epsilon = interpolate.griddata( (taper_ratio_data, aspect_ratio_data), twist_correction, np.array([taper_ratio, aspect_ratio]).T, method="nearest", ) return float(k_epsilon)
[docs] @staticmethod def cl_p_roll_damping_parameter(taper_ratio, aspect_ratio, mach, sweep_25, k) -> float: """ Roskam data to estimate the contribution to the roll moment of the roll damping parameter (figure 10.35). :param taper_ratio: the taper ratio of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :param mach: the mach number :param sweep_25: the sweep angle at 25 percent of the chord of the lifting surface, in deg :param k: the ratio between the airfoil slope and 2*np.pi :return k_roll_damping: the roll damping parameter """ beta = np.sqrt(1.0 - mach**2.0) corrected_ar = aspect_ratio * beta / k corrected_sweep = np.arctan(np.tan(sweep_25) / beta) file = pth.join(resources.__path__[0], K_ROLL_DAMPING) db = pd.read_csv(file) taper_ratio_data, correct_ar_data, corrected_sweep_data, roll_damping_data = filter_nans( db, ["TAPER_RATIO", "CORRECTED_AR", "CORRECTED_SWEEP", "ROLL_DAMPING_PARAMETER"] ) if float(taper_ratio) != np.clip( float(taper_ratio), min(taper_ratio_data), max(taper_ratio_data) ): _LOGGER.warning("Taper ratio is outside of the range in Roskam's book, value clipped") if float(corrected_ar) != np.clip( float(corrected_ar), min(correct_ar_data), max(correct_ar_data) ): _LOGGER.warning( "Corrected Aspect ratio is outside of the range in Roskam's book, value clipped" ) if float(corrected_sweep) != np.clip( float(corrected_sweep), min(corrected_sweep_data), max(corrected_sweep_data) ): _LOGGER.warning( "Corrected Sweep is outside of the range in Roskam's book, value clipped" ) # Linear interpolation is preferred, but we put the nearest one as protection k_roll_damping = interpolate.griddata( (taper_ratio_data, correct_ar_data, corrected_sweep_data), roll_damping_data, np.array([taper_ratio, corrected_ar, corrected_sweep]).T, method="linear", ) if np.isnan(k_roll_damping): k_roll_damping = interpolate.griddata( (taper_ratio_data, correct_ar_data, corrected_sweep_data), roll_damping_data, np.array([taper_ratio, corrected_ar, corrected_sweep]).T, method="nearest", ) return float(k_roll_damping)
[docs] @staticmethod def cl_p_cdi_roll_damping(sweep_25, aspect_ratio) -> float: """ Roskam data to estimate the contribution to the roll moment damping of the drag-due-to-lift (figure 10.36) :param sweep_25: the sweep angle at 25% of the chord of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :return k_cdi_roll_damping: the contribution to the roll moment of the aspect ratio of the lifting surface. """ file = pth.join(resources.__path__[0], K_CDI_ROLL_DAMPING) db = pd.read_csv(file) sweep_25_data, aspect_ratio_data, cdi_roll_damping_data = filter_nans( db, ["SWEEP_25", "ASPECT_RATIO", "CDI_ROLL_DAMPING_PARAMETER"] ) if float(sweep_25) != np.clip(float(sweep_25), min(sweep_25_data), max(sweep_25_data)): _LOGGER.warning( "Sweep at 25% of the chord is outside of the range in Roskam's book, value clipped" ) if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") # Linear interpolation is preferred, but we put the nearest one as protection k_cdi_roll_damping = interpolate.griddata( (sweep_25_data, aspect_ratio_data), cdi_roll_damping_data, np.array([sweep_25, aspect_ratio]).T, method="linear", ) if np.isnan(k_cdi_roll_damping): k_cdi_roll_damping = interpolate.griddata( (sweep_25_data, aspect_ratio_data), cdi_roll_damping_data, np.array([sweep_25, aspect_ratio]).T, method="nearest", ) return float(k_cdi_roll_damping)
[docs] @staticmethod def cl_r_lifting_effect(aspect_ratio, taper_ratio, sweep_25): """ Roskam data to estimate the slope of the rolling moment due to yaw rate (figure 10.41). The figure is separated into two parts (a and b). :param aspect_ratio: wing aspect ratio :param taper_ratio: wing taper ratio :param sweep_25: wing sweep angle at quarter-taper point line in radians :return cl_r_lift: slope of the rolling moment due to yaw rate """ sweep_25 = sweep_25 * 180.0 / np.pi # radians to degrees # Reading data from the first part (a) relative to the wing taper ratio file = pth.join(resources.__path__[0], CL_R_LIFT_PART_A) db = pd.read_csv(file) x_0, y_0 = filter_nans(db, ["TAPER_RATIO_0_X", "TAPER_RATIO_0_Y"]) x_0.sort() y_0.sort() x_0_25, y_0_25 = filter_nans(db, ["TAPER_RATIO_025_X", "TAPER_RATIO_025_Y"]) x_0_25.sort() y_0_25.sort() x_0_5, y_0_5 = filter_nans(db, ["TAPER_RATIO_05_X", "TAPER_RATIO_05_Y"]) x_0_5.sort() y_0_5.sort() x_1_0, y_1_0 = filter_nans(db, ["TAPER_RATIO_1_X", "TAPER_RATIO_1_Y"]) x_1_0.sort() y_1_0.sort() if ( (aspect_ratio != np.clip(aspect_ratio, min(x_1_0), max(x_1_0))) or (aspect_ratio != np.clip(aspect_ratio, min(x_0_5), max(x_0_5))) or (aspect_ratio != np.clip(aspect_ratio, min(x_0_25), max(x_0_25))) or (aspect_ratio != np.clip(aspect_ratio, min(x_0), max(x_0))) ): _LOGGER.warning( "Aspect ratio value outside of the range in Roskam's book, value clipped" ) k_taper = [ float(np.interp(np.clip(aspect_ratio, min(x_0), max(x_0)), x_0, y_0)), float(np.interp(np.clip(aspect_ratio, min(x_0_25), max(x_0_25)), x_0_25, y_0_25)), float(np.interp(np.clip(aspect_ratio, min(x_0_5), max(x_0_5)), x_0_5, y_0_5)), float(np.interp(np.clip(aspect_ratio, min(x_1_0), max(x_1_0)), x_1_0, y_1_0)), ] if taper_ratio != np.clip(taper_ratio, 0.0, 1.0): _LOGGER.warning( "Taper ratio value outside of the range in Roskam's book, value clipped" ) k_intermediate = float( np.interp(np.clip(taper_ratio, 0.0, 1.0), [0.0, 0.25, 0.5, 1.0], k_taper) ) # Reading the second part of the figure (b) relative to the different wing sweep angles. file = pth.join(resources.__path__[0], CL_R_LIFT_PART_B) db = pd.read_csv(file) x_sw_0, y_sw_0 = filter_nans(db, ["SWEEP_25_0_X", "SWEEP_25_0_Y"]) x_sw_0.sort() y_sw_0.sort() x_sw_15, y_sw_15 = filter_nans(db, ["SWEEP_25_15_X", "SWEEP_25_15_Y"]) x_sw_15.sort() y_sw_15.sort() x_sw_30, y_sw_30 = filter_nans(db, ["SWEEP_25_30_X", "SWEEP_25_30_Y"]) x_sw_30.sort() y_sw_30.sort() x_sw_45, y_sw_45 = filter_nans(db, ["SWEEP_25_45_X", "SWEEP_25_45_Y"]) x_sw_45.sort() y_sw_45.sort() x_sw_60, y_sw_60 = filter_nans(db, ["SWEEP_25_60_X", "SWEEP_25_60_Y"]) x_sw_60.sort() y_sw_60.sort() if ( (k_intermediate != np.clip(k_intermediate, min(x_sw_45), max(x_sw_45))) or (k_intermediate != np.clip(k_intermediate, min(x_sw_30), max(x_sw_30))) or (k_intermediate != np.clip(k_intermediate, min(x_sw_15), max(x_sw_15))) or (k_intermediate != np.clip(k_intermediate, min(x_sw_0), max(x_sw_0))) or (k_intermediate != np.clip(k_intermediate, min(x_sw_60), max(x_sw_60))) ): _LOGGER.warning( "Intermediate value outside of the range in Roskam's book, value clipped" ) k_sweep = [ float(np.interp(np.clip(k_intermediate, min(x_sw_0), max(x_sw_0)), x_sw_0, y_sw_0)), float(np.interp(np.clip(k_intermediate, min(x_sw_15), max(x_sw_15)), x_sw_15, y_sw_15)), float(np.interp(np.clip(k_intermediate, min(x_sw_30), max(x_sw_30)), x_sw_30, y_sw_30)), float(np.interp(np.clip(k_intermediate, min(x_sw_45), max(x_sw_45)), x_sw_45, y_sw_45)), float(np.interp(np.clip(k_intermediate, min(x_sw_60), max(x_sw_60)), x_sw_60, y_sw_60)), ] if sweep_25 != np.clip(sweep_25, 0.0, 60.0): _LOGGER.warning( "Sweep angle value outside of the range in Roskam's book, value clipped" ) cl_r_lift = float( np.interp(np.clip(sweep_25, 0.0, 60.0), [0.0, 15.0, 30.0, 45.0, 60.0], k_sweep) ) return cl_r_lift
[docs] @staticmethod def cl_r_twist_effect(taper_ratio, aspect_ratio) -> float: """ Roskam data to estimate the contribution to the roll moment coefficient of the twist. (figure 10.42) :param taper_ratio: the taper ratio of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :return k_twist: contribution to the roll moment coefficient of the twist. """ file = pth.join(resources.__path__[0], CL_R_TWIST_EFFECT) db = pd.read_csv(file) taper_ratio_data, aspect_ratio_data, twist_effect_data = filter_nans( db, ["TAPER_RATIO", "ASPECT_RATIO", "TWIST_EFFECT"] ) if float(taper_ratio) != np.clip( float(taper_ratio), min(taper_ratio_data), max(taper_ratio_data) ): _LOGGER.warning("Taper ratio is outside of the range in Roskam's book, value clipped") if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") # Linear interpolation is preferred, but we put the nearest one as protection k_twist = interpolate.griddata( (taper_ratio_data, aspect_ratio_data), twist_effect_data, np.array([taper_ratio, aspect_ratio]).T, method="linear", ) if np.isnan(k_twist): k_twist = interpolate.griddata( (taper_ratio_data, aspect_ratio_data), twist_effect_data, np.array([taper_ratio, aspect_ratio]).T, method="nearest", ) return float(k_twist)
[docs] @staticmethod def cn_delta_a_correlation_constant(taper_ratio, aspect_ratio, eta_i) -> float: """ Roskam data to estimate the correlation constant for the computation of the yaw moment due to aileron. (figure 10.48) :param taper_ratio: the taper ratio of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :param eta_i: aileron inboard span location, as a ratio of the span :return k_a: the correlation constant for the computation of the yaw moment due to aileron """ file = pth.join(resources.__path__[0], CN_DELTA_A_K_A) db = pd.read_csv(file) taper_ratio_data, aspect_ratio_data, eta_i_data, correlation_constant = filter_nans( db, ["TAPER_RATIO", "ASPECT_RATIO", "SPAN_RATIO", "CORRELATION_CONSTANT"] ) if float(taper_ratio) != np.clip( float(taper_ratio), min(taper_ratio_data), max(taper_ratio_data) ): _LOGGER.warning("Taper ratio is outside of the range in Roskam's book, value clipped") if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") if float(eta_i) != np.clip( float(eta_i), min(correlation_constant), max(correlation_constant) ): _LOGGER.warning( "Aileron inboard location is outside of the range in Roskam's book, value clipped" ) # Linear interpolation is preferred, but we put the nearest one as protection k_a = interpolate.griddata( (taper_ratio_data, aspect_ratio_data, eta_i_data), correlation_constant, np.array([taper_ratio, aspect_ratio, eta_i]).T, method="linear", ) if np.isnan(k_a): k_a = interpolate.griddata( (taper_ratio_data, aspect_ratio_data, eta_i_data), correlation_constant, np.array([taper_ratio, aspect_ratio, eta_i]).T, method="nearest", ) return float(k_a)
[docs] @staticmethod def cn_p_twist_contribution(taper_ratio, aspect_ratio) -> float: """ Roskam data to estimate the contribution to the yaw moment of the twist of the lifting surface. (figure 10.37) :param taper_ratio: the taper ratio of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :return cn_p_twist: the contribution to the yaw moment of the twist of the lifting surface. """ file = pth.join(resources.__path__[0], CN_P_TWIST) db = pd.read_csv(file) taper_ratio_data, aspect_ratio_data, twist_contribution = filter_nans( db, ["TAPER_RATIO", "ASPECT_RATIO", "TWIST_CONTRIBUTION"] ) if float(taper_ratio) != np.clip( float(taper_ratio), min(taper_ratio_data), max(taper_ratio_data) ): _LOGGER.warning("Taper ratio is outside of the range in Roskam's book, value clipped") if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") # Linear interpolation is preferred, but we put the nearest one as protection cn_p_twist = interpolate.griddata( (taper_ratio_data, aspect_ratio_data), twist_contribution, np.array([taper_ratio, aspect_ratio]).T, method="linear", ) if np.isnan(cn_p_twist): cn_p_twist = interpolate.griddata( (taper_ratio_data, aspect_ratio_data), twist_contribution, np.array([taper_ratio, aspect_ratio]).T, method="nearest", ) return float(cn_p_twist)
[docs] @staticmethod def cn_r_lift_effect(static_margin, sweep_25, aspect_ratio, taper_ratio) -> float: """ Roskam data to estimate the effect of lift for the computation of the yaw moment due yaw rate (yaw damping). (figure 10.48) :param static_margin: distance between aft cg and aircraft aerodynamic center divided by MAC :param sweep_25: the sweep at 25% of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :param taper_ratio: the taper ratio of the lifting surface :return lift_effect: the effect of lift fot the computation of the yaw moment due to yaw rate """ # Only absolute value counts for this coefficient sweep_25 = abs(sweep_25) file = pth.join(resources.__path__[0], CN_R_LIFT_EFFECT) db = pd.read_csv(file) static_margin_data, sweep_25_data, aspect_ratio_data, intermediate_coeff_data = filter_nans( db, ["STATIC_MARGIN", "SWEEP_25", "ASPECT_RATIO", "INTERMEDIATE_COEFF"] ) if float(static_margin) != np.clip( float(static_margin), min(static_margin_data), max(static_margin_data) ): _LOGGER.warning("Static margin is outside of the range in Roskam's book, value clipped") if float(sweep_25) != np.clip(float(sweep_25), min(sweep_25_data), max(sweep_25_data)): _LOGGER.warning( "Sweep at 25% chord is outside of the range in Roskam's book, value clipped" ) if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") # Linear interpolation is preferred, but we put the nearest one as protection mid_coeff = interpolate.griddata( (static_margin_data, sweep_25_data, aspect_ratio_data), intermediate_coeff_data, np.array([static_margin, sweep_25, aspect_ratio]).T, method="linear", ) if np.isnan(mid_coeff): mid_coeff = interpolate.griddata( (static_margin_data, sweep_25_data, aspect_ratio_data), intermediate_coeff_data, np.array([static_margin, sweep_25, aspect_ratio]).T, method="nearest", ) lift_effect = 1.0 / 20.0 * (mid_coeff - 2.7 - 0.3 * taper_ratio) return float(lift_effect)
[docs] @staticmethod def cn_r_drag_effect(static_margin, sweep_25, aspect_ratio) -> float: """ Roskam data to estimate the effect of drag for the computation of the yaw moment due yaw rate (yaw damping). (figure 10.48) :param static_margin: distance between aft cg and aircraft aerodynamic center divided by MAC :param sweep_25: the sweep at 25% of the lifting surface :param aspect_ratio: the aspect ratio of the lifting surface :return drag_effect: the effect of drag for the computation of the yaw moment due to yaw rate """ # Only absolute value counts for this coefficient sweep_25 = abs(sweep_25) file = pth.join(resources.__path__[0], CN_R_DRAG_EFFECT) db = pd.read_csv(file) static_margin_data, sweep_25_data, aspect_ratio_data, drag_effect_data = filter_nans( db, ["STATIC_MARGIN", "SWEEP_25", "ASPECT_RATIO", "DRAG_EFFECT"] ) if float(static_margin) != np.clip( float(static_margin), min(static_margin_data), max(static_margin_data) ): _LOGGER.warning("Static margin is outside of the range in Roskam's book, value clipped") if float(sweep_25) != np.clip(float(sweep_25), min(sweep_25_data), max(sweep_25_data)): _LOGGER.warning( "Sweep at 25% chord is outside of the range in Roskam's book, value clipped" ) if float(aspect_ratio) != np.clip( float(aspect_ratio), min(aspect_ratio_data), max(aspect_ratio_data) ): _LOGGER.warning("Aspect ratio is outside of the range in Roskam's book, value clipped") # Linear interpolation is preferred, but we put the nearest one as protection drag_effect = interpolate.griddata( (static_margin_data, sweep_25_data, aspect_ratio_data), drag_effect_data, np.array([static_margin, sweep_25, aspect_ratio]).T, method="linear", ) if np.isnan(drag_effect): drag_effect = interpolate.griddata( (static_margin_data, sweep_25_data, aspect_ratio_data), drag_effect_data, np.array([static_margin, sweep_25, aspect_ratio]).T, method="nearest", ) return float(drag_effect)
[docs]def interpolate_database(database, tag_x: str, tag_y: str, input_x: float): """ Utility to interpolate the data csv. """ database_x, database_y = filter_nans(database, [tag_x, tag_y]) output_y = float( np.interp(np.clip(input_x, min(database_x), max(database_x)), database_x, database_y) ) return output_y
[docs]def filter_nans(database: pd.DataFrame, tags: List[str]) -> List[np.ndarray]: """ Utility function to jointly filter out NaN in the database with the selected tags. """ filtered_db = database[tags].dropna(axis=0, subset=tags) database_columns = [] for tag in tags: database_columns.append(filtered_db[tag].to_numpy()) return database_columns