*ARCHIVED* development moved to aircraft-studio.
structure of Evaluator mimicks structure of Coordinates
Changed files
creator.py
@@ -13,6 +13,7 @@
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
15
16
Added:
16
17
import sys
17
18
import os.path
18
19
import numpy as np
@@ -29,7 +30,7 @@
29
30
30
31
31
32
class Coordinates:
32
Removed:
"""
33
Added:
'''
33
34
All airfoil components need the following:
34
35
35
36
Parameters:
@@ -41,7 +42,7 @@
41
42
* Save component coordinates to file specified in main.py
42
43
43
44
So, all component classes inherit from class Coordinates.
44
Removed:
"""
45
Added:
'''
45
46
46
47
def __init__(self, chord, semi_span):
47
48
# Global dimensions
@@ -69,18 +70,18 @@
69
70
return type(self).__name__
70
71
71
72
def print_info(self, round):
72
Removed:
"""
73
Added:
'''
73
74
Print all the component's coordinates to the terminal.
74
75
75
76
This function's output is piped to the 'save_coord' function below.
76
Removed:
"""
77
Removed:
print('============================')
78
Removed:
print(' CREATOR DATA ')
77
Added:
'''
78
Added:
print(20 * '-')
79
Added:
print(' CREATOR DATA ')
79
80
print('Component:', str(self))
80
81
print('Chord length:', self.chord)
81
82
print('Semi-span:', self.semi_span)
82
83
print('Mass:', self.mass)
83
Removed:
print('============================')
84
Added:
print(20 * '-')
84
85
print('x_u the upper x-coordinates:\n', np.around(self.x_u, round))
85
86
print('z_u the upper z-coordinates:\n', np.around(self.z_u, round))
86
87
print('x_l the lower x-coordinates:\n', np.around(self.x_l, round))
@@ -88,15 +89,15 @@
88
89
return None
89
90
90
91
def save_info(self, save_dir_path, number):
91
Removed:
"""
92
Added:
'''
92
93
Save all the object's coordinates (must be full path).
93
Removed:
"""
94
Added:
'''
94
95
95
Removed:
file_name = '{}_{}.txt'.format(self, number)
96
Added:
file_name = '{}_{}.txt'.format(str(self).lower(), number)
96
97
full_path = os.path.join(save_dir_path, file_name)
97
98
try:
98
99
with open(full_path, 'w') as sys.stdout:
99
Removed:
self.print_info(2)
100
Added:
self.print_info(6)
100
101
# This line required to reset behavior of sys.stdout
101
102
sys.stdout = sys.__stdout__
102
103
print('Successfully wrote to file {}'.format(full_path))
@@ -115,7 +116,7 @@
115
116
116
117
117
118
class Airfoil(Coordinates):
118
Removed:
"""This class enables the creation of a single NACA airfoil."""
119
Added:
'''This class enables the creation of a single NACA airfoil.'''
119
120
120
121
def __init__(self):
121
122
global parent
@@ -128,7 +129,7 @@
128
129
self.y_c = []
129
130
130
131
def add_naca(self, naca_num):
131
Removed:
"""
132
Added:
'''
132
133
This function generates geometry for our chosen NACA airfoil shape.
133
134
The nested functions perform the required steps to generate geometry,
134
135
and can be called to solve the geometry y-coordinate for any 'x' input.
@@ -139,7 +140,7 @@
139
140
140
141
Return:
141
142
None
142
Removed:
"""
143
Added:
'''
143
144
144
145
# Variables extracted from 'naca_num' argument passed to the function
145
146
self.naca_num = naca_num
@@ -150,9 +151,9 @@
150
151
p_c = p * self.chord
151
152
152
153
def get_camber(x):
153
Removed:
"""
154
Added:
'''
154
155
Returns camber y-coordinate from 1 'x' along the airfoil chord.
155
Removed:
"""
156
Added:
'''
156
157
y_c = float()
157
158
if 0 <= x < p_c:
158
159
y_c = (m / (p ** 2)) * (2 * p * (x / self.chord)
@@ -164,9 +165,9 @@
164
165
return (y_c * self.chord)
165
166
166
167
def get_thickness(x):
167
Removed:
"""
168
Added:
'''
168
169
Returns thickness from 1 'x' along the airfoil chord.
169
Removed:
"""
170
Added:
'''
170
171
y_t = 5 * t * self.chord * (
171
172
+ 0.2969 * sqrt(x / self.chord)
172
173
- 0.1260 * (x / self.chord)
@@ -222,14 +223,14 @@
222
223
223
224
224
225
class Spar(Coordinates):
225
Removed:
"""Contains a single spar's location."""
226
Added:
'''Contains a single spar's location.'''
226
227
global parent
227
228
228
229
def __init__(self):
229
230
super().__init__(parent.chord, parent.semi_span)
230
231
231
232
def add_coord(self, airfoil_coord, spar_x):
232
Removed:
"""
233
Added:
'''
233
234
Add a single spar at the % chord location given to function.
234
235
235
236
Parameters:
@@ -239,7 +240,7 @@
239
240
240
241
Return:
241
242
None
242
Removed:
"""
243
Added:
'''
243
244
# Airfoil surface coordinates
244
245
# unpacked from 'coordinates' (list of lists in 'Coordinates').
245
246
x_u = airfoil_coord[0]
@@ -266,7 +267,7 @@
266
267
267
268
268
269
class Stringer(Coordinates):
269
Removed:
"""Contains the coordinates of all stringers."""
270
Added:
'''Contains the coordinates of all stringers.'''
270
271
global parent
271
272
272
273
def __init__(self):
@@ -275,7 +276,7 @@
275
276
276
277
def add_coord(self, airfoil_coord, spar_coord,
277
278
stringer_u_1, stringer_u_2, stringer_l_1, stringer_l_2):
278
Removed:
"""
279
Added:
'''
279
280
Add equally distributed stringers to four airfoil locations
280
281
(upper nose, lower nose, upper surface, lower surface).
281
282
@@ -289,7 +290,7 @@
289
290
290
291
Returns:
291
292
None
292
Removed:
"""
293
Added:
'''
293
294
294
295
# Airfoil surface coordinates
295
296
# unpacked from 'coordinates' (list of lists in 'Coordinates').
@@ -359,7 +360,7 @@
359
360
360
361
361
362
def plot(airfoil, spar, stringer):
362
Removed:
"""This function plots the elements passed as arguments."""
363
Added:
'''This function plots the elements passed as arguments.'''
363
364
364
365
# Plot chord
365
366
x_chord = [0, airfoil.chord]
evaluator.py
@@ -13,13 +13,14 @@
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program. If not, see <https://www.gnu.org/licenses/>.
15
15
16
Added:
16
17
import sys
17
18
import os.path
18
19
import numpy as np
19
20
from math import sin, cos, atan, sqrt
20
21
21
22
22
Removed:
class Airfoil:
23
Added:
class Evaluator:
23
24
'''Performs structural evaluations for the airfoil passed as argument.'''
24
25
25
26
def __init__(self, airfoil):
@@ -28,7 +29,7 @@
28
29
self.chord = airfoil.chord
29
30
self.semi_span = airfoil.semi_span
30
31
# mass and area
31
Removed:
self.mass_total = float()
32
Added:
self.mass_total = airfoil.mass + airfoil.spar.mass + airfoil.stringer.mass
32
33
self.mass_dist = []
33
34
34
35
self.lift_rectangular = []
@@ -37,22 +38,20 @@
37
38
38
39
self.drag = []
39
40
40
Removed:
def __str__(self):
41
Removed:
return type(self).__name__
42
Removed:
43
41
def print_info(self, round):
44
Removed:
"""
42
Added:
'''
45
43
Print all the component's evaluated data to the terminal.
46
44
47
45
This function's output is piped to the 'save_data' function below.
48
Removed:
"""
49
Removed:
print('============================')
50
Removed:
print(' EVALUATOR DATA ')
51
Removed:
print('Evaluating:', str(self.airfoil))
46
Added:
'''
47
Added:
48
Added:
print(22 * '-')
49
Added:
print(' EVALUATOR DATA ')
50
Added:
print('Evaluating:', self.airfoil)
52
51
print('Chord length:', self.chord)
53
52
print('Semi-span:', self.semi_span)
54
53
print('Total airfoil mass:', self.mass_total)
55
Removed:
print('============================')
54
Added:
print(22 * '-')
56
55
print('Rectangular lift:\n', np.around(self.lift_rectangular, round))
57
56
print('Elliptical lift:\n', np.around(self.lift_elliptical, round))
58
57
print('Combined lift:\n', np.around(self.lift, round))
@@ -61,15 +60,13 @@
61
60
return None
62
61
63
62
def save_info(self, save_dir_path, number):
64
Removed:
"""
65
Removed:
Save all the object's coordinates (must be full path).
66
Removed:
"""
63
Added:
'''Save all the object's coordinates (must be full path).'''
67
64
68
Removed:
file_name = '{}_{}.txt'.format(self, number)
65
Added:
file_name = 'airfoil_{}_eval.txt'.format(number)
69
66
full_path = os.path.join(save_dir_path, file_name)
70
67
try:
71
68
with open(full_path, 'w') as sys.stdout:
72
Removed:
self.print_info(2)
69
Added:
self.print_info(6)
73
70
# This line required to reset behavior of sys.stdout
74
71
sys.stdout = sys.__stdout__
75
72
print('Successfully wrote to file {}'.format(full_path))
@@ -79,53 +76,57 @@
79
76
'Was the full path passed to the function?')
80
77
return None
81
78
82
Removed:
def get_mass_total(airfoil):
83
Removed:
total_mass = airfoil.mass + airfoil.spar.mass + airfoil.stringer.mass
84
Removed:
return total_mass
79
Added:
# def get_mass_total(airfoil):
80
Added:
# total_mass = airfoil.mass + airfoil.spar.mass + airfoil.stringer.mass
81
Added:
# return total_mass
85
82
86
83
# All these functions take integer arguments and return lists.
87
84
88
Removed:
def get_lift_rectangular(airfoil, lift):
89
Removed:
L_prime = [lift / (airfoil.semi_span * 2)
90
Removed:
for x in range(airfoil.semi_span)]
85
Added:
def get_lift_rectangular(self, lift):
86
Added:
L_prime = [lift / (self.semi_span * 2)
87
Added:
for x in range(self.semi_span)]
91
88
return L_prime
92
89
93
Removed:
def get_lift_elliptical(airfoil, L_0):
94
Removed:
L_prime = [L_0 * sqrt(1 - (y / airfoil.semi_span) ** 2)
95
Removed:
for y in range(airfoil.semi_span)]
90
Added:
def get_lift_elliptical(self, L_0):
91
Added:
L_prime = [L_0 * sqrt(1 - (y / self.semi_span) ** 2)
92
Added:
for y in range(self.semi_span)]
96
93
return L_prime
97
94
98
Removed:
def get_lift(rectangular, elliptical):
99
Removed:
F_z = [(rectangular[_] + elliptical[_]) / 2
100
Removed:
for _ in range(len(rectangular))]
95
Added:
def get_lift_total(self):
96
Added:
F_z = [(self.lift_rectangular[_] + self.lift_elliptical[_]) / 2
97
Added:
for _ in range(len(self.lift_rectangular))]
101
98
return F_z
102
99
103
Removed:
def get_mass_distribution(airfoil, total_mass):
104
Removed:
F_z = [total_mass / airfoil.semi_span
105
Removed:
for x in range(0, airfoil.semi_span)]
100
Added:
def get_mass_distribution(self, total_mass):
101
Added:
F_z = [total_mass / self.semi_span
102
Added:
for x in range(0, self.semi_span)]
106
103
return F_z
107
104
108
Removed:
def get_drag(airfoil, drag):
105
Added:
def get_drag(self, drag):
109
106
# Transform semi-span integer into list
110
Removed:
semi_span = [x for x in range(0, airfoil.semi_span)]
107
Added:
semi_span = [x for x in range(0, self.semi_span)]
111
108
112
109
# Drag increases after 80% of the semi_span
113
Removed:
cutoff = round(0.8 * airfoil.semi_span)
110
Added:
cutoff = round(0.8 * self.semi_span)
114
111
115
112
# Drag increases by 25% after 80% of the semi_span
116
113
F_x = [drag for x in semi_span[0:cutoff]]
117
114
F_x.extend([1.25 * drag for x in semi_span[cutoff:]])
118
115
return F_x
119
116
120
Removed:
def evaluate(self):
121
Removed:
self.drag = self.get_drag(self.airfoil, 10)
117
Added:
def analysis(self):
118
Added:
'''
119
Added:
Perform all analysis calculations and store in class instance.
120
Added:
'''
122
121
122
Added:
self.drag = self.get_drag(10)
123
Added:
123
124
self.lift_rectangular = self.get_lift_rectangular(10)
124
125
self.lift_elliptical = self.get_lift_elliptical(15)
125
Removed:
self.lift = self.get_lift(self.lift_rectangular, self.lift_elliptical)
126
Added:
self.lift = self.get_lift_total()
126
127
127
Removed:
self.mass_total = self.get_mass_total()
128
Removed:
self.mass_dist = self.get_mass_distribution(self.total_mass)
128
Added:
# self.mass_total = self.get_mass_total()
129
Added:
self.mass_dist = self.get_mass_distribution(self.mass_total)
129
130
return None
130
131
131
132
# def get_centroid(airfoil):
main.py
@@ -50,7 +50,7 @@
50
50
# TODO: imperial + metric unit setting
51
51
creator.Coordinates(CHORD_LENGTH, SEMI_SPAN)
52
52
53
Removed:
# Interate through all wings in population.
53
Added:
# Interate through all wings in population, creating and evaluating them.
54
54
for _ in range(1, POP_SIZE + 1):
55
55
56
56
# Create airfoil instance
@@ -80,14 +80,16 @@
80
80
# creator.plot(af, af.spar, af.stringer)
81
81
82
82
# Save component info
83
Removed:
# af.save_info(SAVE_PATH, _)
84
Removed:
# af.spar.save_info(SAVE_PATH, _)
85
Removed:
# af.stringer.save_info(SAVE_PATH, _)
83
Added:
af.save_info(SAVE_PATH, _)
84
Added:
af.spar.save_info(SAVE_PATH, _)
85
Added:
af.stringer.save_info(SAVE_PATH, _)
86
86
87
Removed:
# evaluator.Airfoil instance contains the results of the airfoil analysis.
88
Removed:
# The analysis itself takes place in the evaluator.py module.
89
Removed:
eval = evaluator.Airfoil(af)
90
Removed:
eval.print_info(2)
87
Added:
# evaluator.Evaluator instance contains airfoil analysis results.
88
Added:
eval = evaluator.Evaluator(af)
89
Added:
# The analysis is performed in the evaluator.py module.
90
Added:
eval.analysis()
91
Added:
eval.print_info(2)
92
Added:
eval.save_info(SAVE_PATH, _)
91
93
92
94
# Print final execution time
93
95
print("--- %s seconds ---" % (time.time() - start_time))