def vs_keas(self, loadfactor): """Equivalent air speed in knots for stall at design weight, for some loadfactor""" if self.acobj.weight_n is False: defmsg = 'Maximum take-off weight was not specified in the design definitions dictionary' raise ValueError(defmsg) if self.acobj.wingarea_m2 is False: defmsg = 'Reference wing area was not specified in the design definitions dictionary' raise ValueError(defmsg) weight_n = self.acobj.weight_n wingarea_m2 = self.acobj.wingarea_m2 if loadfactor >= 0: if self.acobj.clmaxclean is False: perfmsg = 'Maximum lift coefficient in clean configuration was not specified in performance dictionary' raise ValueError(perfmsg) cl = self.acobj.clmaxclean else: if self.acobj.clminclean is False: perfmsg = 'Minimum lift coefficient in clean configuration was not specified in performance dictionary' raise ValueError(perfmsg) cl = self.acobj.clminclean rho0_kgm3 = self.acobj.designatm.airdens_kgpm3(altitudes_m=0) vs_keas = co.mps2kts( math.sqrt((loadfactor * weight_n) / (0.5 * rho0_kgm3 * wingarea_m2 * cl))) return vs_keas
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 keas2kcas(self, keas, altitude_m): """Converts equivalent airspeed into calibrated airspeed. The relationship between the two depends on the Mach number :math:`M` and the ratio :math:`\\delta` of the pressure at the current altitude :math:`P_\\mathrm{alt}` and the sea level pressure :math:`P_\\mathrm{0}`. We approximate this relationship with the expression: .. math:: \\mathrm{CAS}\\approx\\mathrm{EAS}\\left[1 + \\frac{1}{8}(1-\\delta)M^2 + \\frac{3}{640}\\left(1-10\\delta+9\\delta^2 \\right)M^4 \\right] **Parameters** keas float or numpy array, equivalent airspeed in knots. altitude_m float, altitude in metres. **Returns** kcas float or numpy array, calibrated airspeed in knots. mach float, Mach number. **See also** ``mpseas2mpscas`` **Notes** The reverse conversion is slightly more complicated, as their relationship depends on the Mach number. This, in turn, requires the computation of the true airspeed and that can only be computed from EAS, not CAS. The unit- specific nature of the function is also the result of the need for computing the Mach number. **Example** :: import numpy as np from ADRpy import atmospheres as at from ADRpy import unitconversions as co isa = at.Atmosphere() keas = np.array([100, 200, 300]) altitude_m = co.feet2m(40000) kcas, mach = isa.keas2kcas(keas, altitude_m) print(kcas) Output: :: [ 101.25392563 209.93839073 333.01861569] """ # Note: unit specific, as the calculation requires Mach no. np.asarray(keas) mpseas = co.kts2mps(keas) mpscas, machno = self.mpseas2mpscas(mpseas, altitude_m) kcas = co.mps2kts(mpscas) return kcas, machno
def vsound_kts(self, altitudes_m=0): """Speed of sound in knots.""" return co.mps2kts(self.vsound_mps(altitudes_m))
def _paragraph335(self): """Design airspeeds, as per CS-23.335 (see also 14 CFR 23.335). For all categories of aircraft, this specification item produces limits for design airspeeds. **Outputs:** eas_dict dictionary, containing minimum and maximum allowable design airspeeds in KEAS. """ if self.cruisespeed_keas is False: cruisemsg = 'Cruise speed not specified in the csbrief or designbrief dictionary.' raise ValueError(cruisemsg) vc_keas = self.cruisespeed_keas if self.acobj.clmaxclean is False: perfmsg = 'CLmaxclean must be specified in the performance dictionary.' raise ValueError(perfmsg) clmaxclean = self.acobj.clmaxclean if self.acobj.weight_n is False: designmsg = 'Maximum take-off weight must be specified in the design dictionary.' raise ValueError(designmsg) if self.acobj.wingarea_m2 is False: designmsg = 'Reference wing area must be specified in the design dictionary.' raise ValueError(designmsg) wingloading_pa = self.acobj.weight_n / self.acobj.wingarea_m2 wingloading_lbft2 = co.pa2lbfft2(wingloading_pa) # Create a dictionary of empty dictionaries for each aircraft category cs23categories_list = ['norm', 'util', 'comm', 'aero'] eas_dict = dict( zip(cs23categories_list, [{} for _ in range(len(cs23categories_list))])) # (a) Design cruising speed, V_C # (a)(1, 2) vcfactor_1i = np.interp(wingloading_lbft2, [20, 100], [33, 28.6]) vcfactor_1ii = np.interp(wingloading_lbft2, [20, 100], [36, 28.6]) eas_dict['norm'].update( {'vcmin_keas': vcfactor_1i * np.sqrt(wingloading_lbft2)}) eas_dict['util'].update( {'vcmin_keas': vcfactor_1i * np.sqrt(wingloading_lbft2)}) eas_dict['comm'].update( {'vcmin_keas': vcfactor_1i * np.sqrt(wingloading_lbft2)}) eas_dict['aero'].update( {'vcmin_keas': vcfactor_1ii * np.sqrt(wingloading_lbft2)}) # (a)(3) if self.maxlevelspeed_keas is False: for category in cs23categories_list: eas_dict[category].update({'vcsoftmax_keas': False}) else: for category in cs23categories_list: eas_dict[category].update( {'vcsoftmax_keas': 0.9 * self.maxlevelspeed_keas}) # This is not strict # (a)(4) Requires Mach # (b) Design dive speed, V_D # (b) (1, 2, 3) vdfactor_2i = np.interp(wingloading_lbft2, [20, 100], [1.4, 1.35]) vdfactor_2ii = np.interp(wingloading_lbft2, [20, 100], [1.5, 1.35]) vdfactor_2iii = np.interp(wingloading_lbft2, [20, 100], [1.55, 1.35]) eas_dict['norm'].update({ 'vdmin_keas': np.fmax(1.25 * vc_keas, vdfactor_2i * eas_dict['norm']['vcmin_keas']) }) eas_dict['util'].update({ 'vdmin_keas': np.fmax(1.25 * vc_keas, vdfactor_2ii * eas_dict['util']['vcmin_keas']) }) eas_dict['comm'].update({ 'vdmin_keas': np.fmax(1.25 * vc_keas, vdfactor_2i * eas_dict['comm']['vcmin_keas']) }) eas_dict['aero'].update({ 'vdmin_keas': np.fmax(1.25 * vc_keas, vdfactor_2iii * eas_dict['aero']['vcmin_keas']) }) # (b)(4) Requires Mach # (c) Design manoeuvring speed, V_A # (c)(1, 2) wfract = self.weightfraction vs_keas = self.vs_keas(loadfactor=wfract) manoeuvrelimits = self._paragraph337() for category in cs23categories_list: eas_dict[category].update({ 'vamin_keas': vs_keas * math.sqrt(manoeuvrelimits[category]['npos_min']) }) eas_dict[category].update({'vasoftmax_keas': vc_keas}) # This is not strict # (d) Design speed for maximum gust intensity, V_B # (d)(1) _, gustspeedsmps = self._paragraph333() gustloads, k_g, liftslope_prad = self._paragraph341( speedatgust_keas={'Uc': vc_keas}) vs1_keas = self.vs_keas(loadfactor=wfract) for category in cs23categories_list: if category == 'comm': gust_de_mps = gustspeedsmps[category]['Ub_mps'] else: gust_de_mps = gustspeedsmps[category]['Uc_mps'] trueloading_pa = wingloading_pa * wfract rho0_kgm3 = self.acobj.designatm.airdens_kgpm3(altitudes_m=0) a = 1 b = -(liftslope_prad / clmaxclean) * k_g * gust_de_mps c = -2 * trueloading_pa / (rho0_kgm3 * clmaxclean) * wfract vbmin1_keas = co.mps2kts((-b + (b**2 - 4 * a * c)**0.5) / (2 * a)) eas_dict[category].update({'vbmin1_keas': vbmin1_keas}) vbmin2_keas = vs1_keas * math.sqrt(gustloads[category]['npos_Uc']) eas_dict[category].update({'vbmin2_keas': vbmin2_keas}) vbmin_keas = np.fmin(vbmin1_keas, vbmin2_keas) eas_dict[category].update({'vbmin_keas': vbmin_keas}) # (d)(2) eas_dict[category].update( {'vbmax_keas': np.fmax(vbmin_keas, vc_keas)}) return eas_dict
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 _paragraph335(self, wingloading_pa): """EASA specification for CS-23 Design Airspeeds. For all categories of aircraft, this specification item produces limits for design airspeeds. **Parameters:** wingloading_pa float or array, list of wing-loading values in Pa. **Returns:** eas_dict dictionary, containing minimum and maximum allowable design airspeeds in KEAS. **Note:** This method is not fully implemented, a future revision would allow the cruise and design speeds to be selected as functions of Mach number based on compressibility limits. Furthermore, the cruise speed should be limited by maximum level flight speed but is not currently implemented. """ if self.acobj.cruisealt_m is False: cruisemsg = "Cruise altitude not specified in the designbrief dictionary." raise ValueError(cruisemsg) rho_kgpm3 = self.acobj.designatm.airdens_kgpm3(self.acobj.cruisealt_m) if self.acobj.cruisespeed_ktas is False: cruisemsg = "Cruise speed not specified in the designbrief dictionary." raise ValueError(cruisemsg) vc_keas = co.tas2eas(self.acobj.cruisespeed_ktas, rho_kgpm3) if self.acobj.clmaxclean is False: perfmsg = "CLmaxclean must be specified in the performance dictionary." raise ValueError(perfmsg) clmaxclean = self.acobj.clmaxclean wingloading_pa = actools.recastasnpfloatarray(wingloading_pa) wingloading_lbft2 = co.pa2lbfft2(wingloading_pa) # Create a dictionary of empty dictionaries for each aircraft category cs23categories_list = ['norm', 'util', 'comm', 'aero'] eas_dict = dict( zip(cs23categories_list, [{} for _ in range(len(cs23categories_list))])) # (a) Design cruising speed, V_C # (a)(1, 2) vcfactor_1i = np.interp(wingloading_lbft2, [20, 100], [33, 28.6]) vcfactor_1ii = np.interp(wingloading_lbft2, [20, 100], [36, 28.6]) eas_dict['norm'].update( {'vcmin_keas': vcfactor_1i * np.sqrt(wingloading_lbft2)}) eas_dict['util'].update( {'vcmin_keas': vcfactor_1i * np.sqrt(wingloading_lbft2)}) eas_dict['comm'].update( {'vcmin_keas': vcfactor_1i * np.sqrt(wingloading_lbft2)}) eas_dict['aero'].update( {'vcmin_keas': vcfactor_1ii * np.sqrt(wingloading_lbft2)}) # (a)(3) Requires vh_keas # (a)(4) Requires Mach # (b) Design dive speed, V_D # (b) (1, 2, 3) vdfactor_2i = np.interp(wingloading_lbft2, [20, 100], [1.4, 1.35]) vdfactor_2ii = np.interp(wingloading_lbft2, [20, 100], [1.5, 1.35]) vdfactor_2iii = np.interp(wingloading_lbft2, [20, 100], [1.55, 1.35]) eas_dict['norm'].update({ 'vdmin_keas': np.fmax(1.25 * vc_keas, vdfactor_2i * eas_dict['norm']['vcmin_keas']) }) eas_dict['util'].update({ 'vdmin_keas': np.fmax(1.25 * vc_keas, vdfactor_2ii * eas_dict['util']['vcmin_keas']) }) eas_dict['comm'].update({ 'vdmin_keas': np.fmax(1.25 * vc_keas, vdfactor_2i * eas_dict['comm']['vcmin_keas']) }) eas_dict['aero'].update({ 'vdmin_keas': np.fmax(1.25 * vc_keas, vdfactor_2iii * eas_dict['aero']['vcmin_keas']) }) # (b)(4) Requires Mach # (c) Design manoeuvring speed, V_A # (c)(1, 2) vs_keas = self.vs_keas(loadfactor=1) manoeuvrelimits = self._paragraph337() for category in cs23categories_list: eas_dict[category].update({ 'vamin_keas': vs_keas * math.sqrt(manoeuvrelimits[category]['npos_min']) }) eas_dict[category].update({'vamax_keas': vc_keas}) # (d) Design speed for maximum gust intensity, V_B # (d)(1) _, gustspeedsmps = self._paragraph333() _, k_g, liftslope = self._paragraph341( wingloading_pa, speedatgust_keas={'Uc': vc_keas}) cruisewfraction = self.acobj.cruise_weight_fraction vs1_keas = self.vs_keas(loadfactor=cruisewfraction) for category in cs23categories_list: if category == 'comm': gust_de_mps = gustspeedsmps[category]['Ub_mps'] else: gust_de_mps = gustspeedsmps[category]['Uc_mps'] cruiseloading_pa = wingloading_pa * self.acobj.cruise_weight_fraction rho0_kgm3 = self.acobj.designatm.airdens_kgpm3(altitudes_m=0) a = 1 b = -(liftslope / clmaxclean) * k_g * gust_de_mps c = -2 * cruiseloading_pa / (rho0_kgm3 * clmaxclean) vbmin1_keas = co.mps2kts((-b + (b**2 - 4 * a * c)**0.5) / (2 * a)) eas_dict[category].update({'vbmin1_keas': vbmin1_keas}) vbmin2_keas = vs1_keas * math.sqrt( manoeuvrelimits[category]['npos_min']) eas_dict[category].update({'vbmin2_keas': vbmin2_keas}) vbmin_keas = np.fmin(vbmin1_keas, vbmin2_keas) eas_dict[category].update({'vbmin_keas': vbmin_keas}) # (d)(2) eas_dict[category].update( {'vbmax_keas': np.fmax(vbmin_keas, vc_keas)}) return eas_dict