def test_m310(self): """Tests access to the MIL HDBK 310 atmospheres""" print("MIL HDBK 310 - high temp at 10km.") obs1p, _ = at.mil_hdbk_310('high', 'temp', 10) m310_high10_1p = at.Atmosphere(profile=obs1p) sltemp_c = m310_high10_1p.airtemp_c(100) self.assertEqual(round(1000 * sltemp_c), round(1000 * 25.501))
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 Density(h): """ Air density at a given altitude based on ideal gas law Parameters ---------- h : int or float Altitude. Raises ------ ValueError When wrong arg type is passed. Returns ------- rho : float Density at altitude. """ if not isinstance(h, numbers.Number): raise ValueError('Wrong arg type passed to Density() for h. Must be' + ' int or float') return at.Atmosphere().airdens_kgpm3(h)
def test_isa_sl(self): """Tests the atmosphere class instantiated for an ISA at SL""" print("ISA (SL).") alt_m = 0 isa = at.Atmosphere() self.assertEqual(isa.airtemp_c(alt_m), 15) self.assertEqual(round(100 * isa.airpress_mbar(alt_m)), 101325) self.assertEqual(round(1000 * isa.airdens_kgpm3(alt_m)), 1225) self.assertEqual(round(100 * isa.vsound_mps(alt_m)), round(100 * 340.29))
def test_isa_10k_geop(self): """Tests the atmosphere class instantiated for an ISA at 10km geopotential""" print("ISA (10,000m geopotential).") # ISA at 10,000m geopotential, 10,015.8m geometric altitude. alt_m = 10000 isa = at.Atmosphere() self.assertEqual(isa.airtemp_c(alt_m), -50) self.assertEqual(round(100 * isa.airpress_mbar(alt_m)), 26436) self.assertEqual(round(100000 * isa.airdens_kgpm3(alt_m)), 41271) self.assertEqual(round(1000 * isa.vsound_mps(alt_m)), round(1000 * 299.463))
def test_isa_minus5k_geop(self): """Tests the atmosphere class instantiated for an ISA at -5km geopotential alt""" print("ISA (-5,000m geopotential).") # ISA at Z = -4996.1m geometric, H = -5000m geopotential altitude. alt_m = -5000 isa = at.Atmosphere() self.assertEqual(round(1000 * isa.airtemp_c(alt_m)), round(1000 * 47.5002)) self.assertEqual(round(10 * isa.airpress_mbar(alt_m)), round(10 * 1776.88)) self.assertEqual(round(10000 * isa.airdens_kgpm3(alt_m)), round(10000 * 1.93048)) self.assertEqual(round(1000 * isa.vsound_mps(alt_m)), round(1000 * 358.972))
def test_isa_10k_geom(self): """Tests the atmosphere class instantiated for an ISA at 10km geometric alt""" print("ISA (10,000m geometric).") # ISA at Z = 10,000m geometric, H = 9,984.3m geopotential altitude. alt_m = 9984.3 isa = at.Atmosphere() self.assertEqual(round(10000 * isa.airtemp_c(alt_m)), round(10000 * -49.8979)) self.assertEqual(round(100 * isa.airpress_mbar(alt_m)), round(100 * 264.999)) self.assertEqual(round(100000 * isa.airdens_kgpm3(alt_m)), round(100000 * 0.413511)) self.assertEqual(round(1000 * isa.vsound_mps(alt_m)), round(1000 * 299.532))
def vstall_kias(self, wingloadinglist_pa, clmax): """Calculates the stall speed (indicated) for a given wing loading in a specified cofiguration. **Parameters:** wingloading_pa float or numpy array, list of wing loading values in Pa. clmax maximum lift coefficient (float) or the name of a standard configuration (string) for which a maximum lift coefficient was specified in the :code:`performance` dictionary (currently implemented: 'take-off'). **Returns:** stall speed in knots (float or numpy array) **Note:** The calculation is performed assuming standard day ISA sea level conditions (not in the consitions specified in the atmosphere used when instantiating the :code:`AircraftConcept` object!) so the speed returned is an indicated (IAS) / calibrated (CAS) value. **Example**:: from ADRpy import constraintanalysis as ca designperformance = {'CLmaxTO':1.6} concept = ca.AircraftConcept({}, {}, designperformance, {}) wingloading_pa = 3500 print("VS1(take-off):", concept.vstall_kias(wingloading_pa, 'take-off')) """ isa = at.Atmosphere() rho0_kgpm3 = isa.airdens_kgpm3() if clmax == 'take-off': clmax = self.clmaxto vs_mps = math.sqrt(2 * wingloadinglist_pa / (rho0_kgpm3 * clmax)) return co.mps2kts(vs_mps)
def __init__(self, brief, design, performance, designatm): # Assign a default, if needed, to the atmosphere if not designatm: designatm = at.Atmosphere() self.designatm = designatm # Unpick the design brief dictionary first: if 'groundrun_m' in brief: self.groundrun_m = brief['groundrun_m'] else: # Flag if not specified, error thrown by t/o constraint self.groundrun_m = -1 if 'rwyelevation_m' in brief: self.rwyelevation_m = brief['rwyelevation_m'] else: # Assign sea level, if not specified self.rwyelevation_m = 0 if 'turnalt_m' in brief: self.turnalt_m = brief['turnalt_m'] else: # Assign sea level, if not specified self.turnalt_m = 0 if 'turnspeed_ktas' in brief: self.turnspeed_ktas = brief['turnspeed_ktas'] else: # Flag if not specified, error thrown by turn constraint self.turnspeed_ktas = -1 if 'stloadfactor' in brief: self.stloadfactor = brief['stloadfactor'] else: # Flag if not specified, error thrown by climb constraint self.stloadfactor = -1 if 'climbalt_m' in brief: self.climbalt_m = brief['climbalt_m'] else: # Assign sea level, if not specified self.climbalt_m = 0 if 'climbspeed_kias' in brief: self.climbspeed_kias = brief['climbspeed_kias'] else: # Flag if not specified, error thrown by climb constraint self.climbspeed_kias = -1 if 'climbrate_fpm' in brief: self.climbrate_fpm = brief['climbrate_fpm'] else: # Flag if not specified, error thrown by climb constraint self.climbrate_fpm = -1 if 'cruisealt_m' in brief: self.cruisealt_m = brief['cruisealt_m'] else: # Flag if not specified, error thrown by cruise constraint self.cruisealt_m = -1 if 'cruisespeed_ktas' in brief: # Option to specify Mach number instead coming soon self.cruisespeed_ktas = brief['cruisespeed_ktas'] else: # Flag if not specified, error thrown by cruise constraint self.cruisespeed_ktas = -1 if 'cruisethrustfact' in brief: self.cruisethrustfact = brief['cruisethrustfact'] else: # Assume 100% throttle in cruise self.cruisethrustfact = 1.0 if 'servceil_m' in brief: self.servceil_m = brief['servceil_m'] else: # Flag if not specified, error thrown by cruise constraint self.servceil_m = -1 if 'secclimbspd_kias' in brief: self.secclimbspd_kias = brief['secclimbspd_kias'] else: # Flag if not specified, error thrown by cruise constraint self.secclimbspd_kias = -1 if 'vstallclean_kcas' in brief: self.vstallclean_kcas = brief['vstallclean_kcas'] else: # Flag if not specified, error thrown by cruise constraint self.vstallclean_kcas = -1 # Unpick the design dictionary next: if 'aspectratio' in design: self.aspectratio = design['aspectratio'] else: self.aspectratio = 8 if 'bpr' in design: self.bpr = design['bpr'] else: # Piston engine self.bpr = -1 if 'tr' in design: self.throttle_r = design['tr'] else: self.throttle_r = 1.07 if 'wingheightratio' in design: self.wingheightratio = design['wingheightratio'] else: # Set to a large number if unspecified, leading to # WIG factor of near-unity self.wingheightratio = 100 if 'sweep_le_deg' in design: self.sweep_le_deg = design['sweep_le_deg'] self.sweep_le_rad = math.radians(self.sweep_le_deg) else: self.sweep_le_deg = 0 self.sweep_le_rad = 0 if 'sweep_mt_deg' in design: self.sweep_mt_deg = design['sweep_mt_deg'] self.sweep_mt_rad = math.radians(self.sweep_mt_deg) else: self.sweep_mt_deg = self.sweep_le_deg self.sweep_mt_rad = self.sweep_le_rad if 'weightfractions' in design: if 'cruise' in design['weightfractions']: self.cruise_weight_fraction = design['weightfractions']['cruise'] else: self.cruise_weight_fraction = 1.0 if 'servceil' in design['weightfractions']: self.sec_weight_fraction = design['weightfractions']['servceil'] else: self.sec_weight_fraction = 1.0 if 'turn' in design['weightfractions']: self.turn_weight_fraction = design['weightfractions']['turn'] else: self.turn_weight_fraction = 1.0 if 'climb' in design['weightfractions']: self.climb_weight_fraction = design['weightfractions']['climb'] else: self.climb_weight_fraction = 1.0 else: # Assume all constraints at same weight (e.g., electrically powered a/c) self.cruise_weight_fraction = 1.0 self.sec_weight_fraction = 1.0 self.turn_weight_fraction = 1.0 self.climb_weight_fraction = 1.0 # Next, unpick the performance dictionary if 'CDTO' in performance: self.cdto = performance['CDTO'] else: self.cdto = 0.09 if 'CDminclean' in performance: self.cdminclean = performance['CDminclean'] else: self.cdminclean = 0.03 if 'mu_R' in performance: self.mu_r = performance['mu_R'] else: self.mu_r = 0.03 if 'CLTO' in performance: self.clto = performance['CLTO'] else: self.clto = 0.95 if 'CLmaxTO' in performance: self.clmaxto = performance['CLmaxTO'] else: self.clmaxto = 1.5 if 'etaprop' in performance: self.etaprop = performance['etaprop'] else: self.etaprop = -1 if 'CLmaxclean' in performance: self.clmaxclean = performance['CLmaxclean'] else: self.clmaxclean = -1 self.etadefaultflag = 0 if 'etaprop' in performance: if 'take-off' in performance['etaprop']: self.etaprop_to = performance['etaprop']['take-off'] else: self.etaprop_to = 0.45 self.etadefaultflag += 1 if 'cruise' in performance['etaprop']: self.etaprop_cruise = performance['etaprop']['cruise'] else: self.etaprop_cruise = 0.85 self.etadefaultflag += 1 if 'servceil' in performance['etaprop']: self.etaprop_sec = performance['etaprop']['servceil'] else: self.etaprop_sec = 0.65 self.etadefaultflag += 1 if 'turn' in performance['etaprop']: self.etaprop_turn = performance['etaprop']['turn'] else: self.etaprop_turn = 0.85 self.etadefaultflag += 1 if 'climb' in performance['etaprop']: self.etaprop_climb = performance['etaprop']['climb'] else: self.etaprop_climb = 0.75 self.etadefaultflag += 1 else: self.etaprop_to = 0.45 self.etaprop_cruise = 0.85 self.etaprop_sec = 0.65 self.etaprop_climb = 0.75 self.etaprop_turn = 0.85 self.etadefaultflag = 5
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