Coverage for docs/source/examples/performance_modeling.py: 98%
95 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 16:49 -0500
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 16:49 -0500
1"""Modular aircraft concept"""
2import pickle
3import numpy as np
4from gpkit import Model, Vectorize, parse_variables
7class AircraftP(Model):
8 """Aircraft flight physics: weight <= lift, fuel burn
10 Variables
11 ---------
12 Wfuel [lbf] fuel weight
13 Wburn [lbf] segment fuel burn
15 Upper Unbounded
16 ---------------
17 Wburn, aircraft.wing.c, aircraft.wing.A
19 Lower Unbounded
20 ---------------
21 Wfuel, aircraft.W, state.mu
23 """
24 @parse_variables(__doc__, globals())
25 def setup(self, aircraft, state):
26 self.aircraft = aircraft
27 self.state = state
29 self.wing_aero = aircraft.wing.dynamic(aircraft.wing, state)
30 self.perf_models = [self.wing_aero]
32 W = aircraft.W
33 S = aircraft.wing.S
35 V = state.V
36 rho = state.rho
38 D = self.wing_aero.D
39 CL = self.wing_aero.CL
41 return Wburn >= 0.1*D, W + Wfuel <= 0.5*rho*CL*S*V**2, {
42 "performance":
43 self.perf_models}
46class Aircraft(Model):
47 """The vehicle model
49 Variables
50 ---------
51 W [lbf] weight
53 Upper Unbounded
54 ---------------
55 W
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]
67 return [W >= sum(c.W for c in self.components),
68 self.components]
70 dynamic = AircraftP
73class FlightState(Model):
74 """Context for evaluating flight physics
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
82 """
83 @parse_variables(__doc__, globals())
84 def setup(self):
85 pass
88class FlightSegment(Model):
89 """Combines a context (flight state) and a component (the aircraft)
91 Upper Unbounded
92 ---------------
93 Wburn, aircraft.wing.c, aircraft.wing.A
95 Lower Unbounded
96 ---------------
97 Wfuel, aircraft.W
99 """
100 def setup(self, aircraft):
101 self.aircraft = aircraft
103 self.flightstate = FlightState()
104 self.aircraftp = aircraft.dynamic(aircraft, self.flightstate)
106 self.Wburn = self.aircraftp.Wburn
107 self.Wfuel = self.aircraftp.Wfuel
109 return {"aircraft performance": self.aircraftp,
110 "flightstate": self.flightstate}
113class Mission(Model):
114 """A sequence of flight segments
116 Upper Unbounded
117 ---------------
118 aircraft.wing.c, aircraft.wing.A
120 Lower Unbounded
121 ---------------
122 aircraft.W
123 """
124 def setup(self, aircraft):
125 self.aircraft = aircraft
127 with Vectorize(4): # four flight segments
128 self.fs = FlightSegment(aircraft)
130 Wburn = self.fs.aircraftp.Wburn
131 Wfuel = self.fs.aircraftp.Wfuel
132 self.takeoff_fuel = Wfuel[0]
134 return {
135 "fuel constraints":
136 [Wfuel[:-1] >= Wfuel[1:] + Wburn[:-1],
137 Wfuel[-1] >= Wburn[-1]],
138 "flight segment":
139 self.fs}
142class WingAero(Model):
143 """Wing aerodynamics
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
153 Upper Unbounded
154 ---------------
155 D, Re, wing.A, state.mu
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
166 c = wing.c
167 A = wing.A
168 S = wing.S
169 rho = state.rho
170 V = state.V
171 mu = state.mu
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]
178class Wing(Model):
179 """Aircraft wing model
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
189 Upper Unbounded
190 ---------------
191 W
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]
202 dynamic = WingAero
205class Fuselage(Model):
206 """The thing that carries the fuel, engine, and payload
208 A full model is left as an exercise for the reader.
210 Variables
211 ---------
212 W 100 [lbf] weight
214 """
215 @parse_variables(__doc__, globals())
216 def setup(self):
217 pass
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"))
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"]))
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))
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")
254from gpkit.interactive.references import referencesplot
255referencesplot(M, openimmediately=False)