Coverage for docs/source/examples/performance_modeling.py: 98%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

95 statements  

1"""Modular aircraft concept""" 

2import pickle 

3import numpy as np 

4from gpkit import Model, Vectorize, parse_variables 

5 

6 

7class AircraftP(Model): 

8 """Aircraft flight physics: weight <= lift, fuel burn 

9 

10 Variables 

11 --------- 

12 Wfuel [lbf] fuel weight 

13 Wburn [lbf] segment fuel burn 

14 

15 Upper Unbounded 

16 --------------- 

17 Wburn, aircraft.wing.c, aircraft.wing.A 

18 

19 Lower Unbounded 

20 --------------- 

21 Wfuel, aircraft.W, state.mu 

22 

23 """ 

24 @parse_variables(__doc__, globals()) 

25 def setup(self, aircraft, state): 

26 self.aircraft = aircraft 

27 self.state = state 

28 

29 self.wing_aero = aircraft.wing.dynamic(aircraft.wing, state) 

30 self.perf_models = [self.wing_aero] 

31 

32 W = aircraft.W 

33 S = aircraft.wing.S 

34 

35 V = state.V 

36 rho = state.rho 

37 

38 D = self.wing_aero.D 

39 CL = self.wing_aero.CL 

40 

41 return Wburn >= 0.1*D, W + Wfuel <= 0.5*rho*CL*S*V**2, { 

42 "performance": 

43 self.perf_models} 

44 

45 

46class Aircraft(Model): 

47 """The vehicle model 

48 

49 Variables 

50 --------- 

51 W [lbf] weight 

52 

53 Upper Unbounded 

54 --------------- 

55 W 

56 

57 Lower Unbounded 

58 --------------- 

59 wing.c, wing.S 

60 """ 

61 @parse_variables(__doc__, globals()) 

62 def setup(self): 

63 self.fuse = Fuselage() 

64 self.wing = Wing() 

65 self.components = [self.fuse, self.wing] 

66 

67 return [W >= sum(c.W for c in self.components), 

68 self.components] 

69 

70 dynamic = AircraftP 

71 

72 

73class FlightState(Model): 

74 """Context for evaluating flight physics 

75 

76 Variables 

77 --------- 

78 V 40 [knots] true airspeed 

79 mu 1.628e-5 [N*s/m^2] dynamic viscosity 

80 rho 0.74 [kg/m^3] air density 

81 

82 """ 

83 @parse_variables(__doc__, globals()) 

84 def setup(self): 

85 pass 

86 

87 

88class FlightSegment(Model): 

89 """Combines a context (flight state) and a component (the aircraft) 

90 

91 Upper Unbounded 

92 --------------- 

93 Wburn, aircraft.wing.c, aircraft.wing.A 

94 

95 Lower Unbounded 

96 --------------- 

97 Wfuel, aircraft.W 

98 

99 """ 

100 def setup(self, aircraft): 

101 self.aircraft = aircraft 

102 

103 self.flightstate = FlightState() 

104 self.aircraftp = aircraft.dynamic(aircraft, self.flightstate) 

105 

106 self.Wburn = self.aircraftp.Wburn 

107 self.Wfuel = self.aircraftp.Wfuel 

108 

109 return {"aircraft performance": self.aircraftp, 

110 "flightstate": self.flightstate} 

111 

112 

113class Mission(Model): 

114 """A sequence of flight segments 

115 

116 Upper Unbounded 

117 --------------- 

118 aircraft.wing.c, aircraft.wing.A 

119 

120 Lower Unbounded 

121 --------------- 

122 aircraft.W 

123 """ 

124 def setup(self, aircraft): 

125 self.aircraft = aircraft 

126 

127 with Vectorize(4): # four flight segments 

128 self.fs = FlightSegment(aircraft) 

129 

130 Wburn = self.fs.aircraftp.Wburn 

131 Wfuel = self.fs.aircraftp.Wfuel 

132 self.takeoff_fuel = Wfuel[0] 

133 

134 return { 

135 "fuel constraints": 

136 [Wfuel[:-1] >= Wfuel[1:] + Wburn[:-1], 

137 Wfuel[-1] >= Wburn[-1]], 

138 "flight segment": 

139 self.fs} 

140 

141 

142class WingAero(Model): 

143 """Wing aerodynamics 

144 

145 Variables 

146 --------- 

147 CD [-] drag coefficient 

148 CL [-] lift coefficient 

149 e 0.9 [-] Oswald efficiency 

150 Re [-] Reynold's number 

151 D [lbf] drag force 

152 

153 Upper Unbounded 

154 --------------- 

155 D, Re, wing.A, state.mu 

156 

157 Lower Unbounded 

158 --------------- 

159 CL, wing.S, state.mu, state.rho, state.V 

160 """ 

161 @parse_variables(__doc__, globals()) 

162 def setup(self, wing, state): 

163 self.wing = wing 

164 self.state = state 

165 

166 c = wing.c 

167 A = wing.A 

168 S = wing.S 

169 rho = state.rho 

170 V = state.V 

171 mu = state.mu 

172 

173 return [D >= 0.5*rho*V**2*CD*S, 

174 Re == rho*V*c/mu, 

175 CD >= 0.074/Re**0.2 + CL**2/np.pi/A/e] 

176 

177 

178class Wing(Model): 

179 """Aircraft wing model 

180 

181 Variables 

182 --------- 

183 W [lbf] weight 

184 S [ft^2] surface area 

185 rho 1 [lbf/ft^2] areal density 

186 A 27 [-] aspect ratio 

187 c [ft] mean chord 

188 

189 Upper Unbounded 

190 --------------- 

191 W 

192 

193 Lower Unbounded 

194 --------------- 

195 c, S 

196 """ 

197 @parse_variables(__doc__, globals()) 

198 def setup(self): 

199 return [c == (S/A)**0.5, 

200 W >= S*rho] 

201 

202 dynamic = WingAero 

203 

204 

205class Fuselage(Model): 

206 """The thing that carries the fuel, engine, and payload 

207 

208 A full model is left as an exercise for the reader. 

209 

210 Variables 

211 --------- 

212 W 100 [lbf] weight 

213 

214 """ 

215 @parse_variables(__doc__, globals()) 

216 def setup(self): 

217 pass 

218 

219AC = Aircraft() 

220MISSION = Mission(AC) 

221M = Model(MISSION.takeoff_fuel, [MISSION, AC]) 

222print(M) 

223sol = M.solve(verbosity=0) 

224# save solution to some files 

225sol.savemat() 

226sol.savecsv() 

227sol.savetxt() 

228sol.save("solution.pkl") 

229# retrieve solution from a file 

230sol_loaded = pickle.load(open("solution.pkl", "rb")) 

231 

232vars_of_interest = set(AC.varkeys) 

233# note that there's two ways to access submodels 

234assert (MISSION["flight segment"]["aircraft performance"] 

235 is MISSION.fs.aircraftp) 

236vars_of_interest.update(MISSION.fs.aircraftp.unique_varkeys) 

237vars_of_interest.add(M["D"]) 

238print(sol.summary(vars_of_interest)) 

239print(sol.table(tables=["loose constraints"])) 

240 

241M.append(MISSION.fs.aircraftp.Wburn >= 0.2*MISSION.fs.aircraftp.wing_aero.D) 

242sol = M.solve(verbosity=0) 

243print(sol.diff("solution.pkl", showvars=vars_of_interest, sortbymodel=False)) 

244 

245try: 

246 from gpkit.interactive.sankey import Sankey 

247 variablesankey = Sankey(sol, M).diagram(AC.wing.A) 

248 sankey = Sankey(sol, M).diagram(width=1200, height=400, maxlinks=30) 

249 # the line below shows an interactive graph if run in jupyter notebook 

250 sankey # pylint: disable=pointless-statement 

251except (ImportError, ModuleNotFoundError): 

252 print("Making Sankey diagrams requires the ipysankeywidget package") 

253 

254from gpkit.interactive.references import referencesplot 

255referencesplot(M, openimmediately=False)