def test_take_off(self): """Tests the take-off constraint calculation""" print("Take-off constraint test.") designbrief = {'rwyelevation_m': 1000, 'groundrun_m': 1200} designdefinition = {'aspectratio': 7.3, 'bpr': 3.9, 'tr': 1.05} designperformance = { 'CDTO': 0.04, 'CLTO': 0.9, 'CLmaxTO': 1.6, 'mu_R': 0.02 } wingloadinglist_pa = [2000, 3000, 4000, 5000] atm = at.Atmosphere() concept = ca.AircraftConcept(designbrief, designdefinition, designperformance, atm) tw_sl, liftoffspeed_mpstas, _ = concept.twrequired_to( wingloadinglist_pa) self.assertEqual(round(10000 * tw_sl[0]), round(10000 * 0.19397876)) self.assertEqual(round(10000 * liftoffspeed_mpstas[0]), round(10000 * 52.16511207)) self.assertEqual(round(10000 * tw_sl[3]), round(10000 * 0.41110154)) self.assertEqual(round(10000 * liftoffspeed_mpstas[3]), round(10000 * 82.48028428))
def test_0createobject(self): """Tests parameters were copied in correctly""" print("Create concept test.") # Choose and build a random aircraft from the aircraft test library self.ac_rand_i = random.randint(0, len(self.ac_lib) - 1) self.ac_random = ca.AircraftConcept(self.ac_lib[self.ac_rand_i][0], self.ac_lib[self.ac_rand_i][1], self.ac_lib[self.ac_rand_i][2]) # For each design dictionary of the random aircraft picked for dictindex in range(len(self.ac_lib[self.ac_rand_i])): # Go through each item of a single design dictionary from the test library and confirm it copied correctly for i, (k, test_value) in enumerate( self.ac_lib[self.ac_rand_i][dictindex].items()): if type(self.ac_random.designspace[dictindex][k]) == list: dictvalue = sum(self.ac_random.designspace[dictindex][k]) \ / len(self.ac_random.designspace[dictindex][k]) else: dictvalue = self.ac_random.designspace[dictindex][k] self.assertEqual(dictvalue, test_value) # Build the last aircraft in the aircraft test library self.ac_last = ca.AircraftConcept(self.ac_lib[-1][0], self.ac_lib[-1][1], self.ac_lib[-1][2]) # For each design dictionary of the random aircraft picked for dictindex in range(len(self.ac_lib[-1])): # Go through each item of a single design dictionary from the test library and confirm it copied correctly for i, (k, test_value) in enumerate( self.ac_lib[-1][dictindex].items()): if type(self.ac_last.designspace[dictindex][k]) == list: dictvalue = sum(self.ac_last.designspace[dictindex][k]) \ / len(self.ac_last.designspace[dictindex][k]) else: dictvalue = self.ac_last.designspace[dictindex][k] self.assertEqual(dictvalue, test_value) return
def __init__(self, brief=None, design=None, performance=None, designatm=None): # Build an aircraft object based on the design dictionaries and atmosphere object self.acobj = ca.AircraftConcept(brief=brief, design=design, performance=performance, designatm=designatm) return
def __init__(self, brief=None, design=None, performance=None, designatm=None, propulsion=None, csbrief=None): # Assign a default, if needed, to the csbrief dictionary if csbrief is None: csbrief = {} # Build an aircraft object based on the design dictionaries and atmosphere object self.acobj = ca.AircraftConcept(brief=brief, design=design, performance=performance, designatm=designatm) # Specify default flags or parameters for the Vn definitions dictionary, if parameter is left unspecified default_csbrief = { # Certification category 'certcat': 'norm', # Default category is normal # Vn altitude query 'altitude_m': 0, # Assign sea level (h = 0 metres) # Design Airspeeds 'cruisespeed_keas': False, # Flag as not specified 'divespeed_keas': False, # Flag as not specified 'maxlevelspeed_keas': False, # Flag as not specified # Vn loading query, fraction of MTOW 'weightfraction': 1 # Assume V-n diagram applies to MTOW loading } # Use the templates (default dictionaries) to populate missing values in the provided design dictionaries # Iterate through the defaults dictionary for _, (defaults_k, defaults_v) in enumerate(default_csbrief.items()): # If a parameter of csbrief is left unspecified by the user, copy in the default value if defaults_k not in csbrief: csbrief.update({defaults_k: defaults_v}) # FURTHER COMPREHENSION: If design cruise speed was not specified in the csbrief dictionary argument if csbrief['cruisespeed_keas'] is False: # Check to see if a cruise speed can instead be swiped from the design brief if self.acobj.cruisespeed_ktas is False: self.cruisespeed_keas = False else: rho_kgpm3 = self.acobj.designatm.airdens_kgpm3(csbrief['altitude_m']) self.cruisespeed_keas = co.tas2eas(self.acobj.cruisespeed_ktas, rho_kgpm3) else: self.cruisespeed_keas = csbrief['cruisespeed_keas'] # Populate object attributes self.category = csbrief['certcat'] self.divespeed_keas = csbrief['divespeed_keas'] self.maxlevelspeed_keas = csbrief['maxlevelspeed_keas'] self.altitude_m = csbrief['altitude_m'] self.weightfraction = csbrief['weightfraction'] return
def test_vstall(self): """Tests the stall speed method""" print("Stall speed method (vstall_kias) test.") designperformance = {'CLmaxTO': 1.6} concept = ca.AircraftConcept({}, {}, designperformance, {}) wingloading_pa = 3500 self.assertEqual( round(10000 * concept.vstall_kias(wingloading_pa, 'take-off')), round(10000 * 116.166934173))
def test_wig(self): """Tests the wing in ground effect factor calculation""" print("WIG factor test.") designdef = {'aspectratio': 8} wingarea_m2 = 10 wingspan_m = math.sqrt(designdef['aspectratio'] * wingarea_m2) for wingheight_m in [0.6, 0.8, 1.0]: designdef['wingheightratio'] = wingheight_m / wingspan_m aircraft = ca.AircraftConcept({}, designdef, {}, {}) self.assertEqual(round(10000 * aircraft.wigfactor()), round(10000 * 0.7619047))
def test_findchordsweep(self): """Tests the calculation of sweep angle in radians, at some point of the wing chord""" print("Find arbitrary chord-sweep test.") # Use Aircraft 1: Piston propeller aircraft acindex = 1 concept = ca.AircraftConcept(self.ac_lib[acindex][0], self.ac_lib[acindex][1], self.ac_lib[acindex][2]) sweep_le_rad = concept.sweep_le_rad sweep_25_rad = concept.sweep_25_rad self.assertEqual(concept.findchordsweep_rad(0), sweep_le_rad) self.assertEqual(concept.findchordsweep_rad(0.25), sweep_25_rad) return
def test_estimateliftslope(self): """Tests the generation of predicted lift-slope with mach number""" print("Lift-curve slope estimation test.") # Use Aircraft 2: F/A-18C acindex = 2 concept = ca.AircraftConcept(self.ac_lib[acindex][0], self.ac_lib[acindex][1], self.ac_lib[acindex][2]) macharray = np.arange(0.1, 4, 0.1) liftslopelist = [] for mach_inf in macharray: liftslopelist.append(concept.liftslope(mach_inf=mach_inf)) # Assert that the lift curve slope is never below or equal to zero self.assertGreater(min(liftslopelist), 0) return
def test_kfactorlesm(self): """Tests the induced drag factor K calculation using LESM""" print("Induced drag factor test.") # Use Aircraft 2: F/A-18C acindex = 2 concept = ca.AircraftConcept(self.ac_lib[acindex][0], self.ac_lib[acindex][1], self.ac_lib[acindex][2]) macharray = np.arange(0.1, 2, 0.1) kpredlist = [] for mach_inf in macharray: kpredlist.append( concept.induceddragfact_lesm(mach_inf=mach_inf, cl_real=0.455)) # Assert that the induced drag is never below or equal to zero self.assertGreater(min(kpredlist), 0) return
def PlantSizing(plant, rEM=0.6): PP = plant.copy() isa = at.Atmosphere() # aircraft design concept object creation requires a design-brief designbrief = { 'rwyelevation_m': 0, # altitudue of the runway 'groundrun_m': 50, # maximumm allowed take-off distance 'climbrate_fpm': 3.5 * (60 / 0.3048), # required climb rate that must be achieved 'climbspeed_kias': co.mps2kts(40), # indidcated airspeed requirement for climb 'climbalt_m': 5000, # altitude at which the climb rate must be achieved 'secclimbspd_kias': co.mps2kts(25), # speed at which service ceiling is reached 'cruisespeed_ktas': co.mps2kts(40), # cruise velocity 'cruisealt_m': 5000, # altitude at which the cruise speed must be achieved 'cruisethrustfact': 1, # ratio of cruise thrust to total thrust 'servceil_m': 5000, # alt at which the max rate of climb drops to 100 ft/min # dummy values to prevent errors, not needed since no turns are simulated 'turnspeed_ktas': 10, 'stloadfactor': 1.5 } # aircraft design concept object creation requires a design spec design = { 'aspectratio': AC['AR'], 'bpr': -3 # bypass ratio; -3 means no thrust correction (neglected for the aircraft) } # aircraft design concept object creation requires a performance estimate designperf = { 'CDTO': GetCd(AC['maxPitch'], 0), # take-off coefficient of drag 'CLTO': GetCl(AC['maxPitch']), # take-off coefficient of lift 'CLmaxTO': GetCl(AC['maxPitch']), # take-off maximum coefficient of lift 'CLmaxclean': GetCl(AC['maxPitch']), # max lift coefficient in flight, # with (non-existant) flaps retracted 'CDminclean': AC['parasiteDrag'], # min, zero lift drag coefficient 'etaprop': { 'take-off': 0.45, 'climb': 0.75, 'cruise': 0.85, 'turn': 0.85, 'servceil': 0.65 }, # propeller efficiencies } # An aircraft concept object can now be instantiated concept = ca.AircraftConcept(designbrief, design, designperf, isa) tow = PP['battMassList'][-1] + PP['fullTankMass'] + AC['emptyMass']\ + AC['payloadMass'] + PP['EMMass'] + PP['ICEMass'] wingloading = tow * AC['g'] / AC['S'] power = concept.powerrequired(wingloading, tow, feasibleonly=False) # select largest power requirement, covnert from HP to W powerReq = max(power['take-off'], power['climb'], power['cruise']) powerReq = co.hp2kw(powerReq) * 1000 # power is satisfied by ICE and EM ICEPowerReq = (1 - rEM) * powerReq EMPowerReq = rEM * powerReq # power available before sizing ICEPower = 2 * math.pi * PP['ICEMaprps'][-1] * PP['ICEMapTorque'][-1] EMPower = 2 * math.pi * PP['maxEMrps'] * PP['maxEMTorque'] ICEFactor = ICEPowerReq / ICEPower EMFactor = EMPowerReq / EMPower # resize the torque scales to get adjusted power limits PP['ICEMapTorque'] *= ICEFactor PP['maxEMTorque'] *= EMFactor PP['maxEMPower'] = 2 * math.pi * PP['maxEMrps'] * PP['maxEMTorque'] # resize the mass PP['ICEMass'] *= ICEFactor PP['EMMass'] *= EMFactor # propeller sizing maxICETorque = PP['ICEMapTorque'][-1] maxICErps = PP['ICEMaprps'][-1] dFun = lambda D: maxICETorque - PropQFun(1, maxICErps, 0, D) PP['D'] = fsolve(dFun, 0.3) return PP
def test_propulsionsensitivity(self): """Tests the statistical analysis method for the one-at-a-time inquiry of T/W sensitivity to input parameters""" print("Propulsion Sensitivity (One-at-a-time) test.") # Use Aircraft 0: Business jet acindex = 0 concept = ca.AircraftConcept(self.ac_lib[acindex][0], self.ac_lib[acindex][1], self.ac_lib[acindex][2]) wingloadinglist_pa = np.arange(2000, 5000, 10) concept.propulsionsensitivity_monothetic( wingloading_pa=wingloadinglist_pa, y_var='tw', x_var='ws_pa', show=False) # Use Aircraft 3: Custom Business Jet acindex = 3 concept = ca.AircraftConcept(self.ac_lib[acindex][0], self.ac_lib[acindex][1], self.ac_lib[acindex][2]) wingloadinglist_pa = np.arange(2000, 8000, 50) customlabelling = { 'aspectratio': 'AR', 'bpr': 'BPR', 'sweep_le_deg': '$\\Lambda_{LE}$', 'sweep_25_deg': '$\\Lambda_{25}$', 'sweep_mt_deg': '$\\Lambda_{MT}$' } concept.propulsionsensitivity_monothetic( wingloading_pa=wingloadinglist_pa, y_var='tw', x_var='s_m2', customlabels=customlabelling, show=False, maskbool=False, figsize_in=[16, 9]) # Use Aircraft 4: Custom SEPPA acindex = 4 concept = ca.AircraftConcept(self.ac_lib[acindex][0], self.ac_lib[acindex][1], self.ac_lib[acindex][2]) wingloadinglist_pa = np.arange(700, 2500, 15) concept.propulsionsensitivity_monothetic( wingloading_pa=wingloadinglist_pa, y_var='p_hp', x_var='s_m2', show=False, maskbool=True) # Use Aircraft 5: Keane's Small UAV acindex = 5 concept = ca.AircraftConcept(self.ac_lib[acindex][0], self.ac_lib[acindex][1], self.ac_lib[acindex][2]) wingloadinglist_pa = np.arange(50, 2500, 10) concept.propulsionsensitivity_monothetic( wingloading_pa=wingloadinglist_pa, y_lim=5, y_var='p_hp', x_var='s_m2', show=False, maskbool=True) return
def test_twreqconstraint(self): """Tests the thrust-to-weight constraint calculations""" # Use Aircraft 0: Business Jet acindex = 0 concept = ca.AircraftConcept(self.ac_lib[acindex][0], self.ac_lib[acindex][1], self.ac_lib[acindex][2]) wingloadinglist_pa = [2000, 3000, 4000, 5000, 6000, 7000] # Investigate the climb constraint print("T/W Climb constraint test.") tw_climb = concept.twrequired_clm(wingloading_pa=wingloadinglist_pa) testarray1 = np.array([ 0.15268568, 0.1239213, 0.11219318, 0.10727957, 0.10577321, 0.10621385 ]) self.assertIs(tw_climb.all(), testarray1.all()) # Investigate the cruise constraint print("T/W Cruise constraint test.") tw_cruise = concept.twrequired_crs(wingloading_pa=wingloadinglist_pa) testarray1 = np.array([ 0.37261153, 0.31997436, 0.31512577, 0.32939262, 0.35321719, 0.38250331 ]) self.assertEqual(tw_cruise.all(), testarray1.all()) # Investigate the service ceiling constraint print("T/W Service Ceiling constraint test.") tw_serviceceil = concept.twrequired_sec( wingloading_pa=wingloadinglist_pa) testarray1 = np.array([ 0.46419224, 0.34031968, 0.28625287, 0.26010837, 0.24792501, 0.24371946 ]) self.assertEqual(tw_serviceceil.all(), testarray1.all()) # Investigate the take-off constraint print("T/W Take-off constraint test.") tw_sl, liftoffspeed_mpstas, _ = concept.twrequired_to( wingloadinglist_pa) self.assertEqual(round(10000 * tw_sl[0]), round(10000 * 0.19397876)) self.assertEqual(round(10000 * liftoffspeed_mpstas[0]), round(10000 * 52.16511207)) self.assertEqual(round(10000 * tw_sl[3]), round(10000 * 0.41110154)) self.assertEqual(round(10000 * liftoffspeed_mpstas[3]), round(10000 * 82.48028428)) # Investigate the cruise constraint print("T/W Turn constraint test.") tw_turn = concept.twrequired_trn(wingloading_pa=wingloadinglist_pa) testarray1 = np.array([ 0.21826547, 0.21048143, 0.22608074, 0.25103339, 0.28066271, 0.31296442 ]) testarray2 = np.array([ 0.45627288, 0.68440931, 0.91254575, 1.14068219, 1.36881863, 1.59695506 ]) testarray3 = np.array([ 0.21826547, 0.21048143, 0.22608074, 0.25103339, 0.28066271, np.nan ]) self.assertEqual(tw_turn[0].all(), testarray1.all()) self.assertEqual(tw_turn[1].all(), testarray2.all()) self.assertEqual(tw_turn[2].all(), testarray3.all()) return