*ARCHIVED* development moved to aircraft-studio.
start packagification
Changed files
__init__.py
@@ -1,17 +0,0 @@
1
Removed:
# This file is part of Marius Peter's airfoil analysis package (this program).
2
Removed:
#
3
Removed:
# This program is free software: you can redistribute it and/or modify
4
Removed:
# it under the terms of the GNU General Public License as published by
5
Removed:
# the Free Software Foundation, either version 3 of the License, or
6
Removed:
# (at your option) any later version.
7
Removed:
#
8
Removed:
# This program is distributed in the hope that it will be useful,
9
Removed:
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
Removed:
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
Removed:
# GNU General Public License for more details.
12
Removed:
#
13
Removed:
# You should have received a copy of the GNU General Public License
14
Removed:
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
Removed:
__author__ = "Marius Peter"
16
Removed:
# __version__ = "2.3"
17
Removed:
# __revision__ = "2.3.1"
creator.py
@@ -1,416 +0,0 @@
1
Removed:
# This file is part of Marius Peter's airfoil analysis package (this program).
2
Removed:
#
3
Removed:
# This program is free software: you can redistribute it and/or modify
4
Removed:
# it under the terms of the GNU General Public License as published by
5
Removed:
# the Free Software Foundation, either version 3 of the License, or
6
Removed:
# (at your option) any later version.
7
Removed:
#
8
Removed:
# This program is distributed in the hope that it will be useful,
9
Removed:
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
Removed:
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
Removed:
# GNU General Public License for more details.
12
Removed:
#
13
Removed:
# You should have received a copy of the GNU General Public License
14
Removed:
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
Removed:
16
Removed:
"""
17
Removed:
The creator.py module contains class definitions for coordinates
18
Removed:
and various components we add to an airfoil (spars, stringers, and ribs).
19
Removed:
20
Removed:
Classes:
21
Removed:
Airfoil: instantiated with class method to provide coordinates to heirs.
22
Removed:
Spar: inherits from Airfoil.
23
Removed:
Stringer: also inherits from Airfoil.
24
Removed:
25
Removed:
Functions:
26
Removed:
plot_geom(airfoil): generates a 2D plot of the airfoil & any components.
27
Removed:
"""
28
Removed:
29
Removed:
import sys
30
Removed:
import os.path
31
Removed:
import numpy as np
32
Removed:
from math import sin, cos, atan
33
Removed:
import bisect as bi
34
Removed:
import matplotlib.pyplot as plt
35
Removed:
36
Removed:
37
Removed:
class Airfoil:
38
Removed:
"""This class represents a single NACA airfoil.
39
Removed:
40
Removed:
Please note: the coordinates are saved as two lists
41
Removed:
for the x- and z-coordinates. The coordinates start at
42
Removed:
the leading edge, travel over the airfoil's upper edge,
43
Removed:
then loop back to the leading edge via the lower edge.
44
Removed:
45
Removed:
This method was chosen for easier future exports
46
Removed:
to 3D CAD packages like SolidWorks, which can import such
47
Removed:
geometry as coordinates written in a CSV file.
48
Removed:
"""
49
Removed:
50
Removed:
# Defaults
51
Removed:
chord = 100
52
Removed:
semi_span = 200
53
Removed:
54
Removed:
def __init__(self):
55
Removed:
# mass and area
56
Removed:
self.mass = float()
57
Removed:
self.area = float()
58
Removed:
# Component material
59
Removed:
self.material = str()
60
Removed:
# Coordinates
61
Removed:
self.x = []
62
Removed:
self.z = []
63
Removed:
64
Removed:
@classmethod
65
Removed:
def from_dimensions(cls, chord, semi_span):
66
Removed:
"""Create airfoil from its chord and semi-span."""
67
Removed:
if chord > 20:
68
Removed:
cls.chord = chord
69
Removed:
else:
70
Removed:
cls.chord = 20
71
Removed:
print('Chord too small, using minimum value of 20.')
72
Removed:
cls.semi_span = semi_span
73
Removed:
return Airfoil()
74
Removed:
75
Removed:
def __str__(self):
76
Removed:
return type(self).__name__
77
Removed:
78
Removed:
def add_naca(self, naca_num):
79
Removed:
"""Generate surface geometry for a NACA airfoil.
80
Removed:
81
Removed:
The nested functions perform the required steps to generate geometry,
82
Removed:
and can be called to solve the geometry y-coordinate for any 'x' input.
83
Removed:
Equation coefficients were retrieved from Wikipedia.org.
84
Removed:
85
Removed:
Parameters:
86
Removed:
naca_num: 4-digit NACA wing
87
Removed:
88
Removed:
Return:
89
Removed:
None
90
Removed:
"""
91
Removed:
# Variables extracted from 'naca_num' argument passed to the function
92
Removed:
self.naca_num = naca_num
93
Removed:
m = int(str(naca_num)[0]) / 100
94
Removed:
p = int(str(naca_num)[1]) / 10
95
Removed:
t = int(str(naca_num)[2:]) / 100
96
Removed:
# x-coordinate of maximum camber
97
Removed:
p_c = p * self.chord
98
Removed:
99
Removed:
def get_camber(x):
100
Removed:
"""
101
Removed:
Returns camber z-coordinate from 1 'x' along the airfoil chord.
102
Removed:
"""
103
Removed:
z_c = float()
104
Removed:
if 0 <= x < p_c:
105
Removed:
z_c = (m / (p ** 2)) * (2 * p * (x / self.chord)
106
Removed:
- (x / self.chord) ** 2)
107
Removed:
elif p_c <= x <= self.chord:
108
Removed:
z_c = (m / ((1 - p) ** 2)) * ((1 - 2 * p)
109
Removed:
+ 2 * p * (x / self.chord)
110
Removed:
- (x / self.chord) ** 2)
111
Removed:
return (z_c * self.chord)
112
Removed:
113
Removed:
def get_thickness(x):
114
Removed:
"""Return thickness from 1 'x' along the airfoil chord."""
115
Removed:
x = 0 if x < 0 else x
116
Removed:
z_t = 5 * t * self.chord * (
117
Removed:
+ 0.2969 * (x / self.chord) ** 0.5
118
Removed:
- 0.1260 * (x / self.chord) ** 1
119
Removed:
- 0.3516 * (x / self.chord) ** 2
120
Removed:
+ 0.2843 * (x / self.chord) ** 3
121
Removed:
- 0.1015 * (x / self.chord) ** 4)
122
Removed:
return z_t
123
Removed:
124
Removed:
def get_theta(x):
125
Removed:
dz_c = float()
126
Removed:
if 0 <= x < p_c:
127
Removed:
dz_c = ((2 * m) / p ** 2) * (p - x / self.chord)
128
Removed:
elif p_c <= x <= self.chord:
129
Removed:
dz_c = (2 * m) / ((1 - p) ** 2) * (p - x / self.chord)
130
Removed:
theta = atan(dz_c)
131
Removed:
return theta
132
Removed:
133
Removed:
def get_upper_coord(x):
134
Removed:
x = x - get_thickness(x) * sin(get_theta(x))
135
Removed:
z = get_camber(x) + get_thickness(x) * cos(get_theta(x))
136
Removed:
return (x, z)
137
Removed:
138
Removed:
def get_lower_coord(x):
139
Removed:
x = x + get_thickness(x) * sin(get_theta(x))
140
Removed:
z = get_camber(x) - get_thickness(x) * cos(get_theta(x))
141
Removed:
return (x, z)
142
Removed:
143
Removed:
# Densify x-coordinates 10 times for first 1/4 chord length
144
Removed:
x_chord_25_percent = round(self.chord / 4)
145
Removed:
146
Removed:
x_chord = [i / 10 for i in range(x_chord_25_percent * 10)]
147
Removed:
x_chord.extend(i for i in range(x_chord_25_percent, self.chord + 1))
148
Removed:
# Reversed list for our lower airfoil coordinate densification
149
Removed:
x_chord_rev = [i for i in range(self.chord, x_chord_25_percent, -1)]
150
Removed:
extend = [i / 10 for i in range(x_chord_25_percent * 10, -1, -1)]
151
Removed:
x_chord_rev.extend(extend)
152
Removed:
153
Removed:
# Generate our airfoil geometry from previous sub-functions.
154
Removed:
self.x_c = []
155
Removed:
self.z_c = []
156
Removed:
for x in x_chord:
157
Removed:
self.x_c.append(x)
158
Removed:
self.z_c.append(get_camber(x))
159
Removed:
self.x.append(get_upper_coord(x)[0])
160
Removed:
self.z.append(get_upper_coord(x)[1])
161
Removed:
for x in x_chord_rev:
162
Removed:
self.x.append(get_lower_coord(x)[0])
163
Removed:
self.z.append(get_lower_coord(x)[1])
164
Removed:
return None
165
Removed:
166
Removed:
def add_mass(self, mass):
167
Removed:
self.mass = mass
168
Removed:
169
Removed:
def info_print(self, round):
170
Removed:
"""Print all the component's coordinates to the terminal."""
171
Removed:
name = ' CREATOR DATA FOR {} '.format(str(self).upper())
172
Removed:
num_of_dashes = len(name)
173
Removed:
print(num_of_dashes * '-')
174
Removed:
print(name)
175
Removed:
for k, v in self.__dict__.items():
176
Removed:
if type(v) != list:
177
Removed:
print('{}:\n'.format(k), v)
178
Removed:
print(num_of_dashes * '-')
179
Removed:
for k, v in self.__dict__.items():
180
Removed:
if type(v) == list:
181
Removed:
print('{}:\n'.format(k), np.around(v, round))
182
Removed:
return None
183
Removed:
184
Removed:
def info_save(self, save_path, number):
185
Removed:
"""Save all the object's coordinates (must be full path)."""
186
Removed:
file_name = '{}_{}.txt'.format(str(self).lower(), number)
187
Removed:
full_path = os.path.join(save_path, file_name)
188
Removed:
try:
189
Removed:
with open(full_path, 'w') as sys.stdout:
190
Removed:
self.info_print(6)
191
Removed:
# This line required to reset behavior of sys.stdout
192
Removed:
sys.stdout = sys.__stdout__
193
Removed:
print('Successfully wrote to file {}'.format(full_path))
194
Removed:
except IOError:
195
Removed:
print('Unable to write {} to specified directory.\n'
196
Removed:
.format(file_name),
197
Removed:
'Was the full path passed to the function?')
198
Removed:
return None
199
Removed:
200
Removed:
201
Removed:
class Spar(Airfoil):
202
Removed:
"""Contains a single spar's location."""
203
Removed:
204
Removed:
def __init__(self):
205
Removed:
super().__init__()
206
Removed:
self.x_start = []
207
Removed:
self.x_end = []
208
Removed:
self.thickness = float()
209
Removed:
self.z_start = []
210
Removed:
self.z_end = []
211
Removed:
212
Removed:
def add_coord(self, airfoil, x_loc_percent):
213
Removed:
"""Add a single spar at the % chord location given to function.
214
Removed:
215
Removed:
Parameters:
216
Removed:
airfoil: gives the spar access to airfoil's coordinates.
217
Removed:
x_loc_percent: spar's location as a % of total chord length.
218
Removed:
219
Removed:
Return:
220
Removed:
None
221
Removed:
"""
222
Removed:
223
Removed:
# Scaled spar location with regards to chord
224
Removed:
loc = x_loc_percent * self.chord
225
Removed:
# bi.bisect_left: returns index of first value in airfoil.x > loc
226
Removed:
# This ensures that spar geom intersects with airfoil geom.
227
Removed:
# Spar upper coordinates
228
Removed:
spar_x = bi.bisect_left(airfoil.x, loc) - 1
229
Removed:
x = [airfoil.x[spar_x]]
230
Removed:
z = [airfoil.z[spar_x]]
231
Removed:
# Spar lower coordinates
232
Removed:
spar_x = bi.bisect_left(airfoil.x[::-1], loc)
233
Removed:
x += [airfoil.x[-spar_x]]
234
Removed:
z += [airfoil.z[-spar_x]]
235
Removed:
self.x.append(x)
236
Removed:
self.z.append(z)
237
Removed:
return None
238
Removed:
239
Removed:
def add_spar_caps(self, spar_cap_area):
240
Removed:
self.cap_area = spar_cap_area
241
Removed:
return None
242
Removed:
243
Removed:
def add_mass(self, mass):
244
Removed:
self.mass = len(self.x) * mass
245
Removed:
return None
246
Removed:
247
Removed:
def add_webs(self, thickness):
248
Removed:
"""Add webs to spars."""
249
Removed:
for _ in range(len(self.x)):
250
Removed:
self.x_start.append(self.x[_][0])
251
Removed:
self.x_end.append(self.x[_][1])
252
Removed:
self.z_start.append(self.z[_][0])
253
Removed:
self.z_end.append(self.z[_][1])
254
Removed:
self.thickness = thickness
255
Removed:
return None
256
Removed:
257
Removed:
258
Removed:
class Stringer(Airfoil):
259
Removed:
"""Contains the coordinates of all stringers."""
260
Removed:
261
Removed:
def __init__(self):
262
Removed:
super().__init__()
263
Removed:
self.x_start = []
264
Removed:
self.x_end = []
265
Removed:
self.thickness = float()
266
Removed:
self.z_start = []
267
Removed:
self.z_end = []
268
Removed:
self.area = float()
269
Removed:
270
Removed:
def add_coord(self, airfoil,
271
Removed:
stringer_u_1, stringer_u_2,
272
Removed:
stringer_l_1, stringer_l_2):
273
Removed:
"""Add equally distributed stringers to four airfoil locations
274
Removed:
(upper nose, lower nose, upper surface, lower surface).
275
Removed:
276
Removed:
Parameters:
277
Removed:
airfoil_coord: packed airfoil coordinates
278
Removed:
spar_coord: packed spar coordinates
279
Removed:
stringer_u_1: upper nose number of stringers
280
Removed:
stringer_u_2: upper surface number of stringers
281
Removed:
stringer_l_1: lower nose number of stringers
282
Removed:
stringer_l_2: lower surface number of stringers
283
Removed:
284
Removed:
Returns:
285
Removed:
None
286
Removed:
"""
287
Removed:
288
Removed:
# Find distance between leading edge and first upper stringer
289
Removed:
interval = airfoil.spar.x[0][0] / (stringer_u_1 + 1)
290
Removed:
# initialise first self.stringer_x at first interval
291
Removed:
x = interval
292
Removed:
# Add upper stringers from leading edge until first spar.
293
Removed:
for _ in range(0, stringer_u_1):
294
Removed:
# Index of the first value of airfoil.x > x
295
Removed:
i = bi.bisect_left(airfoil.x, x)
296
Removed:
self.x.append(airfoil.x[i])
297
Removed:
self.z.append(airfoil.z[i])
298
Removed:
x += interval
299
Removed:
# Add upper stringers from first spar until last spar
300
Removed:
# TODO: stringer placement if only one spar is created
301
Removed:
interval = (airfoil.spar.x[-1][0]
302
Removed:
- airfoil.spar.x[0][0]) / (stringer_u_2 + 1)
303
Removed:
x = interval + airfoil.spar.x[0][0]
304
Removed:
for _ in range(0, stringer_u_2):
305
Removed:
i = bi.bisect_left(airfoil.x, x)
306
Removed:
self.x.append(airfoil.x[i])
307
Removed:
self.z.append(airfoil.z[i])
308
Removed:
x += interval
309
Removed:
310
Removed:
# Find distance between leading edge and first lower stringer
311
Removed:
interval = airfoil.spar.x[0][1] / (stringer_l_1 + 1)
312
Removed:
x = interval
313
Removed:
# Add lower stringers from leading edge until first spar.
314
Removed:
for _ in range(0, stringer_l_1):
315
Removed:
i = bi.bisect_left(airfoil.x[::-1], x)
316
Removed:
self.x.append(airfoil.x[-i])
317
Removed:
self.z.append(airfoil.z[-i])
318
Removed:
x += interval
319
Removed:
# Add lower stringers from first spar until last spar
320
Removed:
interval = (airfoil.spar.x[-1][1]
321
Removed:
- airfoil.spar.x[0][1]) / (stringer_l_2 + 1)
322
Removed:
x = interval + airfoil.spar.x[0][1]
323
Removed:
for _ in range(0, stringer_l_2):
324
Removed:
i = bi.bisect_left(airfoil.x[::-1], x)
325
Removed:
self.x.append(airfoil.x[-i])
326
Removed:
self.z.append(airfoil.z[-i])
327
Removed:
x += interval
328
Removed:
return None
329
Removed:
330
Removed:
def add_area(self, area):
331
Removed:
self.area = area
332
Removed:
return None
333
Removed:
334
Removed:
def add_mass(self, mass):
335
Removed:
self.mass = len(self.x) * mass + len(self.x) * mass
336
Removed:
return None
337
Removed:
338
Removed:
def add_webs(self, thickness):
339
Removed:
"""Add webs to stringers."""
340
Removed:
for _ in range(len(self.x) // 2):
341
Removed:
self.x_start.append(self.x[_])
342
Removed:
self.x_end.append(self.x[_ + 1])
343
Removed:
self.z_start.append(self.z[_])
344
Removed:
self.z_end.append(self.z[_ + 1])
345
Removed:
self.thickness = thickness
346
Removed:
return None
347
Removed:
348
Removed:
def info_print(self, round):
349
Removed:
super().info_print(round)
350
Removed:
print('Stringer Area:\n', np.around(self.area, round))
351
Removed:
return None
352
Removed:
353
Removed:
354
Removed:
def plot_geom(airfoil, view: False):
355
Removed:
"""This function plots the airfoil's + sub-components' geometry."""
356
Removed:
fig, ax = plt.subplots()
357
Removed:
358
Removed:
# Plot chord
359
Removed:
x = [0, airfoil.chord]
360
Removed:
y = [0, 0]
361
Removed:
ax.plot(x, y, linewidth='1')
362
Removed:
# Plot quarter chord
363
Removed:
ax.plot(airfoil.chord / 4, 0,
364
Removed:
'.', color='g', markersize=24,
365
Removed:
label='Quarter-chord')
366
Removed:
# Plot mean camber line
367
Removed:
ax.plot(airfoil.x_c, airfoil.z_c,
368
Removed:
'-.', color='r', linewidth='2',
369
Removed:
label='Mean camber line')
370
Removed:
# Plot airfoil surfaces
371
Removed:
ax.plot(airfoil.x, airfoil.z,
372
Removed:
color='b', linewidth='1')
373
Removed:
374
Removed:
# Plot spars
375
Removed:
try:
376
Removed:
for _ in range(len(airfoil.spar.x)):
377
Removed:
x = (airfoil.spar.x[_])
378
Removed:
y = (airfoil.spar.z[_])
379
Removed:
ax.plot(x, y, '-', color='y', linewidth='4')
380
Removed:
except AttributeError:
381
Removed:
print('No spars to plot.')
382
Removed:
# Plot stringers
383
Removed:
try:
384
Removed:
for _ in range(0, len(airfoil.stringer.x)):
385
Removed:
x = airfoil.stringer.x[_]
386
Removed:
y = airfoil.stringer.z[_]
387
Removed:
ax.plot(x, y, '.', color='y', markersize=12)
388
Removed:
except AttributeError:
389
Removed:
print('No stringers to plot.')
390
Removed:
391
Removed:
# Graph formatting
392
Removed:
plot_bound = max(airfoil.x)
393
Removed:
ax.set(title='NACA ' + str(airfoil.naca_num) + ' airfoil',
394
Removed:
xlabel='X axis',
395
Removed:
xlim=[- 0.10 * plot_bound, 1.10 * plot_bound],
396
Removed:
ylabel='Z axis',
397
Removed:
ylim=[- (1.10 * plot_bound / 2), (1.10 * plot_bound / 2)])
398
Removed:
399
Removed:
plt.grid(axis='both', linestyle=':', linewidth=1)
400
Removed:
plt.gca().set_aspect('equal', adjustable='box')
401
Removed:
plt.gca().legend(bbox_to_anchor=(1, 1),
402
Removed:
bbox_transform=plt.gcf().transFigure)
403
Removed:
404
Removed:
if view == True:
405
Removed:
plt.show()
406
Removed:
else:
407
Removed:
pass
408
Removed:
return fig, ax
409
Removed:
410
Removed:
411
Removed:
def main():
412
Removed:
return None
413
Removed:
414
Removed:
415
Removed:
if __name__ == '__main__':
416
Removed:
main()
evaluator.py
@@ -1,288 +0,0 @@
1
Removed:
# This file is part of Marius Peter's airfoil analysis package (this program).
2
Removed:
#
3
Removed:
# This program is free software: you can redistribute it and/or modify
4
Removed:
# it under the terms of the GNU General Public License as published by
5
Removed:
# the Free Software Foundation, either version 3 of the License, or
6
Removed:
# (at your option) any later version.
7
Removed:
#
8
Removed:
# This program is distributed in the hope that it will be useful,
9
Removed:
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
Removed:
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
Removed:
# GNU General Public License for more details.
12
Removed:
#
13
Removed:
# You should have received a copy of the GNU General Public License
14
Removed:
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
Removed:
"""
16
Removed:
The evaluator.py module contains a single Evaluator class,
17
Removed:
which knows all the attributes of a specified Airfoil instance,
18
Removed:
and contains functions to analyse the airfoil's geometrical
19
Removed:
& structural properties.
20
Removed:
"""
21
Removed:
22
Removed:
import sys
23
Removed:
import os.path
24
Removed:
import numpy as np
25
Removed:
from math import sqrt
26
Removed:
import matplotlib.pyplot as plt
27
Removed:
28
Removed:
29
Removed:
class Evaluator:
30
Removed:
"""Performs structural evaluations for the airfoil passed as argument."""
31
Removed:
32
Removed:
def __init__(self, airfoil):
33
Removed:
# Evaluator knows all geometrical info from evaluated airfoil
34
Removed:
self.airfoil = airfoil
35
Removed:
self.spar = airfoil.spar
36
Removed:
self.stringer = airfoil.stringer
37
Removed:
# Global dimensions
38
Removed:
self.chord = airfoil.chord
39
Removed:
self.semi_span = airfoil.semi_span
40
Removed:
# Mass & spanwise distribution
41
Removed:
self.mass_total = float(airfoil.mass
42
Removed:
+ airfoil.spar.mass
43
Removed:
+ airfoil.stringer.mass)
44
Removed:
self.mass_dist = []
45
Removed:
# Lift
46
Removed:
self.lift_rectangular = []
47
Removed:
self.lift_elliptical = []
48
Removed:
self.lift_total = []
49
Removed:
# Drag
50
Removed:
self.drag = []
51
Removed:
# centroid
52
Removed:
self.centroid = []
53
Removed:
# Inertia terms:
54
Removed:
self.I_ = {'x': 0, 'z': 0, 'xz': 0}
55
Removed:
56
Removed:
def __str__(self):
57
Removed:
return type(self).__name__
58
Removed:
59
Removed:
def info_print(self, round):
60
Removed:
"""Print all the component's evaluated data to the terminal."""
61
Removed:
name = ' EVALUATOR DATA FOR {} '.format(str(self).upper())
62
Removed:
num_of_dashes = len(name)
63
Removed:
print(num_of_dashes * '-')
64
Removed:
print(name)
65
Removed:
for k, v in self.__dict__.items():
66
Removed:
if type(v) != list:
67
Removed:
print('{}:\n'.format(k), v)
68
Removed:
print(num_of_dashes * '-')
69
Removed:
for k, v in self.__dict__.items():
70
Removed:
if type(v) == list:
71
Removed:
print('{}:\n'.format(k), np.around(v, round))
72
Removed:
return None
73
Removed:
74
Removed:
def info_save(self, save_path, number):
75
Removed:
"""Save all the object's coordinates (must be full path)."""
76
Removed:
file_name = 'airfoil_{}_eval.txt'.format(number)
77
Removed:
full_path = os.path.join(save_path, file_name)
78
Removed:
try:
79
Removed:
with open(full_path, 'w') as sys.stdout:
80
Removed:
self.info_print(6)
81
Removed:
# This line required to reset behavior of sys.stdout
82
Removed:
sys.stdout = sys.__stdout__
83
Removed:
print('Successfully wrote to file {}'.format(full_path))
84
Removed:
except IOError:
85
Removed:
print(
86
Removed:
'Unable to write {} to specified directory.\n'.format(
87
Removed:
file_name), 'Was the full path passed to the function?')
88
Removed:
return None
89
Removed:
90
Removed:
# All these functions take integer arguments and return lists.
91
Removed:
92
Removed:
def get_lift_rectangular(self, lift):
93
Removed:
L_prime = [lift / (self.semi_span * 2) for x in range(self.semi_span)]
94
Removed:
return L_prime
95
Removed:
96
Removed:
def get_lift_elliptical(self, L_0):
97
Removed:
L_prime = [
98
Removed:
L_0 / (self.semi_span * 2) * sqrt(1 - (y / self.semi_span)**2)
99
Removed:
for y in range(self.semi_span)
100
Removed:
]
101
Removed:
return L_prime
102
Removed:
103
Removed:
def get_lift_total(self):
104
Removed:
F_z = [(self.lift_rectangular[_] + self.lift_elliptical[_]) / 2
105
Removed:
for _ in range(len(self.lift_rectangular))]
106
Removed:
return F_z
107
Removed:
108
Removed:
def get_mass_distribution(self, total_mass):
109
Removed:
F_z = [total_mass / self.semi_span for x in range(0, self.semi_span)]
110
Removed:
return F_z
111
Removed:
112
Removed:
def get_drag(self, drag):
113
Removed:
# Transform semi-span integer into list
114
Removed:
semi_span = [x for x in range(0, self.semi_span)]
115
Removed:
116
Removed:
# Drag increases after 80% of the semi_span
117
Removed:
cutoff = round(0.8 * self.semi_span)
118
Removed:
119
Removed:
# Drag increases by 25% after 80% of the semi_span
120
Removed:
F_x = [drag for x in semi_span[0:cutoff]]
121
Removed:
F_x.extend([1.25 * drag for x in semi_span[cutoff:]])
122
Removed:
return F_x
123
Removed:
124
Removed:
def get_centroid(self):
125
Removed:
"""Return the coordinates of the centroid."""
126
Removed:
stringer_area = self.stringer.area
127
Removed:
cap_area = self.spar.cap_area
128
Removed:
129
Removed:
caps_x = [value for spar in self.spar.x for value in spar]
130
Removed:
caps_z = [value for spar in self.spar.z for value in spar]
131
Removed:
stringers_x = self.stringer.x
132
Removed:
stringers_z = self.stringer.z
133
Removed:
134
Removed:
denominator = float(len(caps_x) * cap_area
135
Removed:
+ len(stringers_x) * stringer_area)
136
Removed:
137
Removed:
centroid_x = float(sum([x * cap_area for x in caps_x])
138
Removed:
+ sum([x * stringer_area for x in stringers_x]))
139
Removed:
centroid_x = centroid_x / denominator
140
Removed:
141
Removed:
centroid_z = float(sum([z * cap_area for z in caps_z])
142
Removed:
+ sum([z * stringer_area for z in stringers_z]))
143
Removed:
centroid_z = centroid_z / denominator
144
Removed:
145
Removed:
return (centroid_x, centroid_z)
146
Removed:
147
Removed:
def get_inertia_terms(self):
148
Removed:
"""Obtain all inertia terms."""
149
Removed:
stringer_area = self.stringer.area
150
Removed:
cap_area = self.spar.cap_area
151
Removed:
152
Removed:
# Adds upper and lower components' coordinates to list
153
Removed:
x_stringers = self.stringer.x
154
Removed:
z_stringers = self.stringer.z
155
Removed:
x_spars = self.spar.x[:][0] + self.spar.x[:][1]
156
Removed:
z_spars = self.spar.z[:][0] + self.spar.z[:][1]
157
Removed:
stringer_count = range(len(x_stringers))
158
Removed:
spar_count = range(len(self.spar.x))
159
Removed:
160
Removed:
# I_x is the sum of the contributions of the spar caps and stringers
161
Removed:
# TODO: replace list indices with dictionary value
162
Removed:
I_x = sum([cap_area * (z_spars[i] - self.centroid[1])**2
163
Removed:
for i in spar_count])
164
Removed:
I_x += sum([stringer_area * (z_stringers[i] - self.centroid[1])**2
165
Removed:
for i in stringer_count])
166
Removed:
167
Removed:
I_z = sum([cap_area * (x_spars[i] - self.centroid[0])**2
168
Removed:
for i in spar_count])
169
Removed:
I_z += sum([stringer_area * (x_stringers[i] - self.centroid[0])**2
170
Removed:
for i in stringer_count])
171
Removed:
172
Removed:
I_xz = sum([cap_area * (x_spars[i] - self.centroid[0])
173
Removed:
* (z_spars[i] - self.centroid[1])
174
Removed:
for i in spar_count])
175
Removed:
I_xz += sum([stringer_area * (x_stringers[i] - self.centroid[0])
176
Removed:
* (z_stringers[i] - self.centroid[1])
177
Removed:
for i in stringer_count])
178
Removed:
return (I_x, I_z, I_xz)
179
Removed:
180
Removed:
def get_dx(self, component):
181
Removed:
return [x - self.centroid[0] for x in component.x_start]
182
Removed:
183
Removed:
def get_dz(self, component):
184
Removed:
return [x - self.centroid[1] for x in component.x_start]
185
Removed:
186
Removed:
def get_dP(self, xDist, zDist, V_x, V_z, area):
187
Removed:
I_x = self.I_['x']
188
Removed:
I_z = self.I_['z']
189
Removed:
I_xz = self.I_['xz']
190
Removed:
denom = float(I_x * I_z - I_xz ** 2)
191
Removed:
z = float()
192
Removed:
for _ in range(len(xDist)):
193
Removed:
z += float(-area * xDist[_] * (I_x * V_x - I_xz * V_z)
194
Removed:
/ denom
195
Removed:
- area * zDist[_] * (I_z * V_z - I_xz * V_x)
196
Removed:
/ denom)
197
Removed:
return z
198
Removed:
199
Removed:
def analysis(self, V_x, V_z):
200
Removed:
"""Perform all analysis calculations and store in class instance."""
201
Removed:
self.drag = self.get_drag(10)
202
Removed:
self.lift_rectangular = self.get_lift_rectangular(13.7)
203
Removed:
self.lift_elliptical = self.get_lift_elliptical(15)
204
Removed:
self.lift_total = self.get_lift_total()
205
Removed:
self.mass_dist = self.get_mass_distribution(self.mass_total)
206
Removed:
self.centroid = self.get_centroid()
207
Removed:
self.I_['x'] = self.get_inertia_terms()[0]
208
Removed:
self.I_['z'] = self.get_inertia_terms()[1]
209
Removed:
self.I_['xz'] = self.get_inertia_terms()[2]
210
Removed:
spar_dx = self.get_dx(self.spar)
211
Removed:
spar_dz = self.get_dz(self.spar)
212
Removed:
self.spar.dP_x = self.get_dP(spar_dx, spar_dz,
213
Removed:
V_x, 0, self.spar.cap_area)
214
Removed:
self.spar.dP_z = self.get_dP(spar_dx, spar_dz,
215
Removed:
0, V_z, self.spar.cap_area)
216
Removed:
return None
217
Removed:
218
Removed:
219
Removed:
def plot_geom(evaluator):
220
Removed:
"""This function plots analysis results over the airfoil's geometry."""
221
Removed:
# Plot chord
222
Removed:
x_chord = [0, evaluator.chord]
223
Removed:
y_chord = [0, 0]
224
Removed:
plt.plot(x_chord, y_chord, linewidth='1')
225
Removed:
# Plot quarter chord
226
Removed:
plt.plot(evaluator.chord / 4, 0,
227
Removed:
'.', color='g', markersize=24, label='Quarter-chord')
228
Removed:
# Plot airfoil surfaces
229
Removed:
x = [0.98 * x for x in evaluator.airfoil.x]
230
Removed:
y = [0.98 * z for z in evaluator.airfoil.z]
231
Removed:
plt.fill(x, y, color='w', linewidth='1', fill=False)
232
Removed:
x = [1.02 * x for x in evaluator.airfoil.x]
233
Removed:
y = [1.02 * z for z in evaluator.airfoil.z]
234
Removed:
plt.fill(x, y, color='b', linewidth='1', fill=False)
235
Removed:
236
Removed:
# Plot spars
237
Removed:
try:
238
Removed:
for _ in range(len(evaluator.spar.x)):
239
Removed:
x = (evaluator.spar.x[_])
240
Removed:
y = (evaluator.spar.z[_])
241
Removed:
plt.plot(x, y, '-', color='b')
242
Removed:
except AttributeError:
243
Removed:
print('No spars to plot.')
244
Removed:
# Plot stringers
245
Removed:
try:
246
Removed:
for _ in range(0, len(evaluator.stringer.x)):
247
Removed:
x = evaluator.stringer.x[_]
248
Removed:
y = evaluator.stringer.z[_]
249
Removed:
plt.plot(x, y, '.', color='y', markersize=12)
250
Removed:
except AttributeError:
251
Removed:
print('No stringers to plot.')
252
Removed:
253
Removed:
# Plot centroid
254
Removed:
x = evaluator.centroid[0]
255
Removed:
y = evaluator.centroid[1]
256
Removed:
plt.plot(x, y, '.', color='r', markersize=24, label='centroid')
257
Removed:
258
Removed:
# Graph formatting
259
Removed:
plt.xlabel('X axis')
260
Removed:
plt.ylabel('Z axis')
261
Removed:
262
Removed:
plot_bound = max(evaluator.airfoil.x)
263
Removed:
plt.xlim(-0.10 * plot_bound, 1.10 * plot_bound)
264
Removed:
plt.ylim(-(1.10 * plot_bound / 2), (1.10 * plot_bound / 2))
265
Removed:
plt.gca().set_aspect('equal', adjustable='box')
266
Removed:
plt.gca().legend()
267
Removed:
plt.grid(axis='both', linestyle=':', linewidth=1)
268
Removed:
plt.show()
269
Removed:
return None
270
Removed:
271
Removed:
272
Removed:
def plot_lift(evaluator):
273
Removed:
x = range(evaluator.semi_span)
274
Removed:
y_1 = evaluator.lift_rectangular
275
Removed:
y_2 = evaluator.lift_elliptical
276
Removed:
y_3 = evaluator.lift_total
277
Removed:
plt.plot(x, y_1, '.', color='b', markersize=4, label='Rectangular lift')
278
Removed:
plt.plot(x, y_2, '.', color='g', markersize=4, label='Elliptical lift')
279
Removed:
plt.plot(x, y_3, '.', color='r', markersize=4, label='Total lift')
280
Removed:
281
Removed:
# Graph formatting
282
Removed:
plt.xlabel('Semi-span location')
283
Removed:
plt.ylabel('Lift')
284
Removed:
285
Removed:
plt.gca().legend()
286
Removed:
plt.grid(axis='both', linestyle=':', linewidth=1)
287
Removed:
plt.show()
288
Removed:
return None
example_airfoil.py
@@ -1,13 +1,14 @@
1
1
"""This example illustrates the usage of creator, evaluator and generator.
2
2
3
Added:
All the steps of airfoil creation & evaluation are detailed here;
4
Added:
however, the generator.py module contains certain presets (default airfoils).
5
Added:
3
6
Create an airfoil;
4
7
Evaluate an airfoil;
5
8
Generate a population of airfoils & optimize.
6
9
"""
7
10
8
Removed:
import creator # Create geometry
9
Removed:
import evaluator # Evaluate geometry
10
Removed:
import generator # Iteratevely evaluate instances of geometry and optimize
11
Added:
from tools import creator, evaluator, generator
11
12
12
13
import time
13
14
start_time = time.time()
@@ -36,10 +37,9 @@
36
37
NOSE_TOP_STRINGERS = 3
37
38
NOSE_BOTTOM_STRINGERS = 5
38
39
39
Removed:
# population information & save path
40
Removed:
POP_SIZE = 1
41
40
SAVE_PATH = 'C:/Users/blend/github/UCLA_MAE_154B/save'
42
41
42
Added:
43
43
# Create airfoil instance
44
44
af = creator.Airfoil.from_dimensions(CHORD_LENGTH, SEMI_SPAN)
45
45
af.add_naca(NACA_NUM)
@@ -49,10 +49,10 @@
49
49
50
50
# Create spar instance
51
51
af.spar = creator.Spar()
52
Removed:
# Define the spar coordinates and mass, stored in single spar object
52
Added:
# All spar coordinates are stored in single Spar object
53
53
af.spar.add_coord(af, 0.23)
54
54
af.spar.add_coord(af, 0.57)
55
Removed:
# Automatically adds spar caps for each spar defined previously
55
Added:
# Automatically adds spar caps for each spar previously defined
56
56
af.spar.add_spar_caps(SPAR_CAP_AREA)
57
57
af.spar.add_mass(SPAR_MASS)
58
58
af.spar.add_webs(SPAR_THICKNESS)
generator.py
@@ -1,64 +0,0 @@
1
Removed:
# This file is part of Marius Peter's airfoil analysis package (this program).
2
Removed:
#
3
Removed:
# This program is free software: you can redistribute it and/or modify
4
Removed:
# it under the terms of the GNU General Public License as published by
5
Removed:
# the Free Software Foundation, either version 3 of the License, or
6
Removed:
# (at your option) any later version.
7
Removed:
#
8
Removed:
# This program is distributed in the hope that it will be useful,
9
Removed:
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
Removed:
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
Removed:
# GNU General Public License for more details.
12
Removed:
#
13
Removed:
# You should have received a copy of the GNU General Public License
14
Removed:
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
Removed:
"""
16
Removed:
The generator.py module contains a single Population class,
17
Removed:
which represents a collection of randomized airfoils.
18
Removed:
"""
19
Removed:
20
Removed:
import creator
21
Removed:
22
Removed:
23
Removed:
def default_airfoil():
24
Removed:
"""Generate the default airfoil."""
25
Removed:
airfoil = creator.Airfoil.from_dimensions(100, 200)
26
Removed:
airfoil.add_naca(2412)
27
Removed:
airfoil.add_mass(10)
28
Removed:
29
Removed:
airfoil.spar = creator.Spar()
30
Removed:
airfoil.spar.add_coord(airfoil, 0.23)
31
Removed:
airfoil.spar.add_coord(airfoil, 0.57)
32
Removed:
airfoil.spar.add_spar_caps(0.3)
33
Removed:
airfoil.spar.add_mass(10)
34
Removed:
airfoil.spar.add_webs(0.4)
35
Removed:
36
Removed:
airfoil.stringer = creator.Stringer()
37
Removed:
airfoil.stringer.add_coord(airfoil, 3, 6, 5, 4)
38
Removed:
airfoil.stringer.add_area(0.1)
39
Removed:
airfoil.stringer.add_mass(5)
40
Removed:
airfoil.stringer.add_webs(0.1)
41
Removed:
42
Removed:
return airfoil
43
Removed:
44
Removed:
45
Removed:
class Population(creator.Airfoil):
46
Removed:
"""Collection of random airfoils."""
47
Removed:
48
Removed:
def __init__(self, size):
49
Removed:
af = creator.Airfoil
50
Removed:
# print(af)
51
Removed:
self.size = size
52
Removed:
self.gen_number = 0 # incremented for every generation
53
Removed:
54
Removed:
def mutate(self, prob_mt):
55
Removed:
"""Randomly mutate the genes of prob_mt % of the population."""
56
Removed:
57
Removed:
def crossover(self, prob_cx):
58
Removed:
"""Combine the genes of prob_cx % of the population."""
59
Removed:
60
Removed:
def reproduce(self, prob_rp):
61
Removed:
"""Pass on the genes of the fittest prob_rp % of the population."""
62
Removed:
63
Removed:
def fitness():
64
Removed:
"""Rate the fitness of an individual on a relative scale (0-100)"""
tools/creator.py
@@ -0,0 +1,416 @@
1
Added:
# This file is part of Marius Peter's airfoil analysis package (this program).
2
Added:
#
3
Added:
# This program is free software: you can redistribute it and/or modify
4
Added:
# it under the terms of the GNU General Public License as published by
5
Added:
# the Free Software Foundation, either version 3 of the License, or
6
Added:
# (at your option) any later version.
7
Added:
#
8
Added:
# This program is distributed in the hope that it will be useful,
9
Added:
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
Added:
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
Added:
# GNU General Public License for more details.
12
Added:
#
13
Added:
# You should have received a copy of the GNU General Public License
14
Added:
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
Added:
16
Added:
"""
17
Added:
The creator.py module contains class definitions for coordinates
18
Added:
and various components we add to an airfoil (spars, stringers, and ribs).
19
Added:
20
Added:
Classes:
21
Added:
Airfoil: instantiated with class method to provide coordinates to heirs.
22
Added:
Spar: inherits from Airfoil.
23
Added:
Stringer: also inherits from Airfoil.
24
Added:
25
Added:
Functions:
26
Added:
plot_geom(airfoil): generates a 2D plot of the airfoil & any components.
27
Added:
"""
28
Added:
29
Added:
import sys
30
Added:
import os.path
31
Added:
import numpy as np
32
Added:
from math import sin, cos, atan
33
Added:
import bisect as bi
34
Added:
import matplotlib.pyplot as plt
35
Added:
36
Added:
37
Added:
class Airfoil:
38
Added:
"""This class represents a single NACA airfoil.
39
Added:
40
Added:
Please note: the coordinates are saved as two lists
41
Added:
for the x- and z-coordinates. The coordinates start at
42
Added:
the leading edge, travel over the airfoil's upper edge,
43
Added:
then loop back to the leading edge via the lower edge.
44
Added:
45
Added:
This method was chosen for easier future exports
46
Added:
to 3D CAD packages like SolidWorks, which can import such
47
Added:
geometry as coordinates written in a CSV file.
48
Added:
"""
49
Added:
50
Added:
# Defaults
51
Added:
chord = 100
52
Added:
semi_span = 200
53
Added:
54
Added:
def __init__(self):
55
Added:
# mass and area
56
Added:
self.mass = float()
57
Added:
self.area = float()
58
Added:
# Component material
59
Added:
self.material = str()
60
Added:
# Coordinates
61
Added:
self.x = []
62
Added:
self.z = []
63
Added:
64
Added:
@classmethod
65
Added:
def from_dimensions(cls, chord, semi_span):
66
Added:
"""Create airfoil from its chord and semi-span."""
67
Added:
if chord > 20:
68
Added:
cls.chord = chord
69
Added:
else:
70
Added:
cls.chord = 20
71
Added:
print('Chord too small, using minimum value of 20.')
72
Added:
cls.semi_span = semi_span
73
Added:
return Airfoil()
74
Added:
75
Added:
def __str__(self):
76
Added:
return type(self).__name__
77
Added:
78
Added:
def add_naca(self, naca_num):
79
Added:
"""Generate surface geometry for a NACA airfoil.
80
Added:
81
Added:
The nested functions perform the required steps to generate geometry,
82
Added:
and can be called to solve the geometry y-coordinate for any 'x' input.
83
Added:
Equation coefficients were retrieved from Wikipedia.org.
84
Added:
85
Added:
Parameters:
86
Added:
naca_num: 4-digit NACA wing
87
Added:
88
Added:
Return:
89
Added:
None
90
Added:
"""
91
Added:
# Variables extracted from 'naca_num' argument passed to the function
92
Added:
self.naca_num = naca_num
93
Added:
m = int(str(naca_num)[0]) / 100
94
Added:
p = int(str(naca_num)[1]) / 10
95
Added:
t = int(str(naca_num)[2:]) / 100
96
Added:
# x-coordinate of maximum camber
97
Added:
p_c = p * self.chord
98
Added:
99
Added:
def get_camber(x):
100
Added:
"""
101
Added:
Returns camber z-coordinate from 1 'x' along the airfoil chord.
102
Added:
"""
103
Added:
z_c = float()
104
Added:
if 0 <= x < p_c:
105
Added:
z_c = (m / (p ** 2)) * (2 * p * (x / self.chord)
106
Added:
- (x / self.chord) ** 2)
107
Added:
elif p_c <= x <= self.chord:
108
Added:
z_c = (m / ((1 - p) ** 2)) * ((1 - 2 * p)
109
Added:
+ 2 * p * (x / self.chord)
110
Added:
- (x / self.chord) ** 2)
111
Added:
return (z_c * self.chord)
112
Added:
113
Added:
def get_thickness(x):
114
Added:
"""Return thickness from 1 'x' along the airfoil chord."""
115
Added:
x = 0 if x < 0 else x
116
Added:
z_t = 5 * t * self.chord * (
117
Added:
+ 0.2969 * (x / self.chord) ** 0.5
118
Added:
- 0.1260 * (x / self.chord) ** 1
119
Added:
- 0.3516 * (x / self.chord) ** 2
120
Added:
+ 0.2843 * (x / self.chord) ** 3
121
Added:
- 0.1015 * (x / self.chord) ** 4)
122
Added:
return z_t
123
Added:
124
Added:
def get_theta(x):
125
Added:
dz_c = float()
126
Added:
if 0 <= x < p_c:
127
Added:
dz_c = ((2 * m) / p ** 2) * (p - x / self.chord)
128
Added:
elif p_c <= x <= self.chord:
129
Added:
dz_c = (2 * m) / ((1 - p) ** 2) * (p - x / self.chord)
130
Added:
theta = atan(dz_c)
131
Added:
return theta
132
Added:
133
Added:
def get_upper_coord(x):
134
Added:
x = x - get_thickness(x) * sin(get_theta(x))
135
Added:
z = get_camber(x) + get_thickness(x) * cos(get_theta(x))
136
Added:
return (x, z)
137
Added:
138
Added:
def get_lower_coord(x):
139
Added:
x = x + get_thickness(x) * sin(get_theta(x))
140
Added:
z = get_camber(x) - get_thickness(x) * cos(get_theta(x))
141
Added:
return (x, z)
142
Added:
143
Added:
# Densify x-coordinates 10 times for first 1/4 chord length
144
Added:
x_chord_25_percent = round(self.chord / 4)
145
Added:
146
Added:
x_chord = [i / 10 for i in range(x_chord_25_percent * 10)]
147
Added:
x_chord.extend(i for i in range(x_chord_25_percent, self.chord + 1))
148
Added:
# Reversed list for our lower airfoil coordinate densification
149
Added:
x_chord_rev = [i for i in range(self.chord, x_chord_25_percent, -1)]
150
Added:
extend = [i / 10 for i in range(x_chord_25_percent * 10, -1, -1)]
151
Added:
x_chord_rev.extend(extend)
152
Added:
153
Added:
# Generate our airfoil geometry from previous sub-functions.
154
Added:
self.x_c = []
155
Added:
self.z_c = []
156
Added:
for x in x_chord:
157
Added:
self.x_c.append(x)
158
Added:
self.z_c.append(get_camber(x))
159
Added:
self.x.append(get_upper_coord(x)[0])
160
Added:
self.z.append(get_upper_coord(x)[1])
161
Added:
for x in x_chord_rev:
162
Added:
self.x.append(get_lower_coord(x)[0])
163
Added:
self.z.append(get_lower_coord(x)[1])
164
Added:
return None
165
Added:
166
Added:
def add_mass(self, mass):
167
Added:
self.mass = mass
168
Added:
169
Added:
def info_print(self, round):
170
Added:
"""Print all the component's coordinates to the terminal."""
171
Added:
name = ' CREATOR DATA FOR {} '.format(str(self).upper())
172
Added:
num_of_dashes = len(name)
173
Added:
print(num_of_dashes * '-')
174
Added:
print(name)
175
Added:
for k, v in self.__dict__.items():
176
Added:
if type(v) != list:
177
Added:
print('{}:\n'.format(k), v)
178
Added:
print(num_of_dashes * '-')
179
Added:
for k, v in self.__dict__.items():
180
Added:
if type(v) == list:
181
Added:
print('{}:\n'.format(k), np.around(v, round))
182
Added:
return None
183
Added:
184
Added:
def info_save(self, save_path, number):
185
Added:
"""Save all the object's coordinates (must be full path)."""
186
Added:
file_name = '{}_{}.txt'.format(str(self).lower(), number)
187
Added:
full_path = os.path.join(save_path, file_name)
188
Added:
try:
189
Added:
with open(full_path, 'w') as sys.stdout:
190
Added:
self.info_print(6)
191
Added:
# This line required to reset behavior of sys.stdout
192
Added:
sys.stdout = sys.__stdout__
193
Added:
print('Successfully wrote to file {}'.format(full_path))
194
Added:
except IOError:
195
Added:
print('Unable to write {} to specified directory.\n'
196
Added:
.format(file_name),
197
Added:
'Was the full path passed to the function?')
198
Added:
return None
199
Added:
200
Added:
201
Added:
class Spar(Airfoil):
202
Added:
"""Contains a single spar's location."""
203
Added:
204
Added:
def __init__(self):
205
Added:
super().__init__()
206
Added:
self.x_start = []
207
Added:
self.x_end = []
208
Added:
self.thickness = float()
209
Added:
self.z_start = []
210
Added:
self.z_end = []
211
Added:
212
Added:
def add_coord(self, airfoil, x_loc_percent):
213
Added:
"""Add a single spar at the % chord location given to function.
214
Added:
215
Added:
Parameters:
216
Added:
airfoil: gives the spar access to airfoil's coordinates.
217
Added:
x_loc_percent: spar's location as a % of total chord length.
218
Added:
219
Added:
Return:
220
Added:
None
221
Added:
"""
222
Added:
223
Added:
# Scaled spar location with regards to chord
224
Added:
loc = x_loc_percent * self.chord
225
Added:
# bi.bisect_left: returns index of first value in airfoil.x > loc
226
Added:
# This ensures that spar geom intersects with airfoil geom.
227
Added:
# Spar upper coordinates
228
Added:
spar_x = bi.bisect_left(airfoil.x, loc) - 1
229
Added:
x = [airfoil.x[spar_x]]
230
Added:
z = [airfoil.z[spar_x]]
231
Added:
# Spar lower coordinates
232
Added:
spar_x = bi.bisect_left(airfoil.x[::-1], loc)
233
Added:
x += [airfoil.x[-spar_x]]
234
Added:
z += [airfoil.z[-spar_x]]
235
Added:
self.x.append(x)
236
Added:
self.z.append(z)
237
Added:
return None
238
Added:
239
Added:
def add_spar_caps(self, spar_cap_area):
240
Added:
self.cap_area = spar_cap_area
241
Added:
return None
242
Added:
243
Added:
def add_mass(self, mass):
244
Added:
self.mass = len(self.x) * mass
245
Added:
return None
246
Added:
247
Added:
def add_webs(self, thickness):
248
Added:
"""Add webs to spars."""
249
Added:
for _ in range(len(self.x)):
250
Added:
self.x_start.append(self.x[_][0])
251
Added:
self.x_end.append(self.x[_][1])
252
Added:
self.z_start.append(self.z[_][0])
253
Added:
self.z_end.append(self.z[_][1])
254
Added:
self.thickness = thickness
255
Added:
return None
256
Added:
257
Added:
258
Added:
class Stringer(Airfoil):
259
Added:
"""Contains the coordinates of all stringers."""
260
Added:
261
Added:
def __init__(self):
262
Added:
super().__init__()
263
Added:
self.x_start = []
264
Added:
self.x_end = []
265
Added:
self.thickness = float()
266
Added:
self.z_start = []
267
Added:
self.z_end = []
268
Added:
self.area = float()
269
Added:
270
Added:
def add_coord(self, airfoil,
271
Added:
stringer_u_1, stringer_u_2,
272
Added:
stringer_l_1, stringer_l_2):
273
Added:
"""Add equally distributed stringers to four airfoil locations
274
Added:
(upper nose, lower nose, upper surface, lower surface).
275
Added:
276
Added:
Parameters:
277
Added:
airfoil_coord: packed airfoil coordinates
278
Added:
spar_coord: packed spar coordinates
279
Added:
stringer_u_1: upper nose number of stringers
280
Added:
stringer_u_2: upper surface number of stringers
281
Added:
stringer_l_1: lower nose number of stringers
282
Added:
stringer_l_2: lower surface number of stringers
283
Added:
284
Added:
Returns:
285
Added:
None
286
Added:
"""
287
Added:
288
Added:
# Find distance between leading edge and first upper stringer
289
Added:
interval = airfoil.spar.x[0][0] / (stringer_u_1 + 1)
290
Added:
# initialise first self.stringer_x at first interval
291
Added:
x = interval
292
Added:
# Add upper stringers from leading edge until first spar.
293
Added:
for _ in range(0, stringer_u_1):
294
Added:
# Index of the first value of airfoil.x > x
295
Added:
i = bi.bisect_left(airfoil.x, x)
296
Added:
self.x.append(airfoil.x[i])
297
Added:
self.z.append(airfoil.z[i])
298
Added:
x += interval
299
Added:
# Add upper stringers from first spar until last spar
300
Added:
# TODO: stringer placement if only one spar is created
301
Added:
interval = (airfoil.spar.x[-1][0]
302
Added:
- airfoil.spar.x[0][0]) / (stringer_u_2 + 1)
303
Added:
x = interval + airfoil.spar.x[0][0]
304
Added:
for _ in range(0, stringer_u_2):
305
Added:
i = bi.bisect_left(airfoil.x, x)
306
Added:
self.x.append(airfoil.x[i])
307
Added:
self.z.append(airfoil.z[i])
308
Added:
x += interval
309
Added:
310
Added:
# Find distance between leading edge and first lower stringer
311
Added:
interval = airfoil.spar.x[0][1] / (stringer_l_1 + 1)
312
Added:
x = interval
313
Added:
# Add lower stringers from leading edge until first spar.
314
Added:
for _ in range(0, stringer_l_1):
315
Added:
i = bi.bisect_left(airfoil.x[::-1], x)
316
Added:
self.x.append(airfoil.x[-i])
317
Added:
self.z.append(airfoil.z[-i])
318
Added:
x += interval
319
Added:
# Add lower stringers from first spar until last spar
320
Added:
interval = (airfoil.spar.x[-1][1]
321
Added:
- airfoil.spar.x[0][1]) / (stringer_l_2 + 1)
322
Added:
x = interval + airfoil.spar.x[0][1]
323
Added:
for _ in range(0, stringer_l_2):
324
Added:
i = bi.bisect_left(airfoil.x[::-1], x)
325
Added:
self.x.append(airfoil.x[-i])
326
Added:
self.z.append(airfoil.z[-i])
327
Added:
x += interval
328
Added:
return None
329
Added:
330
Added:
def add_area(self, area):
331
Added:
self.area = area
332
Added:
return None
333
Added:
334
Added:
def add_mass(self, mass):
335
Added:
self.mass = len(self.x) * mass + len(self.x) * mass
336
Added:
return None
337
Added:
338
Added:
def add_webs(self, thickness):
339
Added:
"""Add webs to stringers."""
340
Added:
for _ in range(len(self.x) // 2):
341
Added:
self.x_start.append(self.x[_])
342
Added:
self.x_end.append(self.x[_ + 1])
343
Added:
self.z_start.append(self.z[_])
344
Added:
self.z_end.append(self.z[_ + 1])
345
Added:
self.thickness = thickness
346
Added:
return None
347
Added:
348
Added:
def info_print(self, round):
349
Added:
super().info_print(round)
350
Added:
print('Stringer Area:\n', np.around(self.area, round))
351
Added:
return None
352
Added:
353
Added:
354
Added:
def plot_geom(airfoil, view: False):
355
Added:
"""This function plots the airfoil's + sub-components' geometry."""
356
Added:
fig, ax = plt.subplots()
357
Added:
358
Added:
# Plot chord
359
Added:
x = [0, airfoil.chord]
360
Added:
y = [0, 0]
361
Added:
ax.plot(x, y, linewidth='1')
362
Added:
# Plot quarter chord
363
Added:
ax.plot(airfoil.chord / 4, 0,
364
Added:
'.', color='g', markersize=24,
365
Added:
label='Quarter-chord')
366
Added:
# Plot mean camber line
367
Added:
ax.plot(airfoil.x_c, airfoil.z_c,
368
Added:
'-.', color='r', linewidth='2',
369
Added:
label='Mean camber line')
370
Added:
# Plot airfoil surfaces
371
Added:
ax.plot(airfoil.x, airfoil.z,
372
Added:
color='b', linewidth='1')
373
Added:
374
Added:
# Plot spars
375
Added:
try:
376
Added:
for _ in range(len(airfoil.spar.x)):
377
Added:
x = (airfoil.spar.x[_])
378
Added:
y = (airfoil.spar.z[_])
379
Added:
ax.plot(x, y, '-', color='y', linewidth='4')
380
Added:
except AttributeError:
381
Added:
print('No spars to plot.')
382
Added:
# Plot stringers
383
Added:
try:
384
Added:
for _ in range(0, len(airfoil.stringer.x)):
385
Added:
x = airfoil.stringer.x[_]
386
Added:
y = airfoil.stringer.z[_]
387
Added:
ax.plot(x, y, '.', color='y', markersize=12)
388
Added:
except AttributeError:
389
Added:
print('No stringers to plot.')
390
Added:
391
Added:
# Graph formatting
392
Added:
plot_bound = max(airfoil.x)
393
Added:
ax.set(title='NACA ' + str(airfoil.naca_num) + ' airfoil',
394
Added:
xlabel='X axis',
395
Added:
xlim=[- 0.10 * plot_bound, 1.10 * plot_bound],
396
Added:
ylabel='Z axis',
397
Added:
ylim=[- (1.10 * plot_bound / 2), (1.10 * plot_bound / 2)])
398
Added:
399
Added:
plt.grid(axis='both', linestyle=':', linewidth=1)
400
Added:
plt.gca().set_aspect('equal', adjustable='box')
401
Added:
plt.gca().legend(bbox_to_anchor=(1, 1),
402
Added:
bbox_transform=plt.gcf().transFigure)
403
Added:
404
Added:
if view == True:
405
Added:
plt.show()
406
Added:
else:
407
Added:
pass
408
Added:
return fig, ax
409
Added:
410
Added:
411
Added:
def main():
412
Added:
return None
413
Added:
414
Added:
415
Added:
if __name__ == '__main__':
416
Added:
main()
tools/evaluator.py
@@ -0,0 +1,288 @@
1
Added:
# This file is part of Marius Peter's airfoil analysis package (this program).
2
Added:
#
3
Added:
# This program is free software: you can redistribute it and/or modify
4
Added:
# it under the terms of the GNU General Public License as published by
5
Added:
# the Free Software Foundation, either version 3 of the License, or
6
Added:
# (at your option) any later version.
7
Added:
#
8
Added:
# This program is distributed in the hope that it will be useful,
9
Added:
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
Added:
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
Added:
# GNU General Public License for more details.
12
Added:
#
13
Added:
# You should have received a copy of the GNU General Public License
14
Added:
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
Added:
"""
16
Added:
The evaluator.py module contains a single Evaluator class,
17
Added:
which knows all the attributes of a specified Airfoil instance,
18
Added:
and contains functions to analyse the airfoil's geometrical
19
Added:
& structural properties.
20
Added:
"""
21
Added:
22
Added:
import sys
23
Added:
import os.path
24
Added:
import numpy as np
25
Added:
from math import sqrt
26
Added:
import matplotlib.pyplot as plt
27
Added:
28
Added:
29
Added:
class Evaluator:
30
Added:
"""Performs structural evaluations for the airfoil passed as argument."""
31
Added:
32
Added:
def __init__(self, airfoil):
33
Added:
# Evaluator knows all geometrical info from evaluated airfoil
34
Added:
self.airfoil = airfoil
35
Added:
self.spar = airfoil.spar
36
Added:
self.stringer = airfoil.stringer
37
Added:
# Global dimensions
38
Added:
self.chord = airfoil.chord
39
Added:
self.semi_span = airfoil.semi_span
40
Added:
# Mass & spanwise distribution
41
Added:
self.mass_total = float(airfoil.mass
42
Added:
+ airfoil.spar.mass
43
Added:
+ airfoil.stringer.mass)
44
Added:
self.mass_dist = []
45
Added:
# Lift
46
Added:
self.lift_rectangular = []
47
Added:
self.lift_elliptical = []
48
Added:
self.lift_total = []
49
Added:
# Drag
50
Added:
self.drag = []
51
Added:
# centroid
52
Added:
self.centroid = []
53
Added:
# Inertia terms:
54
Added:
self.I_ = {'x': 0, 'z': 0, 'xz': 0}
55
Added:
56
Added:
def __str__(self):
57
Added:
return type(self).__name__
58
Added:
59
Added:
def info_print(self, round):
60
Added:
"""Print all the component's evaluated data to the terminal."""
61
Added:
name = ' EVALUATOR DATA FOR {} '.format(str(self).upper())
62
Added:
num_of_dashes = len(name)
63
Added:
print(num_of_dashes * '-')
64
Added:
print(name)
65
Added:
for k, v in self.__dict__.items():
66
Added:
if type(v) != list:
67
Added:
print('{}:\n'.format(k), v)
68
Added:
print(num_of_dashes * '-')
69
Added:
for k, v in self.__dict__.items():
70
Added:
if type(v) == list:
71
Added:
print('{}:\n'.format(k), np.around(v, round))
72
Added:
return None
73
Added:
74
Added:
def info_save(self, save_path, number):
75
Added:
"""Save all the object's coordinates (must be full path)."""
76
Added:
file_name = 'airfoil_{}_eval.txt'.format(number)
77
Added:
full_path = os.path.join(save_path, file_name)
78
Added:
try:
79
Added:
with open(full_path, 'w') as sys.stdout:
80
Added:
self.info_print(6)
81
Added:
# This line required to reset behavior of sys.stdout
82
Added:
sys.stdout = sys.__stdout__
83
Added:
print('Successfully wrote to file {}'.format(full_path))
84
Added:
except IOError:
85
Added:
print(
86
Added:
'Unable to write {} to specified directory.\n'.format(
87
Added:
file_name), 'Was the full path passed to the function?')
88
Added:
return None
89
Added:
90
Added:
# All these functions take integer arguments and return lists.
91
Added:
92
Added:
def get_lift_rectangular(self, lift):
93
Added:
L_prime = [lift / (self.semi_span * 2) for x in range(self.semi_span)]
94
Added:
return L_prime
95
Added:
96
Added:
def get_lift_elliptical(self, L_0):
97
Added:
L_prime = [
98
Added:
L_0 / (self.semi_span * 2) * sqrt(1 - (y / self.semi_span)**2)
99
Added:
for y in range(self.semi_span)
100
Added:
]
101
Added:
return L_prime
102
Added:
103
Added:
def get_lift_total(self):
104
Added:
F_z = [(self.lift_rectangular[_] + self.lift_elliptical[_]) / 2
105
Added:
for _ in range(len(self.lift_rectangular))]
106
Added:
return F_z
107
Added:
108
Added:
def get_mass_distribution(self, total_mass):
109
Added:
F_z = [total_mass / self.semi_span for x in range(0, self.semi_span)]
110
Added:
return F_z
111
Added:
112
Added:
def get_drag(self, drag):
113
Added:
# Transform semi-span integer into list
114
Added:
semi_span = [x for x in range(0, self.semi_span)]
115
Added:
116
Added:
# Drag increases after 80% of the semi_span
117
Added:
cutoff = round(0.8 * self.semi_span)
118
Added:
119
Added:
# Drag increases by 25% after 80% of the semi_span
120
Added:
F_x = [drag for x in semi_span[0:cutoff]]
121
Added:
F_x.extend([1.25 * drag for x in semi_span[cutoff:]])
122
Added:
return F_x
123
Added:
124
Added:
def get_centroid(self):
125
Added:
"""Return the coordinates of the centroid."""
126
Added:
stringer_area = self.stringer.area
127
Added:
cap_area = self.spar.cap_area
128
Added:
129
Added:
caps_x = [value for spar in self.spar.x for value in spar]
130
Added:
caps_z = [value for spar in self.spar.z for value in spar]
131
Added:
stringers_x = self.stringer.x
132
Added:
stringers_z = self.stringer.z
133
Added:
134
Added:
denominator = float(len(caps_x) * cap_area
135
Added:
+ len(stringers_x) * stringer_area)
136
Added:
137
Added:
centroid_x = float(sum([x * cap_area for x in caps_x])
138
Added:
+ sum([x * stringer_area for x in stringers_x]))
139
Added:
centroid_x = centroid_x / denominator
140
Added:
141
Added:
centroid_z = float(sum([z * cap_area for z in caps_z])
142
Added:
+ sum([z * stringer_area for z in stringers_z]))
143
Added:
centroid_z = centroid_z / denominator
144
Added:
145
Added:
return (centroid_x, centroid_z)
146
Added:
147
Added:
def get_inertia_terms(self):
148
Added:
"""Obtain all inertia terms."""
149
Added:
stringer_area = self.stringer.area
150
Added:
cap_area = self.spar.cap_area
151
Added:
152
Added:
# Adds upper and lower components' coordinates to list
153
Added:
x_stringers = self.stringer.x
154
Added:
z_stringers = self.stringer.z
155
Added:
x_spars = self.spar.x[:][0] + self.spar.x[:][1]
156
Added:
z_spars = self.spar.z[:][0] + self.spar.z[:][1]
157
Added:
stringer_count = range(len(x_stringers))
158
Added:
spar_count = range(len(self.spar.x))
159
Added:
160
Added:
# I_x is the sum of the contributions of the spar caps and stringers
161
Added:
# TODO: replace list indices with dictionary value
162
Added:
I_x = sum([cap_area * (z_spars[i] - self.centroid[1])**2
163
Added:
for i in spar_count])
164
Added:
I_x += sum([stringer_area * (z_stringers[i] - self.centroid[1])**2
165
Added:
for i in stringer_count])
166
Added:
167
Added:
I_z = sum([cap_area * (x_spars[i] - self.centroid[0])**2
168
Added:
for i in spar_count])
169
Added:
I_z += sum([stringer_area * (x_stringers[i] - self.centroid[0])**2
170
Added:
for i in stringer_count])
171
Added:
172
Added:
I_xz = sum([cap_area * (x_spars[i] - self.centroid[0])
173
Added:
* (z_spars[i] - self.centroid[1])
174
Added:
for i in spar_count])
175
Added:
I_xz += sum([stringer_area * (x_stringers[i] - self.centroid[0])
176
Added:
* (z_stringers[i] - self.centroid[1])
177
Added:
for i in stringer_count])
178
Added:
return (I_x, I_z, I_xz)
179
Added:
180
Added:
def get_dx(self, component):
181
Added:
return [x - self.centroid[0] for x in component.x_start]
182
Added:
183
Added:
def get_dz(self, component):
184
Added:
return [x - self.centroid[1] for x in component.x_start]
185
Added:
186
Added:
def get_dP(self, xDist, zDist, V_x, V_z, area):
187
Added:
I_x = self.I_['x']
188
Added:
I_z = self.I_['z']
189
Added:
I_xz = self.I_['xz']
190
Added:
denom = float(I_x * I_z - I_xz ** 2)
191
Added:
z = float()
192
Added:
for _ in range(len(xDist)):
193
Added:
z += float(-area * xDist[_] * (I_x * V_x - I_xz * V_z)
194
Added:
/ denom
195
Added:
- area * zDist[_] * (I_z * V_z - I_xz * V_x)
196
Added:
/ denom)
197
Added:
return z
198
Added:
199
Added:
def analysis(self, V_x, V_z):
200
Added:
"""Perform all analysis calculations and store in class instance."""
201
Added:
self.drag = self.get_drag(10)
202
Added:
self.lift_rectangular = self.get_lift_rectangular(13.7)
203
Added:
self.lift_elliptical = self.get_lift_elliptical(15)
204
Added:
self.lift_total = self.get_lift_total()
205
Added:
self.mass_dist = self.get_mass_distribution(self.mass_total)
206
Added:
self.centroid = self.get_centroid()
207
Added:
self.I_['x'] = self.get_inertia_terms()[0]
208
Added:
self.I_['z'] = self.get_inertia_terms()[1]
209
Added:
self.I_['xz'] = self.get_inertia_terms()[2]
210
Added:
spar_dx = self.get_dx(self.spar)
211
Added:
spar_dz = self.get_dz(self.spar)
212
Added:
self.spar.dP_x = self.get_dP(spar_dx, spar_dz,
213
Added:
V_x, 0, self.spar.cap_area)
214
Added:
self.spar.dP_z = self.get_dP(spar_dx, spar_dz,
215
Added:
0, V_z, self.spar.cap_area)
216
Added:
return None
217
Added:
218
Added:
219
Added:
def plot_geom(evaluator):
220
Added:
"""This function plots analysis results over the airfoil's geometry."""
221
Added:
# Plot chord
222
Added:
x_chord = [0, evaluator.chord]
223
Added:
y_chord = [0, 0]
224
Added:
plt.plot(x_chord, y_chord, linewidth='1')
225
Added:
# Plot quarter chord
226
Added:
plt.plot(evaluator.chord / 4, 0,
227
Added:
'.', color='g', markersize=24, label='Quarter-chord')
228
Added:
# Plot airfoil surfaces
229
Added:
x = [0.98 * x for x in evaluator.airfoil.x]
230
Added:
y = [0.98 * z for z in evaluator.airfoil.z]
231
Added:
plt.fill(x, y, color='w', linewidth='1', fill=False)
232
Added:
x = [1.02 * x for x in evaluator.airfoil.x]
233
Added:
y = [1.02 * z for z in evaluator.airfoil.z]
234
Added:
plt.fill(x, y, color='b', linewidth='1', fill=False)
235
Added:
236
Added:
# Plot spars
237
Added:
try:
238
Added:
for _ in range(len(evaluator.spar.x)):
239
Added:
x = (evaluator.spar.x[_])
240
Added:
y = (evaluator.spar.z[_])
241
Added:
plt.plot(x, y, '-', color='b')
242
Added:
except AttributeError:
243
Added:
print('No spars to plot.')
244
Added:
# Plot stringers
245
Added:
try:
246
Added:
for _ in range(0, len(evaluator.stringer.x)):
247
Added:
x = evaluator.stringer.x[_]
248
Added:
y = evaluator.stringer.z[_]
249
Added:
plt.plot(x, y, '.', color='y', markersize=12)
250
Added:
except AttributeError:
251
Added:
print('No stringers to plot.')
252
Added:
253
Added:
# Plot centroid
254
Added:
x = evaluator.centroid[0]
255
Added:
y = evaluator.centroid[1]
256
Added:
plt.plot(x, y, '.', color='r', markersize=24, label='centroid')
257
Added:
258
Added:
# Graph formatting
259
Added:
plt.xlabel('X axis')
260
Added:
plt.ylabel('Z axis')
261
Added:
262
Added:
plot_bound = max(evaluator.airfoil.x)
263
Added:
plt.xlim(-0.10 * plot_bound, 1.10 * plot_bound)
264
Added:
plt.ylim(-(1.10 * plot_bound / 2), (1.10 * plot_bound / 2))
265
Added:
plt.gca().set_aspect('equal', adjustable='box')
266
Added:
plt.gca().legend()
267
Added:
plt.grid(axis='both', linestyle=':', linewidth=1)
268
Added:
plt.show()
269
Added:
return None
270
Added:
271
Added:
272
Added:
def plot_lift(evaluator):
273
Added:
x = range(evaluator.semi_span)
274
Added:
y_1 = evaluator.lift_rectangular
275
Added:
y_2 = evaluator.lift_elliptical
276
Added:
y_3 = evaluator.lift_total
277
Added:
plt.plot(x, y_1, '.', color='b', markersize=4, label='Rectangular lift')
278
Added:
plt.plot(x, y_2, '.', color='g', markersize=4, label='Elliptical lift')
279
Added:
plt.plot(x, y_3, '.', color='r', markersize=4, label='Total lift')
280
Added:
281
Added:
# Graph formatting
282
Added:
plt.xlabel('Semi-span location')
283
Added:
plt.ylabel('Lift')
284
Added:
285
Added:
plt.gca().legend()
286
Added:
plt.grid(axis='both', linestyle=':', linewidth=1)
287
Added:
plt.show()
288
Added:
return None
tools/generator.py
@@ -0,0 +1,64 @@
1
Added:
# This file is part of Marius Peter's airfoil analysis package (this program).
2
Added:
#
3
Added:
# This program is free software: you can redistribute it and/or modify
4
Added:
# it under the terms of the GNU General Public License as published by
5
Added:
# the Free Software Foundation, either version 3 of the License, or
6
Added:
# (at your option) any later version.
7
Added:
#
8
Added:
# This program is distributed in the hope that it will be useful,
9
Added:
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
Added:
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
Added:
# GNU General Public License for more details.
12
Added:
#
13
Added:
# You should have received a copy of the GNU General Public License
14
Added:
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
Added:
"""
16
Added:
The generator.py module contains a single Population class,
17
Added:
which represents a collection of randomized airfoils.
18
Added:
"""
19
Added:
20
Added:
from tools import creator
21
Added:
22
Added:
23
Added:
def default_airfoil():
24
Added:
"""Generate the default airfoil."""
25
Added:
airfoil = creator.Airfoil.from_dimensions(100, 200)
26
Added:
airfoil.add_naca(2412)
27
Added:
airfoil.add_mass(10)
28
Added:
29
Added:
airfoil.spar = creator.Spar()
30
Added:
airfoil.spar.add_coord(airfoil, 0.23)
31
Added:
airfoil.spar.add_coord(airfoil, 0.57)
32
Added:
airfoil.spar.add_spar_caps(0.3)
33
Added:
airfoil.spar.add_mass(10)
34
Added:
airfoil.spar.add_webs(0.4)
35
Added:
36
Added:
airfoil.stringer = creator.Stringer()
37
Added:
airfoil.stringer.add_coord(airfoil, 3, 6, 5, 4)
38
Added:
airfoil.stringer.add_area(0.1)
39
Added:
airfoil.stringer.add_mass(5)
40
Added:
airfoil.stringer.add_webs(0.1)
41
Added:
42
Added:
return airfoil
43
Added:
44
Added:
45
Added:
class Population(creator.Airfoil):
46
Added:
"""Collection of random airfoils."""
47
Added:
48
Added:
def __init__(self, size):
49
Added:
af = creator.Airfoil
50
Added:
# print(af)
51
Added:
self.size = size
52
Added:
self.gen_number = 0 # incremented for every generation
53
Added:
54
Added:
def mutate(self, prob_mt):
55
Added:
"""Randomly mutate the genes of prob_mt % of the population."""
56
Added:
57
Added:
def crossover(self, prob_cx):
58
Added:
"""Combine the genes of prob_cx % of the population."""
59
Added:
60
Added:
def reproduce(self, prob_rp):
61
Added:
"""Pass on the genes of the fittest prob_rp % of the population."""
62
Added:
63
Added:
def fitness():
64
Added:
"""Rate the fitness of an individual on a relative scale (0-100)"""