def UpdateAirSpeed(self, simdt, simt): # Compute horizontal acceleration delta_spd = self.pilot.tas - self.tas need_ax = np.abs(delta_spd) > kts # small threshold self.ax = need_ax * np.sign(delta_spd) * self.perf.acceleration() # Update velocities self.tas = self.tas + self.ax * simdt self.cas = vtas2cas(self.tas, self.alt) self.M = vtas2mach(self.tas, self.alt) # Turning turnrate = np.degrees(g0 * np.tan(self.bank) / np.maximum(self.tas, self.eps)) delhdg = (self.pilot.hdg - self.hdg + 180) % 360 - 180 # [deg] self.swhdgsel = np.abs(delhdg) > np.abs(1.5 * simdt * turnrate) # Update heading self.hdg = (self.hdg + np.where( self.swhdgsel, simdt * turnrate * np.sign(delhdg), delhdg)) % 360.0 # Update vertical speed delta_alt = self.pilot.alt - self.alt self.swaltsel = np.abs(delta_alt) > np.maximum( 10 * ft, np.abs(2 * simdt * np.abs(self.vs))) target_vs = self.swaltsel * np.sign(delta_alt) * np.abs(self.pilot.vs) delta_vs = target_vs - self.vs # print(delta_vs / fpm) need_az = np.abs(delta_vs) > 300 * fpm # small threshold self.az = need_az * np.sign(delta_vs) * ( 300 * fpm) # fixed vertical acc approx 1.6 m/s^2 self.vs = np.where(need_az, self.vs + self.az * simdt, target_vs) self.vs = np.where(np.isfinite(self.vs), self.vs, 0) # fix vs nan issue
def UpdateAirSpeed(self, simdt, simt): # Acceleration self.delspd = self.pilot.tas - self.tas swspdsel = np.abs(self.delspd) > 0.4 # <1 kts = 0.514444 m/s ax = self.perf.acceleration(simdt) # Update velocities self.tas = np.where(swspdsel, \ self.tas + swspdsel * ax * np.sign(self.delspd) * simdt,\ self.pilot.tas) self.cas = vtas2cas(self.tas, self.alt) self.M = vtas2mach(self.tas, self.alt) # Turning turnrate = np.degrees(g0 * np.tan(self.bank) / np.maximum(self.tas, self.eps)) delhdg = (self.pilot.hdg - self.hdg + 180.) % 360 - 180. # [deg] self.swhdgsel = np.abs(delhdg) > np.abs(2. * simdt * turnrate) # Update heading self.hdg = (self.hdg + simdt * turnrate * self.swhdgsel * np.sign(delhdg)) % 360. # Update vertical speed delalt = self.pilot.alt - self.alt self.swaltsel = np.abs(delalt) > np.maximum(10 * ft, np.abs(2. * simdt * np.abs(self.vs))) self.vs = self.swaltsel * np.sign(delalt) * np.abs(self.pilot.vs)
def UpdateAirSpeed(self, simdt, simt): # Compute horizontal acceleration delta_spd = self.pilot.tas - self.tas need_ax = np.abs(delta_spd) > kts # small threshold self.ax = need_ax * np.sign(delta_spd) * self.perf.acceleration() # Update velocities self.tas = self.tas + self.ax * simdt self.cas = vtas2cas(self.tas, self.alt) self.M = vtas2mach(self.tas, self.alt) # Turning turnrate = np.degrees(g0 * np.tan(self.bank) / np.maximum(self.tas, self.eps)) delhdg = (self.pilot.hdg - self.hdg + 180) % 360 - 180 # [deg] self.swhdgsel = np.abs(delhdg) > np.abs(2 * simdt * turnrate) # Update heading self.hdg = (self.hdg + simdt * turnrate * self.swhdgsel * np.sign(delhdg)) % 360. # Update vertical speed delta_alt = self.pilot.alt - self.alt self.swaltsel = np.abs(delta_alt) > np.maximum(10 * ft, np.abs(2 * simdt * np.abs(self.vs))) target_vs = self.swaltsel * np.sign(delta_alt) * np.abs(self.pilot.vs) delta_vs = target_vs - self.vs # print(delta_vs / fpm) need_az = np.abs(delta_vs) > 300 * fpm # small threshold self.az = need_az * np.sign(delta_vs) * (300 * fpm) # fixed vertical acc approx 1.6 m/s^2 self.vs = np.where(need_az, self.vs+self.az*simdt, target_vs) self.vs = np.where(np.isfinite(self.vs), self.vs, 0) # fix vs nan issue
def update_airspeed(self): # Compute horizontal acceleration delta_spd = self.aporasas.tas - self.tas ax = self.perf.acceleration() need_ax = np.abs(delta_spd) > np.abs(bs.sim.simdt * ax) self.ax = need_ax * np.sign(delta_spd) * ax # Update velocities self.tas = np.where(need_ax, self.tas + self.ax * bs.sim.simdt, self.aporasas.tas) self.cas = vtas2cas(self.tas, self.alt) self.M = vtas2mach(self.tas, self.alt) # Turning turnrate = np.degrees(g0 * np.tan(np.where(self.aphi>self.eps,self.aphi,self.bank) \ / np.maximum(self.tas, self.eps))) delhdg = (self.aporasas.hdg - self.hdg + 180) % 360 - 180 # [deg] self.swhdgsel = np.abs(delhdg) > np.abs(bs.sim.simdt * turnrate) # Update heading self.hdg = np.where(self.swhdgsel, self.hdg + bs.sim.simdt * turnrate * np.sign(delhdg), self.aporasas.hdg) % 360.0 # Update vertical speed delta_alt = self.aporasas.alt - self.alt self.swaltsel = np.abs(delta_alt) > np.maximum( 10 * ft, np.abs(2 * np.abs(bs.sim.simdt * self.vs))) target_vs = self.swaltsel * np.sign(delta_alt) * np.abs(self.aporasas.vs) delta_vs = target_vs - self.vs # print(delta_vs / fpm) need_az = np.abs(delta_vs) > 300 * fpm # small threshold self.az = need_az * np.sign(delta_vs) * (300 * fpm) # fixed vertical acc approx 1.6 m/s^2 self.vs = np.where(need_az, self.vs+self.az*bs.sim.simdt, target_vs) self.vs = np.where(np.isfinite(self.vs), self.vs, 0) # fix vs nan issue
def inflight(v, h, vs, thr0): """Compute thrust ration for inflight""" def dfunc(mratio): d = -0.4204 * mratio + 1.0824 return d def nfunc(roc): n = 2.667e-05 * roc + 0.8633 return n def mfunc(vratio, roc): m = -1.2043e-1 * vratio - 8.8889e-9 * roc**2 + 2.4444e-5 * roc + 4.7379e-1 return m roc = np.abs(np.asarray(vs / aero.fpm)) v = np.where(v < 10, 10, v) mach = aero.vtas2mach(v, h) vcas = aero.vtas2cas(v, h) p = aero.vpressure(h) p10 = aero.vpressure(10000 * aero.ft) p35 = aero.vpressure(35000 * aero.ft) # approximate thrust at top of climb (REF 2) F35 = (200 + 0.2 * thr0 / 4.448) * 4.448 mach_ref = 0.8 vcas_ref = aero.vmach2cas(mach_ref, 35000 * aero.ft) # segment 3: alt > 35000: d = dfunc(mach / mach_ref) b = (mach / mach_ref)**(-0.11) ratio_seg3 = d * np.log(p / p35) + b # segment 2: 10000 < alt <= 35000: a = (vcas / vcas_ref)**(-0.1) n = nfunc(roc) ratio_seg2 = a * (p / p35)**(-0.355 * (vcas / vcas_ref) + n) # segment 1: alt <= 10000: F10 = F35 * a * (p10 / p35)**(-0.355 * (vcas / vcas_ref) + n) m = mfunc(vcas / vcas_ref, roc) ratio_seg1 = m * (p / p35) + (F10 / F35 - m * (p10 / p35)) ratio = np.where( h > 35000 * aero.ft, ratio_seg3, np.where(h > 10000 * aero.ft, ratio_seg2, ratio_seg1), ) # convert to maximum static thrust ratio ratio_F0 = ratio * F35 / thr0 return ratio_F0
def compute_thrust_ratio(phase, bpr, v, h, unit='SI'): """Computer the dynamic thrust based on engine bypass-ratio, static maximum thrust, aircraft true airspeed, and aircraft altitude Args: phase (int or 1D-array): phase of flight, option: phase.[NA, TO, IC, CL, CR, DE, FA, LD, GD] bpr (int or 1D-array): engine bypass ratio v (int or 1D-array): aircraft true airspeed h (int or 1D-array): aircraft altitude Returns: int or 1D-array: thust in N """ n = len(phase) if unit == 'EP': v = v * aero.kts h = h * aero.ft G0 = 0.0606 * bpr + 0.6337 Mach = aero.vtas2mach(v, h) P0 = aero.p0 P = aero.vpressure(h) PP = P / P0 # thrust ratio at take off ratio_takeoff = 1 - 0.377 * (1+bpr) / np.sqrt((1+0.82*bpr)*G0) * Mach \ + (0.23 + 0.19 * np.sqrt(bpr)) * Mach**2 # thrust ratio for climb and cruise A = -0.4327 * PP**2 + 1.3855 * PP + 0.0472 Z = 0.9106 * PP**3 - 1.7736 * PP**2 + 1.8697 * PP X = 0.1377 * PP**3 - 0.4374 * PP**2 + 1.3003 * PP ratio_inflight = A - 0.377 * (1+bpr) / np.sqrt((1+0.82*bpr)*G0) * Z * Mach \ + (0.23 + 0.19 * np.sqrt(bpr)) * X * Mach**2 # thrust ratio for descent, considering 15% of inflight model thrust ratio_idle = 0.15 * ratio_inflight # thrust ratio array # LD and GN assume ZERO thrust tr = np.zeros(n) tr = np.where(phase == ph.TO, ratio_takeoff, tr) tr = np.where((phase == ph.IC) | (phase == ph.CL) | (phase == ph.CR), ratio_inflight, tr) tr = np.where(phase == ph.DE, ratio_idle, tr) return tr
def inflight(v, h, vs, thr0): """Compute thrust ration for inflight""" def dfunc(mratio): d = -0.4204 * mratio + 1.0824 return d def nfunc(roc): n = 2.667e-05 * roc + 0.8633 return n def mfunc(vratio, roc): m = -1.2043e-1 * vratio - 8.8889e-9 * roc**2 + 2.4444e-5 * roc + 4.7379e-1 return m roc = np.abs(np.asarray(vs/aero.fpm)) v = np.where(v < 10, 10, v) mach = aero.vtas2mach(v, h) vcas = aero.vtas2cas(v, h) p = aero.vpressure(h) p10 = aero.vpressure(10000*aero.ft) p35 = aero.vpressure(35000*aero.ft) # approximate thrust at top of climb (REF 2) F35 = (200 + 0.2 * thr0/4.448) * 4.448 mach_ref = 0.8 vcas_ref = aero.vmach2cas(mach_ref, 35000*aero.ft) # segment 3: alt > 35000: d = dfunc(mach/mach_ref) b = (mach / mach_ref) ** (-0.11) ratio_seg3 = d * np.log(p/p35) + b # segment 2: 10000 < alt <= 35000: a = (vcas / vcas_ref) ** (-0.1) n = nfunc(roc) ratio_seg2 = a * (p/p35) ** (-0.355 * (vcas/vcas_ref) + n) # segment 1: alt <= 10000: F10 = F35 * a * (p10/p35) ** (-0.355 * (vcas/vcas_ref) + n) m = mfunc(vcas/vcas_ref, roc) ratio_seg1 = m * (p/p35) + (F10/F35 - m * (p10/p35)) ratio = np.where(h>35000*aero.ft, ratio_seg3, np.where(h>10000*aero.ft, ratio_seg2, ratio_seg1)) # convert to maximum static thrust ratio ratio_F0 = ratio * F35 / thr0 return ratio_F0
def compute_thrust_ratio(phase, bpr, v, h, unit='SI'): """Computer the dynamic thrust based on engine bypass-ratio, static maximum thrust, aircraft true airspeed, and aircraft altitude Args: phase (int or 1D-array): phase of flight, option: phase.[NA, TO, IC, CL, CR, DE, FA, LD, GD] bpr (int or 1D-array): engine bypass ratio v (int or 1D-array): aircraft true airspeed h (int or 1D-array): aircraft altitude Returns: int or 1D-array: thust in N """ n = len(phase) if unit == 'EP': v = v * aero.kts h = h * aero.ft G0 = 0.0606 * bpr + 0.6337 Mach = aero.vtas2mach(v, h) P0 = aero.p0 P = aero.vpressure(h) PP = P / P0 # thrust ratio at take off ratio_takeoff = 1 - 0.377 * (1+bpr) / np.sqrt((1+0.82*bpr)*G0) * Mach \ + (0.23 + 0.19 * np.sqrt(bpr)) * Mach**2 # thrust ratio for climb and cruise A = -0.4327 * PP**2 + 1.3855 * PP + 0.0472 Z = 0.9106 * PP**3 - 1.7736 * PP**2 + 1.8697 * PP X = 0.1377 * PP**3 - 0.4374 * PP**2 + 1.3003 * PP ratio_inflight = A - 0.377 * (1+bpr) / np.sqrt((1+0.82*bpr)*G0) * Z * Mach \ + (0.23 + 0.19 * np.sqrt(bpr)) * X * Mach**2 # thrust ratio for descent, considering 15% of inflight model thrust ratio_idle = 0.15 * ratio_inflight # thrust ratio array # LD and GN assume ZERO thrust tr = np.zeros(n) tr = np.where(phase==ph.TO, ratio_takeoff, tr) tr = np.where((phase==ph.IC) | (phase==ph.CL) | (phase==ph.CR), ratio_inflight, tr) tr = np.where(phase==ph.DE, ratio_idle, tr) return tr
def limits(self, intent_v_tas, intent_vs, intent_h, ax): """apply limits on indent speed, vertical speed, and altitude (called in pilot module) Args: intent_v_tas (float or 1D-array): intent true airspeed intent_vs (float or 1D-array): intent vertical speed intent_h (float or 1D-array): intent altitude ax (float or 1D-array): acceleration Returns: floats or 1D-arrays: Allowed TAS, Allowed vetical rate, Allowed altitude """ allow_h = np.where(intent_h > self.hmax, self.hmax, intent_h) intent_v_cas = aero.vtas2cas(intent_v_tas, allow_h) allow_v_cas = np.where((intent_v_cas < self.vmin), self.vmin, intent_v_cas) allow_v_cas = np.where(intent_v_cas > self.vmax, self.vmax, allow_v_cas) allow_v_tas = aero.vcas2tas(allow_v_cas, allow_h) allow_v_tas = np.where( aero.vtas2mach(allow_v_tas, allow_h) > self.mmo, aero.vmach2tas(self.mmo, allow_h), allow_v_tas, ) # maximum cannot exceed MMO vs_max_with_acc = (1 - ax / self.axmax) * self.vsmax allow_vs = np.where( (intent_vs > 0) & (intent_vs > self.vsmax), vs_max_with_acc, intent_vs ) # for climb with vs larger than vsmax allow_vs = np.where( (intent_vs < 0) & (intent_vs < self.vsmin), vs_max_with_acc, allow_vs ) # for descent with vs smaller than vsmin (negative) allow_vs = np.where( (self.phase == ph.GD) & (bs.traf.tas < self.vminto), 0, allow_vs ) # takeoff aircraft # corect rotercraft speed limits ir = np.where(self.lifttype == coeff.LIFT_ROTOR)[0] allow_v_tas[ir] = np.where( (intent_v_tas[ir] < self.vmin[ir]), self.vmin[ir], intent_v_tas[ir] ) allow_v_tas[ir] = np.where( (intent_v_tas[ir] > self.vmax[ir]), self.vmax[ir], allow_v_tas[ir] ) allow_vs[ir] = np.where( (intent_vs[ir] < self.vsmin[ir]), self.vsmin[ir], intent_vs[ir] ) allow_vs[ir] = np.where( (intent_vs[ir] > self.vsmax[ir]), self.vsmax[ir], allow_vs[ir] ) return allow_v_tas, allow_vs, allow_h
def tr_takepff(bpr, v, h): """Compute thrust ration at take-off""" G0 = 0.0606 * bpr + 0.6337 Mach = aero.vtas2mach(v, h) P0 = aero.p0 P = aero.vpressure(h) PP = P / P0 A = -0.4327 * PP**2 + 1.3855 * PP + 0.0472 Z = 0.9106 * PP**3 - 1.7736 * PP**2 + 1.8697 * PP X = 0.1377 * PP**3 - 0.4374 * PP**2 + 1.3003 * PP ratio = A - 0.377 * (1+bpr) / np.sqrt((1+0.82*bpr)*G0) * Z * Mach \ + (0.23 + 0.19 * np.sqrt(bpr)) * X * Mach**2 return ratio
def update_airspeed(self): # Compute horizontal acceleration delta_spd = self.aporasas.tas - self.tas need_ax = np.abs(delta_spd) > np.abs(bs.sim.simdt * self.perf.axmax) self.ax = need_ax * np.sign(delta_spd) * self.perf.axmax # Update velocities self.tas = np.where(need_ax, self.tas + self.ax * bs.sim.simdt, self.aporasas.tas) self.cas = vtas2cas(self.tas, self.alt) self.M = vtas2mach(self.tas, self.alt) # Turning turnrate = np.degrees(g0 * np.tan(np.where(self.ap.turnphi>self.eps,self.ap.turnphi,self.ap.bankdef) \ / np.maximum(self.tas, self.eps))) delhdg = (self.aporasas.hdg - self.hdg + 180) % 360 - 180 # [deg] self.swhdgsel = np.abs(delhdg) > np.abs(bs.sim.simdt * turnrate) # Update heading self.hdg = np.where( self.swhdgsel, self.hdg + bs.sim.simdt * turnrate * np.sign(delhdg), self.aporasas.hdg) % 360.0 # Update vertical speed (alt select, capture and hold autopilot mode) delta_alt = self.aporasas.alt - self.alt # Old dead band version: # self.swaltsel = np.abs(delta_alt) > np.maximum( # 10 * ft, np.abs(2 * bs.sim.simdt * self.vs)) # Update version: time based engage of altitude capture (to adapt for UAV vs airliner scale) self.swaltsel = np.abs(delta_alt) > 1.05*np.maximum(np.abs(bs.sim.simdt * self.aporasas.vs), \ np.abs(bs.sim.simdt * self.vs)) target_vs = self.swaltsel * np.sign(delta_alt) * np.abs( self.aporasas.vs) delta_vs = target_vs - self.vs # print(delta_vs / fpm) need_az = np.abs(delta_vs) > 300 * fpm # small threshold self.az = need_az * np.sign(delta_vs) * ( 300 * fpm) # fixed vertical acc approx 1.6 m/s^2 self.vs = np.where(need_az, self.vs + self.az * bs.sim.simdt, target_vs) self.vs = np.where(np.isfinite(self.vs), self.vs, 0) # fix vs nan issue
def inflight(v, h, vs, thr0): """Compute thrust ration for inflight""" def dfunc(mratio): d = np.where( mratio<0.85, 0.73, np.where( mratio<0.92, 0.73+(0.69-0.73)/(0.92-0.85)*(mratio-0.85), np.where( mratio<1.08, 0.66+(0.63-0.66)/(1.08-1.00)*(mratio-1.00), np.where( mratio<1.15, 0.63+(0.60-0.63)/(1.15-1.08)*(mratio-1.08), 0.60 ) ) ) ) return d def nfunc(roc): n = np.where(roc<1500, 0.89, np.where(roc<2500, 0.93, 0.97)) return n def mfunc(vratio, roc): m = np.where( vratio<0.67, 0.4, np.where( vratio<0.75, 0.39, np.where( vratio<0.83, 0.38, np.where( vratio<0.92, 0.37, 0.36 ) ) ) ) m = np.where( roc<1500, m-0.06, np.where( roc<2500, m-0.01, m ) ) return m roc = np.abs(np.asarray(vs/aero.fpm)) v = np.where(v < 10, 10, v) mach = aero.vtas2mach(v, h) vcas = aero.vtas2cas(v, h) p = aero.vpressure(h) p10 = aero.vpressure(10000*aero.ft) p35 = aero.vpressure(35000*aero.ft) # approximate thrust at top of climb (REF 2) F35 = (200 + 0.2 * thr0/4.448) * 4.448 mach_ref = 0.8 vcas_ref = aero.vmach2cas(mach_ref, 35000*aero.ft) # segment 3: alt > 35000: d = dfunc(mach/mach_ref) b = (mach / mach_ref) ** (-0.11) ratio_seg3 = d * np.log(p/p35) + b # segment 2: 10000 < alt <= 35000: a = (vcas / vcas_ref) ** (-0.1) n = nfunc(roc) ratio_seg2 = a * (p/p35) ** (-0.355 * (vcas/vcas_ref) + n) # segment 1: alt <= 10000: F10 = F35 * a * (p10/p35) ** (-0.355 * (vcas/vcas_ref) + n) m = mfunc(vcas/vcas_ref, roc) ratio_seg1 = m * (p/p35) + (F10/F35 - m * (p10/p35)) ratio = np.where(h>35000*aero.ft, ratio_seg3, np.where(h>10000*aero.ft, ratio_seg2, ratio_seg1)) # convert to maximum static thrust ratio ratio_F0 = ratio * F35 / thr0 return ratio_F0
def inflight(v, h, vs, thr0): """Compute thrust ration for inflight""" def dfunc(mratio): d = np.where( mratio < 0.85, 0.73, np.where( mratio < 0.92, 0.73 + (0.69 - 0.73) / (0.92 - 0.85) * (mratio - 0.85), np.where( mratio < 1.08, 0.66 + (0.63 - 0.66) / (1.08 - 1.00) * (mratio - 1.00), np.where( mratio < 1.15, 0.63 + (0.60 - 0.63) / (1.15 - 1.08) * (mratio - 1.08), 0.60)))) return d def nfunc(roc): n = np.where(roc < 1500, 0.89, np.where(roc < 2500, 0.93, 0.97)) return n def mfunc(vratio, roc): m = np.where( vratio < 0.67, 0.4, np.where( vratio < 0.75, 0.39, np.where(vratio < 0.83, 0.38, np.where(vratio < 0.92, 0.37, 0.36)))) m = np.where(roc < 1500, m - 0.06, np.where(roc < 2500, m - 0.01, m)) return m roc = np.abs(np.asarray(vs / aero.fpm)) v = np.where(v < 10, 10, v) mach = aero.vtas2mach(v, h) vcas = aero.vtas2cas(v, h) p = aero.vpressure(h) p10 = aero.vpressure(10000 * aero.ft) p35 = aero.vpressure(35000 * aero.ft) # approximate thrust at top of climb (REF 2) F35 = (200 + 0.2 * thr0 / 4.448) * 4.448 mach_ref = 0.8 vcas_ref = aero.vmach2cas(mach_ref, 35000 * aero.ft) # segment 3: alt > 35000: d = dfunc(mach / mach_ref) b = (mach / mach_ref)**(-0.11) ratio_seg3 = d * np.log(p / p35) + b # segment 2: 10000 < alt <= 35000: a = (vcas / vcas_ref)**(-0.1) n = nfunc(roc) ratio_seg2 = a * (p / p35)**(-0.355 * (vcas / vcas_ref) + n) # segment 1: alt <= 10000: F10 = F35 * a * (p10 / p35)**(-0.355 * (vcas / vcas_ref) + n) m = mfunc(vcas / vcas_ref, roc) ratio_seg1 = m * (p / p35) + (F10 / F35 - m * (p10 / p35)) ratio = np.where(h > 35000 * aero.ft, ratio_seg3, np.where(h > 10000 * aero.ft, ratio_seg2, ratio_seg1)) # convert to maximum static thrust ratio ratio_F0 = ratio * F35 / thr0 return ratio_F0