def perf(self, simt): if abs(simt - self.t0) >= self.dt: self.t0 = simt else: return """Aircraft performance""" swbada = False # no-bada version # allocate aircraft to their flight phase self.phase, self.bank = \ phases(bs.traf.alt, bs.traf.gs, bs.traf.delalt, \ bs.traf.cas, self.vmto, self.vmic, self.vmap, self.vmcr, self.vmld, bs.traf.bank, bs.traf.bphase, \ bs.traf.swhdgsel,swbada) # AERODYNAMICS # compute CL: CL = 2*m*g/(VTAS^2*rho*S) self.qS = 0.5 * bs.traf.rho * np.maximum(1., bs.traf.tas) * np.maximum( 1., bs.traf.tas) * self.Sref cl = self.mass * g0 / (self.qS * np.cos(self.bank)) * ( self.phase != 6) + 0. * (self.phase == 6) # scaling factors for CD0 and CDi during flight phases according to FAA (2005): SAGE, V. 1.5, Technical Manual # For takeoff (phase = 6) drag is assumed equal to the takeoff phase CD0f = (self.phase==1)*(self.etype==1)*coeffBS.d_CD0j[0] + \ (self.phase==2)*(self.etype==1)*coeffBS.d_CD0j[1] + \ (self.phase==3)*(self.etype==1)*coeffBS.d_CD0j[2] + \ (self.phase==4)*(self.etype==1)*coeffBS.d_CD0j[3] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt>=450.0)*coeffBS.d_CD0j[4] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt<450.0)*coeffBS.d_CD0j[5] + \ (self.phase==6)*(self.etype==1)*coeffBS.d_CD0j[0] + \ (self.phase==1)*(self.etype==2)*coeffBS.d_CD0t[0] + \ (self.phase==2)*(self.etype==2)*coeffBS.d_CD0t[1] + \ (self.phase==3)*(self.etype==2)*coeffBS.d_CD0t[2] + \ (self.phase==4)*(self.etype==2)*coeffBS.d_CD0t[3] # (self.phase==5)*(self.etype==2)*(self.alt>=450)*coeffBS.d_CD0t[4] + \ # (self.phase==5)*(self.etype==2)*(self.alt<450)*coeffBS.d_CD0t[5] # For takeoff (phase = 6) induced drag is assumed equal to the takeoff phase kf = (self.phase==1)*(self.etype==1)*coeffBS.d_kj[0] + \ (self.phase==2)*(self.etype==1)*coeffBS.d_kj[1] + \ (self.phase==3)*(self.etype==1)*coeffBS.d_kj[2] + \ (self.phase==4)*(self.etype==1)*coeffBS.d_kj[3] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt>=450)*coeffBS.d_kj[4] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt<450)*coeffBS.d_kj[5] + \ (self.phase==6)*(self.etype==1)*coeffBS.d_kj[0] + \ (self.phase==1)*(self.etype==2)*coeffBS.d_kt[0] + \ (self.phase==2)*(self.etype==2)*coeffBS.d_kt[1] + \ (self.phase==3)*(self.etype==2)*coeffBS.d_kt[2] + \ (self.phase==4)*(self.etype==2)*coeffBS.d_kt[3] + \ (self.phase==5)*(self.etype==2)*(bs.traf.alt>=450)*coeffBS.d_kt[4] + \ (self.phase==5)*(self.etype==2)*(bs.traf.alt<450)*coeffBS.d_kt[5] # drag coefficient cd = self.CD0 * CD0f + self.k * kf * (cl * cl) # compute drag: CD = CD0 + CDi * CL^2 and D = rho/2*VTAS^2*CD*S self.D = cd * self.qS # energy share factor and crossover altitude epsalt = np.array([0.001] * bs.traf.ntraf) self.climb = np.array(bs.traf.delalt > epsalt) self.descent = np.array(bs.traf.delalt < -epsalt) # crossover altitiude bs.traf.abco = np.array(bs.traf.alt > self.atrans) bs.traf.belco = np.array(bs.traf.alt < self.atrans) # energy share factor self.ESF = esf(bs.traf.abco, bs.traf.belco, bs.traf.alt, bs.traf.M,\ self.climb, self.descent, bs.traf.delspd) # determine thrust self.Thr = (((bs.traf.vs * self.mass * g0) / (self.ESF * np.maximum(bs.traf.eps, bs.traf.tas))) + self.D) # determine thrust required to fulfill requests from pilot # self.Thr_pilot = (((bs.traf.pilot.vs*self.mass*g0)/(self.ESF*np.maximum(bs.traf.eps, bs.traf.pilot.tas))) + self.D) self.Thr_pilot = ( ((bs.traf.ap.vs * self.mass * g0) / (self.ESF * np.maximum(bs.traf.eps, bs.traf.pilot.tas))) + self.D) # maximum thrust jet (Bruenig et al., p. 66): mt_jet = self.rThr * (bs.traf.rho / rho0)**0.75 # maximum thrust prop (Raymer, p.36): mt_prop = self.P * self.eta / np.maximum(bs.traf.eps, bs.traf.tas) # merge self.maxthr = mt_jet * (self.etype == 1) + mt_prop * (self.etype == 2) # Fuel Flow # jet aircraft # ratio current thrust/rated thrust pThr = self.Thr / self.rThr # fuel flow is assumed to be proportional to thrust(Torenbeek, p.62). #For ground operations, idle thrust is used # cruise thrust is approximately equal to approach thrust ff_jet = ((pThr*self.ffto)*(self.phase!=6)*(self.phase!=3)+ \ self.ffid*(self.phase==6) + self.ffap*(self.phase==3) )*(self.etype==1) # print "FFJET", (pThr*self.ffto)*(self.phase!=6)*(self.phase!=3), self.ffid*(self.phase==6), self.ffap*(self.phase==3) # print "FFJET", ff_jet # turboprop aircraft # to be refined - f(spd) # CRUISE-ALTITUDE!!! # above cruise altitude: PSFC_CR PSFC = (((self.PSFC_CR - self.PSFC_TO) / 20000.0)*bs.traf.alt + self.PSFC_TO)*(bs.traf.alt<20.000) + \ self.PSFC_CR*(bs.traf.alt >= 20.000) TSFC = PSFC * bs.traf.tas / (550.0 * self.eta) # formula p.36 Raymer is missing here! ff_prop = self.Thr * TSFC * (self.etype == 2) # combine self.ff = ff_jet + ff_prop # update mass #self.mass = self.mass - self.ff*self.dt/60. # Use fuelflow in kg/min # print bs.traf.id, self.phase, bs.traf.alt/ft, bs.traf.tas/kts, bs.traf.cas/kts, bs.traf.M, \ # self.Thr, self.D, self.ff, cl, cd, bs.traf.vs/fpm, self.ESF,self.atrans, self.maxthr, \ # self.vmto/kts, self.vmic/kts ,self.vmcr/kts, self.vmap/kts, self.vmld/kts, \ # CD0f, kf, self.hmaxact # for aircraft on the runway and taxiways we need to know, whether they # are prior or after their flight self.post_flight = np.where(self.descent, True, self.post_flight) # when landing, we would like to stop the aircraft. bs.traf.pilot.tas = np.where( (bs.traf.alt < 0.5) * (self.post_flight) * self.pf_flag, 0.0, bs.traf.pilot.tas) # the impulse for reducing the speed to 0 should only be given once, # otherwise taxiing will be impossible afterwards self.pf_flag = np.where((bs.traf.alt < 0.5) * (self.post_flight), False, self.pf_flag) return
def perf(self, simt): if abs(simt - self.t0) >= self.dt: self.t0 = simt else: return """AIRCRAFT PERFORMANCE""" # BADA version swbada = True # flight phase self.phase, self.bank = \ phases(bs.traf.alt, bs.traf.gs, bs.traf.delalt, \ bs.traf.cas, self.vmto, self.vmic, self.vmap, self.vmcr, self.vmld, bs.traf.bank, bs.traf.bphase, \ bs.traf.swhdgsel, swbada) # AERODYNAMICS # Lift qS = 0.5*bs.traf.rho*np.maximum(1.,bs.traf.tas)*np.maximum(1.,bs.traf.tas)*self.Sref cl = self.mass*g0/(qS*np.cos(self.bank))*(self.phase!=PHASE["GD"])+ 0.*(self.phase==PHASE["GD"]) # Drag # Drag Coefficient # phases TO, IC, CR cdph = self.cd0cr+self.cd2cr*(cl*cl) # phase AP # in case approach coefficients in OPF-Files are set to zero: #Use cruise values instead cdapp = np.where(self.cd0ap !=0, self.cd0ap+self.cd2ap*(cl*cl), cdph) # phase LD # in case landing coefficients in OPF-Files are set to zero: #Use cruise values instead cdld = np.where(self.cd0ld !=0, self.cd0ld+self.cd2ld*(cl*cl), cdph) # now combine phases cd = (self.phase==PHASE['TO'])*cdph + (self.phase==PHASE["IC"])*cdph + (self.phase==PHASE["CR"])*cdph \ + (self.phase==PHASE['AP'])*cdapp + (self.phase ==PHASE['LD'])*cdld # Drag: self.D = cd*qS # energy share factor and crossover altitude # conditions epsalt = np.array([0.001]*bs.traf.ntraf) climb = np.array(bs.traf.delalt > epsalt) descent = np.array(bs.traf.delalt<-epsalt) lvl = np.array(np.abs(bs.traf.delalt)<0.0001)*1 # crossover altitiude atrans = self.atranscl*climb + self.atransdes*(1-climb) bs.traf.abco = np.array(bs.traf.alt>atrans) bs.traf.belco = np.array(bs.traf.alt<atrans) # energy share factor self.ESF = esf(bs.traf.abco, bs.traf.belco, bs.traf.alt, bs.traf.M,\ climb, descent, bs.traf.delspd) # THRUST # 1. climb: max.climb thrust in ISA conditions (p. 32, BADA User Manual 3.12) # condition: delta altitude positive # temperature correction for non-ISA (as soon as applied) # ThrISA = (1-self.ctct2*(self.dtemp-self.ctct1)) # jet # condition cljet = np.logical_and.reduce([climb, self.jet]) * 1 # thrust Tj = self.ctcth1* (1-(bs.traf.alt/ft)/self.ctcth2+self.ctcth3*(bs.traf.alt/ft)*(bs.traf.alt/ft)) # combine jet and default aircraft Tjc = cljet*Tj # *ThrISA # turboprop # condition clturbo = np.logical_and.reduce([climb, self.turbo]) * 1 # thrust Tt = self.ctcth1/np.maximum(1.,bs.traf.tas/kts)*(1-(bs.traf.alt/ft)/self.ctcth2)+self.ctcth3 # merge Ttc = clturbo*Tt # *ThrISA # piston clpiston = np.logical_and.reduce([climb, self.piston])*1 Tp = self.ctcth1*(1-(bs.traf.alt/ft)/self.ctcth2)+self.ctcth3/np.maximum(1.,bs.traf.tas/kts) Tpc = clpiston*Tp # max climb thrust for futher calculations (equals maximum avaliable thrust) maxthr = Tj*self.jet + Tt*self.turbo + Tp*self.piston # 2. level flight: Thr = D. Tlvl = lvl*self.D # 3. Descent: condition: vs negative/ H>hdes: fixed formula. H<hdes: phase cr, ap, ld # above or below Hpdes? Careful! If non-ISA: ALT must be replaced by Hp! delh = (bs.traf.alt - self.hpdes) # above Hpdes: high = np.array(delh>0) Tdesh = maxthr*self.ctdesh*np.logical_and.reduce([descent, high]) # below Hpdes low = np.array(delh<0) # phase cruise Tdeslc = maxthr*self.ctdesl*np.logical_and.reduce([descent, low, (self.phase==PHASE['CR'])]) # phase approach Tdesla = maxthr*self.ctdesa*np.logical_and.reduce([descent, low, (self.phase==PHASE['AP'])]) # phase landing Tdesll = maxthr*self.ctdesld*np.logical_and.reduce([descent, low, (self.phase==PHASE['LD'])]) # phase ground: minimum descent thrust as a first approach Tgd = np.minimum.reduce([Tdesh, Tdeslc])*(self.phase==PHASE['GD']) # merge all thrust conditions T = np.maximum.reduce([Tjc, Ttc, Tpc, Tlvl, Tdesh, Tdeslc, Tdesla, Tdesll, Tgd]) # vertical speed # vertical speed. Note: ISA only ( tISA = 1 ) # for climbs: reducing factor (reduced climb power) is multiplied # cred applies below 0.8*hmax and for climbing aircraft only hcred = np.array(bs.traf.alt < (self.hmaxact*0.8)) clh = np.logical_and.reduce([hcred, climb]) cred = self.cred*clh cpred = 1-cred*((self.mmax-self.mass)/(self.mmax-self.mmin)) # switch for given vertical speed selvs if (bs.traf.selvs.any()>0.) or (bs.traf.selvs.any()<0.): # thrust = f(selvs) T = ((bs.traf.selvs!=0)*(((bs.traf.pilot.vs*self.mass*g0)/ \ (self.ESF*np.maximum(bs.traf.eps,bs.traf.tas)*cpred)) \ + self.D)) + ((bs.traf.selvs==0)*T) self.Thr = T # Fuel consumption # thrust specific fuel consumption - jet # thrust etaj = self.cf1*(1.0+(bs.traf.tas/kts)/self.cf2) # merge ej = etaj*self.jet # thrust specific fuel consumption - turboprop # thrust etat = self.cf1*(1.-(bs.traf.tas/kts)/self.cf2)*((bs.traf.tas/kts)/1000.) # merge et = etat*self.turbo # thrust specific fuel consumption for all aircraft # eta is given in [kg/(min*kN)] - convert to [kg/(min*N)] eta = np.maximum.reduce([ej, et])/1000. # nominal fuel flow - (jet & turbo) and piston # condition jet,turbo: jt = np.maximum.reduce([self.jet, self.turbo]) pdf = np.maximum.reduce ([self.piston]) fnomjt = eta*self.Thr*jt fnomp = self.cf1*pdf # merge fnom = fnomjt + fnomp # minimal fuel flow jet, turbo and piston fminjt = self.cf3*(1-(bs.traf.alt/ft)/self.cf4)*jt fminp = self.cf3*pdf #merge fmin = fminjt + fminp # cruise fuel flow jet, turbo and piston fcrjt = eta*self.Thr*self.cf_cruise*jt fcrp = self.cf1*self.cf_cruise*pdf #merge fcr = fcrjt + fcrp # approach/landing fuel flow fal = np.maximum(fnom, fmin) # designate each aircraft to its fuelflow # takeoff ffto = fnom*(self.phase==PHASE['TO']) # initial climb ffic = fnom*(self.phase==PHASE['IC'])/2 # phase cruise and climb cc = np.logical_and.reduce([climb, (self.phase==PHASE['CR'])])*1 ffcc = fnom*cc # cruise and level ffcrl = fcr*lvl # descent cruise configuration cd2 = np.logical_and.reduce ([descent, (self.phase==PHASE['CR'])])*1 ffcd = cd2*fmin # approach ffap = fal*(self.phase==PHASE['AP']) # landing ffld = fal*(self.phase==PHASE['LD']) # ground ffgd = fmin*(self.phase==PHASE['GD']) # fuel flow for each condition self.ff = np.maximum.reduce([ffto, ffic, ffcc, ffcrl, ffcd, ffap, ffld, ffgd])/60. # convert from kg/min to kg/sec # update mass self.mass = self.mass - self.ff*self.dt # Use fuelflow in kg/min # for aircraft on the runway and taxiways we need to know, whether they # are prior or after their flight self.post_flight = np.where(descent, True, self.post_flight) # when landing, we would like to stop the aircraft. bs.traf.pilot.tas = np.where((bs.traf.alt <0.5)*(self.post_flight)*self.pf_flag, 0.0, bs.traf.pilot.tas) # otherwise taxiing will be impossible afterwards # pf_flag is released so post_flight flag is only triggered once self.pf_flag = np.where ((bs.traf.alt <0.5)*(self.post_flight), False, self.pf_flag) return
def perf(self,simt): if abs(simt - self.t0) >= self.dt: self.t0 = simt else: return """Aircraft performance""" swbada = False # no-bada version # allocate aircraft to their flight phase self.phase, self.bank = \ phases(bs.traf.alt, bs.traf.gs, bs.traf.delalt, \ bs.traf.cas, self.vmto, self.vmic, self.vmap, self.vmcr, self.vmld, bs.traf.bank, bs.traf.bphase, \ bs.traf.swhdgsel,swbada) # AERODYNAMICS # compute CL: CL = 2*m*g/(VTAS^2*rho*S) self.qS = 0.5*bs.traf.rho*np.maximum(1.,bs.traf.tas)*np.maximum(1.,bs.traf.tas)*self.Sref cl = self.mass*g0/(self.qS*np.cos(self.bank))*(self.phase!=6)+ 0.*(self.phase==6) # scaling factors for CD0 and CDi during flight phases according to FAA (2005): SAGE, V. 1.5, Technical Manual # For takeoff (phase = 6) drag is assumed equal to the takeoff phase CD0f = (self.phase==1)*(self.etype==1)*coeffBS.d_CD0j[0] + \ (self.phase==2)*(self.etype==1)*coeffBS.d_CD0j[1] + \ (self.phase==3)*(self.etype==1)*coeffBS.d_CD0j[2] + \ (self.phase==4)*(self.etype==1)*coeffBS.d_CD0j[3] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt>=450.0)*coeffBS.d_CD0j[4] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt<450.0)*coeffBS.d_CD0j[5] + \ (self.phase==6)*(self.etype==1)*coeffBS.d_CD0j[0] + \ (self.phase==1)*(self.etype==2)*coeffBS.d_CD0t[0] + \ (self.phase==2)*(self.etype==2)*coeffBS.d_CD0t[1] + \ (self.phase==3)*(self.etype==2)*coeffBS.d_CD0t[2] + \ (self.phase==4)*(self.etype==2)*coeffBS.d_CD0t[3] # (self.phase==5)*(self.etype==2)*(self.alt>=450)*coeffBS.d_CD0t[4] + \ # (self.phase==5)*(self.etype==2)*(self.alt<450)*coeffBS.d_CD0t[5] # For takeoff (phase = 6) induced drag is assumed equal to the takeoff phase kf = (self.phase==1)*(self.etype==1)*coeffBS.d_kj[0] + \ (self.phase==2)*(self.etype==1)*coeffBS.d_kj[1] + \ (self.phase==3)*(self.etype==1)*coeffBS.d_kj[2] + \ (self.phase==4)*(self.etype==1)*coeffBS.d_kj[3] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt>=450)*coeffBS.d_kj[4] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt<450)*coeffBS.d_kj[5] + \ (self.phase==6)*(self.etype==1)*coeffBS.d_kj[0] + \ (self.phase==1)*(self.etype==2)*coeffBS.d_kt[0] + \ (self.phase==2)*(self.etype==2)*coeffBS.d_kt[1] + \ (self.phase==3)*(self.etype==2)*coeffBS.d_kt[2] + \ (self.phase==4)*(self.etype==2)*coeffBS.d_kt[3] + \ (self.phase==5)*(self.etype==2)*(bs.traf.alt>=450)*coeffBS.d_kt[4] + \ (self.phase==5)*(self.etype==2)*(bs.traf.alt<450)*coeffBS.d_kt[5] # drag coefficient cd = self.CD0*CD0f + self.k*kf*(cl*cl) # compute drag: CD = CD0 + CDi * CL^2 and D = rho/2*VTAS^2*CD*S self.D = cd*self.qS # energy share factor and crossover altitude epsalt = np.array([0.001]*bs.traf.ntraf) self.climb = np.array(bs.traf.delalt > epsalt) self.descent = np.array(bs.traf.delalt< -epsalt) # crossover altitiude bs.traf.abco = np.array(bs.traf.alt>self.atrans) bs.traf.belco = np.array(bs.traf.alt<self.atrans) # energy share factor self.ESF = esf(bs.traf.abco, bs.traf.belco, bs.traf.alt, bs.traf.M,\ self.climb, self.descent, bs.traf.delspd) # determine thrust self.Thr = (((bs.traf.vs*self.mass*g0)/(self.ESF*np.maximum(bs.traf.eps, bs.traf.tas))) + self.D) # determine thrust required to fulfill requests from pilot # self.Thr_pilot = (((bs.traf.pilot.vs*self.mass*g0)/(self.ESF*np.maximum(bs.traf.eps, bs.traf.pilot.tas))) + self.D) self.Thr_pilot = (((bs.traf.ap.vs*self.mass*g0)/(self.ESF*np.maximum(bs.traf.eps, bs.traf.pilot.tas))) + self.D) # maximum thrust jet (Bruenig et al., p. 66): mt_jet = self.rThr*(bs.traf.rho/rho0)**0.75 # maximum thrust prop (Raymer, p.36): mt_prop = self.P*self.eta/np.maximum(bs.traf.eps, bs.traf.tas) # merge self.maxthr = mt_jet*(self.etype==1) + mt_prop*(self.etype==2) # Fuel Flow # jet aircraft # ratio current thrust/rated thrust pThr = self.Thr/self.rThr # fuel flow is assumed to be proportional to thrust(Torenbeek, p.62). #For ground operations, idle thrust is used # cruise thrust is approximately equal to approach thrust ff_jet = ((pThr*self.ffto)*(self.phase!=6)*(self.phase!=3)+ \ self.ffid*(self.phase==6) + self.ffap*(self.phase==3) )*(self.etype==1) # print "FFJET", (pThr*self.ffto)*(self.phase!=6)*(self.phase!=3), self.ffid*(self.phase==6), self.ffap*(self.phase==3) # print "FFJET", ff_jet # turboprop aircraft # to be refined - f(spd) # CRUISE-ALTITUDE!!! # above cruise altitude: PSFC_CR PSFC = (((self.PSFC_CR - self.PSFC_TO) / 20000.0)*bs.traf.alt + self.PSFC_TO)*(bs.traf.alt<20.000) + \ self.PSFC_CR*(bs.traf.alt >= 20.000) TSFC = PSFC*bs.traf.tas/(550.0*self.eta) # formula p.36 Raymer is missing here! ff_prop = self.Thr*TSFC*(self.etype==2) # combine self.ff = np.maximum(0.0,ff_jet + ff_prop) # update mass #self.mass = self.mass - self.ff*self.dt/60. # Use fuelflow in kg/min # print bs.traf.id, self.phase, bs.traf.alt/ft, bs.traf.tas/kts, bs.traf.cas/kts, bs.traf.M, \ # self.Thr, self.D, self.ff, cl, cd, bs.traf.vs/fpm, self.ESF,self.atrans, self.maxthr, \ # self.vmto/kts, self.vmic/kts ,self.vmcr/kts, self.vmap/kts, self.vmld/kts, \ # CD0f, kf, self.hmaxact # for aircraft on the runway and taxiways we need to know, whether they # are prior or after their flight self.post_flight = np.where(self.descent, True, self.post_flight) # when landing, we would like to stop the aircraft. bs.traf.pilot.tas = np.where((bs.traf.alt <0.5)*(self.post_flight)*self.pf_flag, 0.0, bs.traf.pilot.tas) # the impulse for reducing the speed to 0 should only be given once, # otherwise taxiing will be impossible afterwards self.pf_flag = np.where ((bs.traf.alt <0.5)*(self.post_flight), False, self.pf_flag) return
def update(self, dt=settings.performance_dt): """AIRCRAFT PERFORMANCE""" # BADA version swbada = True delalt = bs.traf.selalt - bs.traf.alt # flight phase self.phase, self.bank = phases(bs.traf.alt, bs.traf.gs, delalt, bs.traf.cas, self.vmto, self.vmic, self.vmap, self.vmcr, self.vmld, bs.traf.bank, bs.traf.bphase, bs.traf.swhdgsel, swbada) # AERODYNAMICS # Lift qS = 0.5 * bs.traf.rho * np.maximum(1., bs.traf.tas) * np.maximum( 1., bs.traf.tas) * self.Sref cl = self.mass * g0 / (qS * np.cos(self.bank)) * ( self.phase != PHASE["GD"]) + 0. * (self.phase == PHASE["GD"]) # Drag # Drag Coefficient # phases TO, IC, CR cdph = self.cd0cr + self.cd2cr * (cl * cl) # phase AP # in case approach coefficients in OPF-Files are set to zero: #Use cruise values instead cdapp = np.where(self.cd0ap != 0, self.cd0ap + self.cd2ap * (cl * cl), cdph) # phase LD # in case landing coefficients in OPF-Files are set to zero: #Use cruise values instead cdld = np.where(self.cd0ld != 0, self.cd0ld + self.cd2ld * (cl * cl), cdph) # now combine phases cd = (self.phase==PHASE['TO'])*cdph + (self.phase==PHASE["IC"])*cdph + (self.phase==PHASE["CR"])*cdph \ + (self.phase==PHASE['AP'])*cdapp + (self.phase ==PHASE['LD'])*cdld # Drag: self.D = cd * qS # energy share factor and crossover altitude # conditions epsalt = np.array([0.001] * bs.traf.ntraf) climb = np.array(delalt > epsalt) descent = np.array(delalt < -epsalt) lvl = np.array(np.abs(delalt) < 0.0001) * 1 # energy share factor delspd = bs.traf.pilot.tas - bs.traf.tas selmach = bs.traf.selspd < 2.0 self.ESF = esf(bs.traf.alt, bs.traf.M, climb, descent, delspd, selmach) # THRUST # 1. climb: max.climb thrust in ISA conditions (p. 32, BADA User Manual 3.12) # condition: delta altitude positive # temperature correction for non-ISA (as soon as applied) # ThrISA = (1-self.ctct2*(self.dtemp-self.ctct1)) # jet # condition cljet = np.logical_and.reduce([climb, self.jet]) * 1 # thrust Tj = self.ctcth1 * (1 - (bs.traf.alt / ft) / self.ctcth2 + self.ctcth3 * (bs.traf.alt / ft) * (bs.traf.alt / ft)) # combine jet and default aircraft Tjc = cljet * Tj # *ThrISA # turboprop # condition clturbo = np.logical_and.reduce([climb, self.turbo]) * 1 # thrust Tt = self.ctcth1 / np.maximum(1., bs.traf.tas / kts) * ( 1 - (bs.traf.alt / ft) / self.ctcth2) + self.ctcth3 # merge Ttc = clturbo * Tt # *ThrISA # piston clpiston = np.logical_and.reduce([climb, self.piston]) * 1 Tp = self.ctcth1 * ( 1 - (bs.traf.alt / ft) / self.ctcth2) + self.ctcth3 / np.maximum( 1., bs.traf.tas / kts) Tpc = clpiston * Tp # max climb thrust for futher calculations (equals maximum avaliable thrust) maxthr = Tj * self.jet + Tt * self.turbo + Tp * self.piston # 2. level flight: Thr = D. Tlvl = lvl * (self.D + bs.traf.ax * self.mass) # 3. Descent: condition: vs negative/ H>hdes: fixed formula. H<hdes: phase cr, ap, ld # above or below Hpdes? Careful! If non-ISA: ALT must be replaced by Hp! delh = (bs.traf.alt - self.hpdes) # above Hpdes: high = np.array(delh > 0) Tdesh = maxthr * self.ctdesh * np.logical_and.reduce([descent, high]) # below Hpdes low = np.array(delh < 0) # phase cruise Tdeslc = maxthr * self.ctdesl * np.logical_and.reduce( [descent, low, (self.phase == PHASE['CR'])]) # phase approach Tdesla = maxthr * self.ctdesa * np.logical_and.reduce( [descent, low, (self.phase == PHASE['AP'])]) # phase landing Tdesll = maxthr * self.ctdesld * np.logical_and.reduce( [descent, low, (self.phase == PHASE['LD'])]) # phase ground: minimum descent thrust as a first approach Tgd = np.minimum.reduce([Tdesh, Tdeslc]) * (self.phase == PHASE['GD']) # merge all thrust conditions T = np.maximum.reduce( [Tjc, Ttc, Tpc, Tlvl, Tdesh, Tdeslc, Tdesla, Tdesll, Tgd]) # vertical speed # vertical speed. Note: ISA only ( tISA = 1 ) # for climbs: reducing factor (reduced climb power) is multiplied # cred applies below 0.8*hmax and for climbing aircraft only hcred = np.array(bs.traf.alt < (self.hmaxact * 0.8)) clh = np.logical_and.reduce([hcred, climb]) cred = self.cred * clh cpred = 1 - cred * ((self.mmax - self.mass) / (self.mmax - self.mmin)) # switch for given vertical speed selvs if (bs.traf.selvs.any() > 0.) or (bs.traf.selvs.any() < 0.): # thrust = f(selvs) T = ((bs.traf.selvs!=0)*(((bs.traf.pilot.vs*self.mass*g0)/ \ (self.ESF*np.maximum(bs.traf.eps,bs.traf.tas)*cpred)) \ + self.D)) + ((bs.traf.selvs==0)*T) self.Thr = T # Fuel consumption # thrust specific fuel consumption - jet # thrust etaj = self.cf1 * (1.0 + (bs.traf.tas / kts) / self.cf2) # merge ej = etaj * self.jet # thrust specific fuel consumption - turboprop # thrust etat = self.cf1 * (1. - (bs.traf.tas / kts) / self.cf2) * ( (bs.traf.tas / kts) / 1000.) # merge et = etat * self.turbo # thrust specific fuel consumption for all aircraft # eta is given in [kg/(min*kN)] - convert to [kg/(min*N)] eta = np.maximum.reduce([ej, et]) / 1000. # nominal fuel flow - (jet & turbo) and piston # condition jet,turbo: jt = np.maximum.reduce([self.jet, self.turbo]) pdf = np.maximum.reduce([self.piston]) fnomjt = eta * self.Thr * jt fnomp = self.cf1 * pdf # merge fnom = fnomjt + fnomp # minimal fuel flow jet, turbo and piston fminjt = self.cf3 * (1 - (bs.traf.alt / ft) / self.cf4) * jt fminp = self.cf3 * pdf #merge fmin = fminjt + fminp # cruise fuel flow jet, turbo and piston fcrjt = eta * self.Thr * self.cf_cruise * jt fcrp = self.cf1 * self.cf_cruise * pdf #merge fcr = fcrjt + fcrp # approach/landing fuel flow fal = np.maximum(fnom, fmin) # designate each aircraft to its fuelflow # takeoff ffto = fnom * (self.phase == PHASE['TO']) # initial climb ffic = fnom * (self.phase == PHASE['IC']) / 2 # phase cruise and climb cc = np.logical_and.reduce([climb, (self.phase == PHASE['CR'])]) * 1 ffcc = fnom * cc # cruise and level ffcrl = fcr * lvl # descent cruise configuration cd2 = np.logical_and.reduce([descent, (self.phase == PHASE['CR'])]) * 1 ffcd = cd2 * fmin # approach ffap = fal * (self.phase == PHASE['AP']) # landing ffld = fal * (self.phase == PHASE['LD']) # ground ffgd = fmin * (self.phase == PHASE['GD']) # fuel flow for each condition self.fuelflow = np.maximum.reduce([ ffto, ffic, ffcc, ffcrl, ffcd, ffap, ffld, ffgd ]) / 60. # convert from kg/min to kg/sec # update mass self.mass -= self.fuelflow * dt # Use fuelflow in kg/min # for aircraft on the runway and taxiways we need to know, whether they # are prior or after their flight self.post_flight = np.where(descent, True, self.post_flight) # when landing, we would like to stop the aircraft. bs.traf.pilot.tas = np.where( (bs.traf.alt < 0.5) * (self.post_flight) * self.pf_flag, 0.0, bs.traf.pilot.tas) # otherwise taxiing will be impossible afterwards # pf_flag is released so post_flight flag is only triggered once self.pf_flag = np.where((bs.traf.alt < 0.5) * (self.post_flight), False, self.pf_flag) return
def update(self, dt): ''' Periodic update function for performance calculations. ''' swbada = False # no-bada version delalt = bs.traf.selalt - bs.traf.alt # allocate aircraft to their flight phase self.phase, self.bank = phases(bs.traf.alt, bs.traf.gs, delalt, bs.traf.cas, self.vmto, self.vmic, self.vmap, self.vmcr, self.vmld, bs.traf.ap.bankdef, bs.traf.bphase, bs.traf.swhdgsel, swbada) # AERODYNAMICS # compute CL: CL = 2*m*g/(VTAS^2*rho*S) self.qS = 0.5 * bs.traf.rho * np.maximum(1., bs.traf.tas) * np.maximum( 1., bs.traf.tas) * self.Sref cl = self.mass * g0 / (self.qS * np.cos(self.bank)) * ( self.phase != 6) + 0. * (self.phase == 6) # scaling factors for CD0 and CDi during flight phases according to FAA (2005): SAGE, V. 1.5, Technical Manual # For takeoff (phase = 6) drag is assumed equal to the takeoff phase CD0f = (self.phase==1)*(self.etype==1)*coeffBS.d_CD0j[0] + \ (self.phase==2)*(self.etype==1)*coeffBS.d_CD0j[1] + \ (self.phase==3)*(self.etype==1)*coeffBS.d_CD0j[2] + \ (self.phase==4)*(self.etype==1)*coeffBS.d_CD0j[3] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt>=450.0)*coeffBS.d_CD0j[4] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt<450.0)*coeffBS.d_CD0j[5] + \ (self.phase==6)*(self.etype==1)*coeffBS.d_CD0j[0] + \ (self.phase==1)*(self.etype==2)*coeffBS.d_CD0t[0] + \ (self.phase==2)*(self.etype==2)*coeffBS.d_CD0t[1] + \ (self.phase==3)*(self.etype==2)*coeffBS.d_CD0t[2] + \ (self.phase==4)*(self.etype==2)*coeffBS.d_CD0t[3] # (self.phase==5)*(self.etype==2)*(self.alt>=450)*coeffBS.d_CD0t[4] + \ # (self.phase==5)*(self.etype==2)*(self.alt<450)*coeffBS.d_CD0t[5] # For takeoff (phase = 6) induced drag is assumed equal to the takeoff phase kf = (self.phase==1)*(self.etype==1)*coeffBS.d_kj[0] + \ (self.phase==2)*(self.etype==1)*coeffBS.d_kj[1] + \ (self.phase==3)*(self.etype==1)*coeffBS.d_kj[2] + \ (self.phase==4)*(self.etype==1)*coeffBS.d_kj[3] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt>=450)*coeffBS.d_kj[4] + \ (self.phase==5)*(self.etype==1)*(bs.traf.alt<450)*coeffBS.d_kj[5] + \ (self.phase==6)*(self.etype==1)*coeffBS.d_kj[0] + \ (self.phase==1)*(self.etype==2)*coeffBS.d_kt[0] + \ (self.phase==2)*(self.etype==2)*coeffBS.d_kt[1] + \ (self.phase==3)*(self.etype==2)*coeffBS.d_kt[2] + \ (self.phase==4)*(self.etype==2)*coeffBS.d_kt[3] + \ (self.phase==5)*(self.etype==2)*(bs.traf.alt>=450)*coeffBS.d_kt[4] + \ (self.phase==5)*(self.etype==2)*(bs.traf.alt<450)*coeffBS.d_kt[5] # drag coefficient cd = self.CD0 * CD0f + self.k * kf * (cl * cl) # compute drag: CD = CD0 + CDi * CL^2 and D = rho/2*VTAS^2*CD*S self.D = cd * self.qS # energy share factor and crossover altitude epsalt = np.array([0.001] * bs.traf.ntraf) climb = np.array(delalt > epsalt) descent = np.array(delalt < -epsalt) # energy share factor delspd = bs.traf.aporasas.tas - bs.traf.tas selmach = bs.traf.selspd < 2.0 self.ESF = esf(bs.traf.alt, bs.traf.M, climb, descent, delspd, selmach) # determine thrust self.thrust = (((bs.traf.vs * self.mass * g0) / (self.ESF * np.maximum(bs.traf.eps, bs.traf.tas))) + self.D) # determine thrust required to fulfill requests from pilot # self.thrust_pilot = (((bs.traf.aporasas.vs*self.mass*g0)/(self.ESF*np.maximum(bs.traf.eps, bs.traf.aporasas.tas))) + self.D) self.thrust_pilot = ( ((bs.traf.ap.vs * self.mass * g0) / (self.ESF * np.maximum(bs.traf.eps, bs.traf.aporasas.tas))) + self.D) # maximum thrust jet (Bruenig et al., p. 66): mt_jet = self.rated_thrust * (bs.traf.rho / rho0)**0.75 # maximum thrust prop (Raymer, p.36): mt_prop = self.P * self.eta / np.maximum(bs.traf.eps, bs.traf.tas) # merge self.maxthr = mt_jet * (self.etype == 1) + mt_prop * (self.etype == 2) # Fuel Flow # jet aircraft # ratio current thrust/rated thrust pThr = self.thrust / self.rated_thrust # fuel flow is assumed to be proportional to thrust(Torenbeek, p.62). #For ground operations, idle thrust is used # cruise thrust is approximately equal to approach thrust ff_jet = ((pThr*self.ffto)*(self.phase!=6)*(self.phase!=3)+ \ self.ffid*(self.phase==6) + self.ffap*(self.phase==3) )*(self.etype==1) # print "FFJET", (pThr*self.ffto)*(self.phase!=6)*(self.phase!=3), self.ffid*(self.phase==6), self.ffap*(self.phase==3) # print "FFJET", ff_jet # turboprop aircraft # to be refined - f(spd) # CRUISE-ALTITUDE!!! # above cruise altitude: PSFC_CR PSFC = (((self.PSFC_CR - self.PSFC_TO) / 20000.0)*bs.traf.alt + self.PSFC_TO)*(bs.traf.alt<20.000) + \ self.PSFC_CR*(bs.traf.alt >= 20.000) TSFC = PSFC * bs.traf.tas / (550.0 * self.eta) # formula p.36 Raymer is missing here! ff_prop = self.thrust * TSFC * (self.etype == 2) # combine self.fuelflow = np.maximum(0.0, ff_jet + ff_prop) # update mass self.mass -= self.fuelflow * dt # print bs.traf.id, self.phase, bs.traf.alt/ft, bs.traf.tas/kts, bs.traf.cas/kts, bs.traf.M, \ # self.thrust, self.D, self.ff, cl, cd, bs.traf.vs/fpm, self.ESF,self.atrans, self.maxthr, \ # self.vmto/kts, self.vmic/kts ,self.vmcr/kts, self.vmap/kts, self.vmld/kts, \ # CD0f, kf, self.hmaxact # for aircraft on the runway and taxiways we need to know, whether they # are prior or after their flight self.post_flight = np.where(descent, True, self.post_flight) # when landing, we would like to stop the aircraft. bs.traf.aporasas.tas = np.where( (bs.traf.alt < 0.5) * (self.post_flight) * self.pf_flag, 0.0, bs.traf.aporasas.tas) # the impulse for reducing the speed to 0 should only be given once, # otherwise taxiing will be impossible afterwards self.pf_flag = np.where((bs.traf.alt < 0.5) * (self.post_flight), False, self.pf_flag) # define acceleration: aircraft taxiing and taking off use ground acceleration, # landing aircraft use ground deceleration, others use standard acceleration self.axmax = ((self.phase == PHASE['IC']) + (self.phase == PHASE['CR']) + (self.phase == PHASE['AP']) + (self.phase == PHASE['LD'])) * 0.5 \ + ((self.phase == PHASE['TO']) + (self.phase == PHASE['GD'])*(1-self.post_flight)) * self.gr_acc \ + (self.phase == PHASE['GD']) * self.post_flight * self.gr_dec