structure of Evaluator mimicks structure of Coordinates

Commit
3bba98c9869e07fede355d83fd2498996e8a54fc
Author
Marius Peter <blendoit@gmail.com>
Author date
Committer
Marius Peter <blendoit@gmail.com>
Committer date
Changed files
creator.py
index 15c090f7..a4f67ad5 100644..100644
@@ -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
index 6965895b..59d233ae 100644..100644
@@ -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
index e5360ff2..821bbb6e 100644..100644
@@ -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))