def __init__(self, ac, eng=None): """Initialize FuelFlow object. Args: ac (string): ICAO aircraft type (for example: A320). eng (string): Engine type (for example: CFM56-5A3). Leave empty to use the default engine specified by in the aircraft database. """ self.aircraft = prop.aircraft(ac) if eng is None: eng = self.aircraft["engine"]["default"] self.engine = prop.engine(eng) self.thrust = Thrust(ac, eng) self.drag = Drag(ac) c3, c2, c1 = ( self.engine["fuel_c3"], self.engine["fuel_c2"], self.engine["fuel_c1"], ) # print(c3,c2,c1) self.fuel_flow_model = lambda x: c3 * x**3 + c2 * x**2 + c1 * x
def test_thrust_value_b734(self): nn = 5 p = om.Problem() tas = np.linspace(0, 100, nn) tas_kt = tas * 1.94384 alt = 0 ivc = p.model.add_subsystem('ivc', subsys=om.IndepVarComp(), promotes_outputs=['*']) ivc.add_output(name='tas', val=tas, units='m/s') ivc.add_output(name='alt', val=alt, units='m') p.model.connect('alt', ['atmos.h', 'propulsion.elevation']) p.model.connect('tas', ['propulsion.tas']) p.model.add_subsystem(name='atmos', subsys=USatm1976Comp(num_nodes=1)) p.model.connect('atmos.sos', 'propulsion.sos') p.model.connect('atmos.pres', ['propulsion.p_amb']) p.model.add_subsystem(name='propulsion', subsys=PropulsionGroup( num_nodes=nn, airplane=self.airplane_734)) p.setup() p.run_model() thrust = Thrust(ac='B734') reference_thrust = [thrust.takeoff(tas=v, alt=alt) for v in tas_kt] assert_near_equal(p.get_val('propulsion.thrust'), reference_thrust, tolerance=0.001)
def plot(): # Thrust = Thrust('A320', 'CFM56-5B4') thrust = Thrust('A320', 'V2500-A1') fig = plt.figure(figsize=(10,8)) ax = fig.add_subplot(111, projection='3d') tas = np.arange(0, 500, 20) alt = np.arange(0, 35000, 2000) x, y = np.meshgrid(tas, alt) thr_to = thrust.takeoff(x, y) thr_cl = thrust.climb(x, y, 2000) c1, c2, c3 = .14231E+06, .51680E+05, .56809E-10 thr_bada = c1 * (1 - y / c2 + c3 * y**2) plt.title('inflight') ax.plot_wireframe(x, y, thr_to, color='r', label='OpenAP-Thrust-TO') ax.plot_wireframe(x, y, thr_cl, color='g', label='Open-Thrust-CL') ax.plot_wireframe(x, y, thr_bada, color='b', label='BADA3') ax.set_xlabel('tas (kts)') ax.set_ylabel('alt (ft)') ax.set_zlabel('thr (N)') # ax.view_init(20, 40) ax.legend() plt.tight_layout() plt.show()
def __init__(self, aircraft_name, write_output: bool = False): self.write = write_output # General Variables self.aircraft_data = prop.aircraft(aircraft_name) self.dt = 1.0 / 60.0 # simulation timestep 60 per seconds aircraft_txt = open(f"./data/{aircraft_name}.json", 'r').read() self.aircraft = json.loads(aircraft_txt) eng_name = self.aircraft_data["engine"]["default"] self.ac_thrust = Thrust(ac=self.aircraft["Name"], eng=eng_name) # self.ac_thrust = Thrust(f"./data/{aircraft_name}_thrust.csv") # Performance Variables (all unit in SI except stated otherwise) self.lift_drag = LiftDrag(f"./data/{aircraft_name}_ld.csv") self.g = 9.81 self.mass = self.aircraft_data["limits"]["MTOW"] self.thrust_lever = 1.0 self.altitude = 0.0 self.pressure = 0.0 self.density = 0.0 self.temp = 0.0 self.cas = 0.0 self.tas = 0.0 self.v_y = 0.0 # vertical Speed self.vs = 0.0 # Vertical Speed [fpm] self.drag = 0.0 self.thrust = 0.0 self.lift = 0.0 self.weight = 0.0 self.t_d = 0.0 # thrust minus drag aka exceed thrust self.l_w = 0.0 # lift minus weight aka exceed lift self.pitch = 0.0 self.fpa = 0.0 self.aoa = 0.0 self.Q = 0.0 # tas² * density self.acc_x = 0.0 self.acc_y = 0.0 self.distance_x = 0.0 # total distance[m] self.d_x = 0.0 # instantaneous X distance[m] self.d_y = 0.0 # instantaneous Y distance[m] self.phase = 0 # Current phase self.cd = 0.0 self.cl = 0.0 self.drag0 = self.aircraft["Drag0"] self.lift0 = self.aircraft["Lift0"] self.gear = False self.flaps = 0 self.pitch_target = 0.0 self.pitch_rate_of_change = 3.0 # rate of change of the pitch [°/sec] self.ac_fuelflow = FuelFlow(ac=self.aircraft["Name"], eng=eng_name) if self.write: self.output = open("output.csv", 'w+') self.output.write(self.__get_header()) self.output.flush()
def __init__(self, ac): super(CruiseOptimizer, self).__init__() self.ac = ac self.aircraft = prop.aircraft(ac) self.thrust = Thrust(ac) self.fuelflow = FuelFlow(ac) self.drag = Drag(ac) # parameters to be optimized: # Mach number, altitude self.x0 = np.array([0.3, 25000 * aero.ft]) self.normfactor = calc_normfactor(self.x0) self.bounds = None self.update_bounds()
def state_update(X, dt, mdl, eng): nrow, ncol = X.shape m, eta, x, y, z, vax, vay, vz, vwx, vwy, tau = X vgx = vax + vwx vgy = vay + vwy vg = np.sqrt(vgx**2 + vgy**2) va = np.sqrt(vax**2 + vay**2) psi = np.arctan2(vax, vay) gamma = np.arcsin(vz / va) thrust = Thrust(mdl, eng) T = thrust.climb(va / aero.kts, z / aero.ft, vz / aero.fpm) drag = Drag(mdl) D = drag.clean(m, va / aero.kts, z / aero.ft) a = (eta * T - D) / m - aero.g0 * np.sin(gamma) m1 = m eta1 = eta x1 = x + vgx * dt y1 = y + vgy * dt z1 = z + vz * dt va1 = va + a * dt vax1 = va1 * np.sin(psi) vay1 = va1 * np.cos(psi) vz1 = vz vwx1 = vwx vwy1 = vwy tau1 = aero.temperature(z) X = np.array([m1, eta1, x1, y1, z1, vax1, vay1, vz1, vwx1, vwy1, tau1]) return X
class CruiseOptimizer(object): """Optimizer for cursie mach number and altitude.""" def __init__(self, ac): super(CruiseOptimizer, self).__init__() self.ac = ac self.aircraft = prop.aircraft(ac) self.thrust = Thrust(ac) self.fuelflow = FuelFlow(ac) self.drag = Drag(ac) # parameters to be optimized: # Mach number, altitude self.x0 = np.array([0.3, 25000 * aero.ft]) self.normfactor = calc_normfactor(self.x0) self.bounds = None self.update_bounds() def update_bounds(self, **kwargs): machmin = kwargs.get('machmin', 0.5) machmax = kwargs.get('machmax', self.aircraft['limits']['MMO']) hmin = kwargs.get('hmin', 25000 * aero.ft) hmax = kwargs.get('hmax', self.aircraft['limits']['ceiling']) self.bounds = np.array([[machmin, machmax], [hmin, hmax] ]) * self.normfactor.reshape(2, -1) def func_fuel(self, x, mass): mach, h = denormalize(x, self.normfactor) va = aero.mach2tas(mach, h) ff = self.fuelflow.enroute(mass, va / aero.kts, h / aero.ft) ff_m = ff / (va + 1e-3) * 1000 # print("%.03f" % mach, "%d" % (h/aero.ft), "%.05f" % ff_m) return ff_m def func_time(self, x, mass): mach, h = denormalize(x, self.normfactor) va = aero.mach2tas(mach, h) va_inv = 1 / (va + 1e-4) * 1000 # print("%.03f" % mach, "%d" % (h/aero.ft), "%.02f" % va) return va_inv def func_cons_lift(self, x, mass): mach, h = denormalize(x, self.normfactor) va = aero.mach2tas(mach, h) Tmax = self.thrust.cruise(va / aero.kts, h / aero.ft) qS = 0.5 * aero.density(h) * va**2 * self.aircraft['wing']['area'] cd0 = self.drag.polar['clean']['cd0'] k = self.drag.polar['clean']['k'] mach_crit = self.drag.polar['mach_crit'] if mach > mach_crit: cd0 += 20 * (mach - mach_crit)**4 dL2 = qS**2 * (1 / k * (Tmax / (qS + 1e-3) - cd0)) - (mass * aero.g0)**2 return dL2 def func_cons_thrust(self, x, mass): mach, h = denormalize(x, self.normfactor) va = aero.mach2tas(mach, h) D = self.drag.clean(mass, va / aero.kts, h / aero.ft) Tmax = self.thrust.cruise(va / aero.kts, h / aero.ft) dT = Tmax - D return dT def optimize(self, goal, mass): if goal == 'fuel': func = self.func_fuel elif goal == 'time': func = self.func_time else: raise RuntimeError('Optimization goal [%s] not avaiable.' % goal) x0 = self.x0 * self.normfactor res = minimize( func, x0, args=(mass, ), bounds=self.bounds, jac=lambda x, m: Jacobian(lambda x: func(x, m))(x), options={'maxiter': 200}, constraints=( { 'type': 'ineq', 'args': (mass, ), 'fun': lambda x, m: self.func_cons_thrust(x, m), 'jac': lambda x, m: Jacobian(lambda x: self.func_cons_thrust( x, m))(x) }, { 'type': 'ineq', 'args': (mass, ), 'fun': lambda x, m: self.func_cons_lift(x, m), 'jac': lambda x, m: Jacobian(lambda x: self.func_cons_lift(x, m)) (x) }, )) return res
class Performance(): """Calulate Instantaneous performance of one object""" def __init__(self, aircraft_name, write_output: bool = False): self.write = write_output # General Variables self.aircraft_data = prop.aircraft(aircraft_name) self.dt = 1.0 / 60.0 # simulation timestep 60 per seconds aircraft_txt = open(f"./data/{aircraft_name}.json", 'r').read() self.aircraft = json.loads(aircraft_txt) eng_name = self.aircraft_data["engine"]["default"] self.ac_thrust = Thrust(ac=self.aircraft["Name"], eng=eng_name) # self.ac_thrust = Thrust(f"./data/{aircraft_name}_thrust.csv") # Performance Variables (all unit in SI except stated otherwise) self.lift_drag = LiftDrag(f"./data/{aircraft_name}_ld.csv") self.g = 9.81 self.mass = self.aircraft_data["limits"]["MTOW"] self.thrust_lever = 1.0 self.altitude = 0.0 self.pressure = 0.0 self.density = 0.0 self.temp = 0.0 self.cas = 0.0 self.tas = 0.0 self.v_y = 0.0 # vertical Speed self.vs = 0.0 # Vertical Speed [fpm] self.drag = 0.0 self.thrust = 0.0 self.lift = 0.0 self.weight = 0.0 self.t_d = 0.0 # thrust minus drag aka exceed thrust self.l_w = 0.0 # lift minus weight aka exceed lift self.pitch = 0.0 self.fpa = 0.0 self.aoa = 0.0 self.Q = 0.0 # tas² * density self.acc_x = 0.0 self.acc_y = 0.0 self.distance_x = 0.0 # total distance[m] self.d_x = 0.0 # instantaneous X distance[m] self.d_y = 0.0 # instantaneous Y distance[m] self.phase = 0 # Current phase self.cd = 0.0 self.cl = 0.0 self.drag0 = self.aircraft["Drag0"] self.lift0 = self.aircraft["Lift0"] self.gear = False self.flaps = 0 self.pitch_target = 0.0 self.pitch_rate_of_change = 3.0 # rate of change of the pitch [°/sec] self.ac_fuelflow = FuelFlow(ac=self.aircraft["Name"], eng=eng_name) if self.write: self.output = open("output.csv", 'w+') self.output.write(self.__get_header()) self.output.flush() def __get_Q(self): self.pressure, self.density, self.temp = aero.atmos(self.altitude) self.Q = self.density * (self.tas**2) def __calculate_FPA(self): self.fpa = math.degrees(math.atan2(self.d_y, self.d_x)) self.aoa = self.pitch - self.fpa def __calculate_lift(self): if self.gear: self.cl += self.aircraft["Gear"]["Lift"] if self.flaps > 0: self.cl += self.aircraft["Flaps"][self.flaps - 1]["Lift"] self.lift = self.lift0\ + (0.5 * self.Q * self.aircraft["WingSpan"] * self.cl) self.lift *= 1.5 def __calculate_drag(self, new: bool = True): if self.gear: self.cd += self.aircraft["Gear"]["Drag"] if self.flaps > 0: self.cd += self.aircraft["Flaps"][self.flaps - 1]["Drag"] self.drag = self.drag0\ + (0.5 * self.Q * self.aircraft["WingSpan"] * self.cd) def __change_pitch(self) -> None: """ Change pitch in relation of pitch target change of pitch occure with a 3 degrees per seconds change. """ if self.pitch == self.pitch_target: return if self.pitch > self.pitch_target: self.pitch -= self.pitch_rate_of_change * self.dt elif self.pitch < self.pitch_target: self.pitch += self.pitch_rate_of_change * self.dt def run(self) -> bool: """Calculate aircraft performance till thrust reduction altitude Args: target_alt (float, optional): The thrust reduction altitude in meters. Defaults to 457.2m (1500.0 feets) Returns: bool: True if the phase is still valid, else False """ if self.distance_x == 0: self.aoa = self.pitch self.cl, self.cd = self.lift_drag.get_data(self.aoa) self.__get_Q() self.__change_pitch() self.__calculate_drag() self.__calculate_lift() self.g = local_gravity(50.0, self.altitude) self.weight = self.mass * self.g max_thrust = self.ac_thrust.takeoff(alt=self.altitude / aero.ft, tas=self.tas / aero.kts) idle_thrust = self.ac_thrust.descent_idle(tas=self.tas / aero.kts, alt=self.altitude / aero.ft) self.thrust = interpolate(self.thrust_lever, 0.0, 1.0, idle_thrust, max_thrust) fuelflow = self.ac_fuelflow.at_thrust(acthr=self.thrust / 2.0, alt=self.altitude / aero.ft) self.mass -= fuelflow * self.dt * 2 self.t_d = self.thrust - self.drag\ - (self.weight * math.sin(math.radians(self.pitch))) self.l_w = self.lift\ - (self.weight * math.cos(math.radians(self.pitch)))\ + (self.thrust * math.sin(math.radians(self.pitch))) acc = self.t_d / self.mass self.acc_x = acc * math.cos(math.radians(self.pitch)) self.acc_y = acc * math.sin(math.radians(self.pitch)) v_acc = self.l_w / self.mass self.acc_y += v_acc * math.cos(math.radians(self.pitch)) self.acc_x += v_acc * math.sin(math.radians(self.pitch)) self.d_x = (self.tas * self.dt) + 0.5 * self.acc_x * (self.dt**2) self.d_y = (self.v_y * self.dt) + 0.5 * self.acc_y * (self.dt**2) self.tas += self.acc_x * self.dt self.cas = aero.tas2cas(self.tas, self.altitude) self.v_y += self.acc_y * self.dt if self.altitude <= 0: self.altitude = 0 if self.d_y < 0: self.d_y = 0 if self.v_y < 0: self.v_y = 0 self.vs = 0 self.altitude += self.d_y self.distance_x += self.d_x self.vs = self.v_y / aero.fpm self.__calculate_FPA() if self.write: self.output.write(str(self)) self.output.flush() def __str__(self): return f"{self.mass},{self.altitude},{self.pressure},{self.density},"\ f"{self.temp},{self.cas},{self.tas},{self.v_y},{self.vs},"\ f"{self.drag},{self.thrust},{self.t_d},{self.pitch},"\ f"{self.fpa},{self.aoa},{self.Q},{self.acc_x},{self.acc_y},"\ f"{self.distance_x},{self.d_x},{self.d_y},{self.phase},"\ f"{self.cd},{self.drag0},{self.gear},{self.flaps},{self.cl},"\ f"{self.lift},{self.weight},{self.l_w},{self.thrust_lever},"\ f"{self.altitude / aero.ft},{self.g},{self.pitch_target}\n" def __get_header(self): return "Mass,Altitude,Pressure,Density,Temperature,Cas,Tas,Vy,VS,"\ "Drag,Thrust,T-D,Pitch,FPA,AOA,Q,AccelerationX,AccelerationY,"\ "DistanceX,Dx,Dy,Phase,Cd,Cd0,Gear,Flaps,Cl,Lift,Weight,L-W,"\ "Thrust Limit,Altitude FT,Gravity,Target Pitch\n"
class FuelFlow(object): """Fuel flow model based on ICAO emmision databank.""" def __init__(self, ac, eng=None): """Initialize FuelFlow object. Args: ac (string): ICAO aircraft type (for example: A320). eng (string): Engine type (for example: CFM56-5A3). Leave empty to use the default engine specified by in the aircraft database. """ self.aircraft = prop.aircraft(ac) if eng is None: eng = self.aircraft["engine"]["default"] self.engine = prop.engine(eng) self.thrust = Thrust(ac, eng) self.drag = Drag(ac) c3, c2, c1 = ( self.engine["fuel_c3"], self.engine["fuel_c2"], self.engine["fuel_c1"], ) # print(c3,c2,c1) self.fuel_flow_model = lambda x: c3 * x**3 + c2 * x**2 + c1 * x @ndarrayconvert def at_thrust(self, acthr, alt=0): """Compute the fuel flow at a given total thrust. Args: acthr (int or ndarray): The total net thrust of the aircraft (unit: N). alt (int or ndarray): Aircraft altitude (unit: ft). Returns: float: Fuel flow (unit: kg/s). """ n_eng = self.aircraft["engine"]["number"] engthr = acthr / n_eng ratio = engthr / self.engine["max_thrust"] ff_sl = self.fuel_flow_model(ratio) ff_corr_alt = self.engine["fuel_ch"] * (engthr / 1000) * (alt * aero.ft) ff_eng = ff_sl + ff_corr_alt fuelflow = ff_eng * n_eng return fuelflow @ndarrayconvert def takeoff(self, tas, alt=None, throttle=1): """Compute the fuel flow at takeoff. The net thrust is first estimated based on the maximum thrust model and throttle setting. Then FuelFlow.at_thrust() is called to compted the thrust. Args: tas (int or ndarray): Aircraft true airspeed (unit: kt). alt (int or ndarray): Altitude of airport (unit: ft). Defaults to sea-level. throttle (float or ndarray): The throttle setting, between 0 and 1. Defaults to 1, which is at full thrust. Returns: float: Fuel flow (unit: kg/s). """ Tmax = self.thrust.takeoff(tas=tas, alt=alt) fuelflow = throttle * self.at_thrust(Tmax) return fuelflow @ndarrayconvert def enroute(self, mass, tas, alt, path_angle=0): """Compute the fuel flow during climb, cruise, or descent. The net thrust is first estimated based on the dynamic equation. Then FuelFlow.at_thrust() is called to compted the thrust. Assuming no flap deflection and no landing gear extended. Args: mass (int or ndarray): Aircraft mass (unit: kg). tas (int or ndarray): Aircraft true airspeed (unit: kt). alt (int or ndarray): Aircraft altitude (unit: ft). path_angle (float or ndarray): Flight path angle (unit: degrees). Returns: float: Fuel flow (unit: kg/s). """ D = self.drag.clean(mass=mass, tas=tas, alt=alt, path_angle=path_angle) # Convert angles from degrees to radians. gamma = np.radians(path_angle) T = D + mass * aero.g0 * np.sin(gamma) T_idle = self.thrust.descent_idle(tas=tas, alt=alt) T = np.where(T < 0, T_idle, T) fuelflow = self.at_thrust(T, alt) # do not return value outside performance boundary, with a margin of 20% T_max = self.thrust.climb(tas=0, alt=alt, roc=0) fuelflow = np.where(T > 1.20 * T_max, np.nan, fuelflow) return fuelflow def plot_model(self, plot=True): """Plot the engine fuel model, or return the pyplot object. Args: plot (bool): Display the plot or return an object. Returns: None or pyplot object. """ import matplotlib.pyplot as plt xx = np.linspace(0, 1, 50) yy = self.fuel_flow_model(xx) # plt.scatter(self.x, self.y, color='k') plt.plot(xx, yy, "--", color="gray") if plot: plt.show() else: return plt
def __init__(self, **kwargs): self.ac = kwargs.get('ac') self.eng = kwargs.get('eng') self.time = kwargs.get('time') self.Y = kwargs.get('obs') # slightly increase the cov matrix (factor of 0.2), for better convergency self.noise = kwargs.get('noise') self.stdn = stdns[self.noise] * 1.2 self.R = np.zeros((nY, nY)) np.fill_diagonal(self.R, self.stdn**2) self.thrust = Thrust(self.ac, self.eng) self.drag = Drag(self.ac) aircraft = prop.aircraft(self.ac) self.mmin = aircraft['limits']['OEW'] self.mmax = aircraft['limits']['MTOW'] self.mrange = (self.mmin, self.mmax) self.eta_min = kwargs.get('eta_min', 0.80) self.eta_max = kwargs.get('eta_max', 1) self.kstd_m = kpm * (self.mmax - self.mmin) self.kstd_eta = kpe * (1 - self.eta_min) self.X = None self.nP = None self.now = 0 self.neff = None self.X_true = None logfn = kwargs.get('logfn', None) self.xlabels = [ 'm (kg)', '$\eta$ (-)', 'x (m)', 'y (m)', 'z (m)', '$v_{ax}$ (m/s)', '$v_{ay}$ (m/s)', '$v_z$ (m/s)', '$v_{wx}$ (m/s)', '$v_{wy}$ (m/s)', '$\\tau$ (K)' ] self.ylabels = [ 'x', 'y', 'z', '$v_{gx}$', '$v_{gy}$', '$v_z$', '$v_{wx}$', '$v_{wy}$', '$\\tau$' ] if (logfn is not None): if ('.log' not in logfn): raise RuntimeError('Log file must end with .log') self.log = root + '/smclog/' + logfn print('writing to log:', self.log) header = ['time'] + self.ylabels \ + [l+" avg" for l in self.xlabels] \ + [l+" med" for l in self.xlabels] \ + [l+" min" for l in self.xlabels] \ + [l+" max" for l in self.xlabels] with open(self.log, 'wt') as fcsv: writer = csv.writer(fcsv, delimiter=',') writer.writerow(header) else: self.log = None
class SIR(): def __init__(self, **kwargs): self.ac = kwargs.get('ac') self.eng = kwargs.get('eng') self.time = kwargs.get('time') self.Y = kwargs.get('obs') # slightly increase the cov matrix (factor of 0.2), for better convergency self.noise = kwargs.get('noise') self.stdn = stdns[self.noise] * 1.2 self.R = np.zeros((nY, nY)) np.fill_diagonal(self.R, self.stdn**2) self.thrust = Thrust(self.ac, self.eng) self.drag = Drag(self.ac) aircraft = prop.aircraft(self.ac) self.mmin = aircraft['limits']['OEW'] self.mmax = aircraft['limits']['MTOW'] self.mrange = (self.mmin, self.mmax) self.eta_min = kwargs.get('eta_min', 0.80) self.eta_max = kwargs.get('eta_max', 1) self.kstd_m = kpm * (self.mmax - self.mmin) self.kstd_eta = kpe * (1 - self.eta_min) self.X = None self.nP = None self.now = 0 self.neff = None self.X_true = None logfn = kwargs.get('logfn', None) self.xlabels = [ 'm (kg)', '$\eta$ (-)', 'x (m)', 'y (m)', 'z (m)', '$v_{ax}$ (m/s)', '$v_{ay}$ (m/s)', '$v_z$ (m/s)', '$v_{wx}$ (m/s)', '$v_{wy}$ (m/s)', '$\\tau$ (K)' ] self.ylabels = [ 'x', 'y', 'z', '$v_{gx}$', '$v_{gy}$', '$v_z$', '$v_{wx}$', '$v_{wy}$', '$\\tau$' ] if (logfn is not None): if ('.log' not in logfn): raise RuntimeError('Log file must end with .log') self.log = root + '/smclog/' + logfn print('writing to log:', self.log) header = ['time'] + self.ylabels \ + [l+" avg" for l in self.xlabels] \ + [l+" med" for l in self.xlabels] \ + [l+" min" for l in self.xlabels] \ + [l+" max" for l in self.xlabels] with open(self.log, 'wt') as fcsv: writer = csv.writer(fcsv, delimiter=',') writer.writerow(header) else: self.log = None def state_update(self, X, dt): nrow, ncol = X.shape m, eta, x, y, z, vax, vay, vz, vwx, vwy, tau = np.split( X, X.shape[1], 1) vgx = vax + vwx vgy = vay + vwy vg = np.sqrt(vgx**2 + vgy**2) va = np.sqrt(vax**2 + vay**2) psi = np.arctan2(vax, vay) gamma = np.arcsin(vz / va) T = self.thrust.climb(va / aero.kts, z / aero.ft, vz / aero.fpm) D = self.drag.clean(m, va / aero.kts, z / aero.ft) a = (eta * T - D) / m - aero.g0 * np.sin(gamma) print(np.mean(a)) m1 = m eta1 = eta x1 = x + vgx * dt y1 = y + vgy * dt z1 = z + vz * dt va1 = va + a * dt # va1 = va + a * np.cos(gamma) * dt vax1 = va1 * np.sin(psi) vay1 = va1 * np.cos(psi) evz = np.random.normal(0, sigma_vz, nrow) vz1 = alpha_vz * vz + evz.reshape(-1, 1) * dt # vz1 = alpha_vz * vz + a * np.sin(gamma) * dt + evz.reshape(-1, 1) * dt evwx = np.random.normal(0, sigma_vwx, nrow) vwx1 = alpha_vwx * vwx + evwx.reshape(-1, 1) * dt evwy = np.random.normal(0, sigma_vwy, nrow) vwy1 = alpha_vwy * vwy + evwy.reshape(-1, 1) * dt etau = np.random.normal(0, sigma_tau, nrow) tau1 = alpha_tau * tau + etau.reshape(-1, 1) * dt X = np.hstack( [m1, eta1, x1, y1, z1, vax1, vay1, vz1, vwx1, vwy1, tau1]) return X def logsm(self): if self.log is None: return t = self.now if t in self.time: idx = list(self.time).index(t) measurement = self.Y[:, idx] else: measurement = np.ones(self.Y.shape[0]) * np.nan state_avgs = np.average(self.X, weights=self.W.T, axis=0) state_meds = np.median(self.X, axis=0) # state_mins = np.percentile(self.X, 2.5, axis=0) # state_maxs = np.percentile(self.X, 95.5, axis=0) state_mins = np.min(self.X, axis=0) state_maxs = np.max(self.X, axis=0) row = np.hstack([[t], measurement, state_avgs, state_meds, state_mins, state_maxs]) with open(self.log, 'at') as fcsv: writer = csv.writer(fcsv, delimiter=',') writer.writerow(row) def compute_neff(self): self.neff = 1 / np.sum(np.square(self.W)) def printstates(self, t): # ---- Debug ---- obsv = Y2X(self.Y[:, t]) obsv[0:2] = ['*****', '****'] avgs = np.average(self.X, weights=self.W, axis=0) meds = np.median(self.X, axis=0) # mins = np.percentile(self.X, 2.5, axis=0) # maxs = np.percentile(self.X, 95.5, axis=0) mins = np.min(self.X, axis=0) maxs = np.max(self.X, axis=0) printarray(obsv, 'obsv') printarray(avgs, 'avgs') # printarray(meds, 'meds') printarray(mins, 'mins') printarray(maxs, 'maxs') def pickle_particles(self, fname): import os, pickle root = os.path.dirname(os.path.realpath(__file__)) fpkl = open(root + '/data/' + fname, 'wb') pickle.dump({'X': self.X, 'W': self.W}, fpkl) def init_particles(self, at=0, n_particles=50000): Mu0 = Y2X(self.Y[:, at]) Mu0[0:2] = [0, 0] Var0 = np.zeros((nX, nX)) np.fill_diagonal(Var0, (np.append([0, 0], self.stdn))**2) printarray(Mu0, 'Mu0') printarray(np.diag(Var0), 'Var0') self.X = np.random.multivariate_normal(Mu0, Var0, n_particles) self.nP = n_particles m_inits = np.random.uniform(self.mmin, self.mmax, n_particles) # mass-related initialization (recommended) er_mins = 1 - (1 - self.eta_min) * (self.mmax - m_inits) / (self.mmax - self.mmin) eta_inits = np.random.uniform(er_mins, self.eta_max, n_particles) # # Uniform initialization # eta_inits = np.random.uniform(self.eta_min, self.eta_max, n_particles) self.X[:, 0] = m_inits self.X[:, 1] = eta_inits self.W = np.ones(n_particles) / (n_particles) return def resample(self): """ References: J. S. Liu and R. Chen. Sequential Monte Carlo methods for dynamic systems. Journal of the American Statistical Association, 93(443):1032–1044, 1998. """ N = self.nP W = self.W idx = np.zeros(N, 'i') # take int(N*w) copies of each weight, which ensures particles with the # same weight are drawn uniformly copyn = (np.floor(N * W)).astype(int) a = np.where(copyn > 0)[0] b = copyn[a] c = np.append(0, np.cumsum(b)) for i, (i0, i1) in enumerate(zip(c[:-1], c[1:])): idx[i0:i1] = a[i] # use multinormal resample on the residual to fill up the rest. This # maximizes the variance of the samples k = c[-1] residual = W - copyn residual /= sum(residual) cumulative_sum = np.cumsum(residual) cumulative_sum[ -1] = 1. # avoid round-off errors: ensures sum is exactly one idx[k:N] = np.searchsorted(cumulative_sum, np.random.random(N - k)) return idx def run(self, processdt=1, use_bada=True): if self.X is None: raise RuntimeError( 'Particles not initialized. Run SIR.init_particles() first.') self.now = self.time[0] self.logsm() for it, tm in enumerate(self.time): print('-' * 100) self.now = tm # ===== SIR update ===== print("SIR / measurement update, time", self.now) self.printstates(it) # ---- weight update ---- Y_true = X2Y(self.X) Yt = self.Y[:, it] Y = np.ones(Y_true.shape) * Yt DY = Y - Y_true Simga_inv = np.linalg.inv(self.R) self.W *= np.exp(-0.5 * np.einsum('ij,ij->i', np.dot(DY, Simga_inv), DY)) self.W = np.where(self.X[:, 1] < self.eta_min, 0, self.W) self.W = np.where(self.X[:, 1] > self.eta_max, 0, self.W) self.W = np.where(self.X[:, 0] < self.mmin, 0, self.W) self.W = np.where(self.X[:, 0] > self.mmax, 0, self.W) self.W = self.W / np.sum(self.W) # normalize # ---- resample ---- idx = self.resample() self.X = self.X[idx, :] self.W = np.ones(self.nP) / self.nP if tm == self.time[-1]: break # ---- apply kernel ---- print((bcolors.OKGREEN + "Kernel applied: [m:%d, eta:%f]" + bcolors.ENDC) % (self.kstd_m, self.kstd_eta)) self.X[:, 0] = self.X[:, 0] + np.random.normal( 0, self.kstd_m, self.nP) self.X[:, 1] = self.X[:, 1] + np.random.normal( 0, self.kstd_eta, self.nP) epsi = np.random.normal(0, kpsi, self.nP) va = np.sqrt(self.X[:, 5]**2 + self.X[:, 6]**2) psi = np.arctan2(self.X[:, 5], self.X[:, 6]) self.X[:, 5] = va * np.sin(psi + epsi) self.X[:, 6] = va * np.cos(psi + epsi) # ===== states evolution ===== dtm = self.time[it + 1] - self.time[it] dtp = min(processdt, dtm) # process update time interval for j in range(int(dtm / dtp)): # number of process update print("process update / state evolution, time", self.now) self.now = tm + (j + 1) * dtp self.X = self.state_update(self.X, dtp) self.logsm() return
class FuelFlow(object): """Fuel flow model based on ICAO emmision databank.""" def __init__(self, ac, eng=None): """Initialize FuelFlow object. Args: ac (string): ICAO aircraft type (for example: A320). eng (string): Engine type (for example: CFM56-5A3). Leave empty to use the default engine specified by in the aircraft database. """ self.aircraft = prop.aircraft(ac) if eng is None: eng = self.aircraft['engine']['default'] self.engine = prop.engine(eng) self.thrust = Thrust(ac, eng) self.drag = Drag(ac) c3, c2, c1 = self.engine['fuel_c3'], self.engine[ 'fuel_c2'], self.engine['fuel_c1'] # print(c3,c2,c1) self.fuel_flow_model = lambda x: c3 * x**3 + c2 * x**2 + c1 * x @ndarrayconvert def at_thrust(self, acthr, alt=0): """Compute the fuel flow at a given total thrust. Args: acthr (int or ndarray): The total net thrust of the aircraft (unit: N). alt (int or ndarray): Aicraft altitude (unit: ft). Returns: float: Fuel flow (unit: kg/s). """ ratio = acthr / (self.engine['max_thrust'] * self.aircraft['engine']['number']) fuelflow = self.fuel_flow_model(ratio) * self.aircraft['engine']['number'] \ + self.engine['fuel_ch'] * (alt*aero.ft) * (acthr/1000) return fuelflow @ndarrayconvert def takeoff(self, tas, alt=None, throttle=1): """Compute the fuel flow at takeoff. The net thrust is first estimated based on the maximum thrust model and throttle setting. Then FuelFlow.at_thrust() is called to compted the thrust. Args: tas (int or ndarray): Aircraft true airspeed (unit: kt). alt (int or ndarray): Altitude of airport (unit: ft). Defaults to sea-level. throttle (float or ndarray): The throttle setting, between 0 and 1. Defaults to 1, which is at full thrust. Returns: float: Fuel flow (unit: kg/s). """ Tmax = self.thrust.takeoff(tas=tas, alt=alt) fuelflow = throttle * self.at_thrust(Tmax) return fuelflow @ndarrayconvert def enroute(self, mass, tas, alt, path_angle=0): """Compute the fuel flow during climb, cruise, or descent. The net thrust is first estimated based on the dynamic equation. Then FuelFlow.at_thrust() is called to compted the thrust. Assuming no flap deflection and no landing gear extended. Args: mass (int or ndarray): Aircraft mass (unit: kg). tas (int or ndarray): Aircraft true airspeed (unit: kt). alt (int or ndarray): Aircraft altitude (unit: ft). path_angle (float or ndarray): Flight path angle (unit: ft). Returns: float: Fuel flow (unit: kg/s). """ D = self.drag.clean(mass=mass, tas=tas, alt=alt, path_angle=path_angle) gamma = np.radians(path_angle) T = D + mass * aero.g0 * np.sin(gamma) T_idle = self.thrust.descent_idle(tas=tas, alt=alt) T = np.where(T < 0, T_idle, T) fuelflow = self.at_thrust(T, alt) return fuelflow def plot_model(self, plot=True): """Plot the engine fuel model, or return the pyplot object. Args: plot (bool): Display the plot or return an object. Returns: None or pyplot object. """ import matplotlib.pyplot as plt xx = np.linspace(0, 1, 50) yy = self.fuel_flow_model(xx) # plt.scatter(self.x, self.y, color='k') plt.plot(xx, yy, '--', color='gray') if plot: plt.show() else: return plt
import pandas as pd import numpy as np from openap import Thrust from matplotlib import pyplot as plt from mpl_toolkits.mplot3d.axes3d import Axes3D thrust = Thrust(ac='A320', eng='CFM56-5B4') print('-'*70) T = thrust.takeoff(tas=100, alt=0) print("thrust.takeoff(tas=100, alt=0)") print(T) print('-'*70) T = thrust.climb(tas=200, alt=20000, roc=1000) print("thrust.climb(tas=200, alt=20000, roc=1000)") print(T) print('-'*70) T = thrust.cruise(tas=230, alt=32000) print("thrust.cruise(tas=230, alt=32000)") print(T) print('-'*70) T = thrust.climb(tas=[200], alt=[20000], roc=[1000]) print("thrust.climb(tas=[200], alt=[20000], roc=[1000])") print(T) print('-'*70)