diff options
author | Blendoit <51464356+Blendoit@users.noreply.github.com> | 2019-06-17 18:37:56 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-17 18:37:56 -0700 |
commit | e66d67542100b0b4e7d405ce0b9dea4333559cde (patch) | |
tree | 9ffee7ee2776e0269e13743b924c05e97e22aa0d | |
parent | eaccaca78eb87c8e88aa94eb3ac81e97ef6cdcbd (diff) | |
parent | 58c8a564e87f6325d66972d6c971eea8c465ba2e (diff) |
Merge pull request #3 from Blendoit/evaluator
Evaluator
-rw-r--r-- | __init__.py | 4 | ||||
-rw-r--r-- | creator.py | 869 | ||||
-rw-r--r-- | evaluator.py | 20 | ||||
-rw-r--r-- | main.py | 56 |
4 files changed, 509 insertions, 440 deletions
diff --git a/__init__.py b/__init__.py index 5e916af..dc031d0 100644 --- a/__init__.py +++ b/__init__.py @@ -13,5 +13,5 @@ # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
__author__ = "Marius Peter"
-__version__ = "2.3"
-__revision__ = "2.3.1"
+# __version__ = "2.3"
+# __revision__ = "2.3.1"
@@ -1,422 +1,447 @@ -# This file is part of Marius Peter's airfoil analysis package (this program).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-import sys
-import os.path
-import numpy as np
-from math import sin, cos, tan, atan, sqrt, ceil
-import bisect as bi
-import matplotlib.pyplot as plt
-import matplotlib as mpl
-from mpl_toolkits.mplot3d import Axes3D
-
-# This variable is required for main.py constant wing dimensions
-# to be passed to inheriting classes (Airfoil, Spar, Stringer, Rib).
-# This way, we don't have to redeclare our coordinates as parameters for
-# our spars, stringers and ribs. This makes for more elegant code.
-global parent
-
-
-class Coordinates:
- """
- All airfoil components need the following:
-
- Parameters:
- * Component material
- * Coordinates relative to the chord & semi-span.
-
- Methods:
- * Print component coordinates
- * Save component coordinates to file specified in main.py
-
- So, all component classes inherit from class Coordinates.
- """
-
- def __init__(self, chord, semi_span):
- # Global dimensions
- self.chord = chord
- if chord < 10:
- self.chord = 10
- self.semi_span = semi_span
- # Component material
- self.material = str()
- # Upper coordinates
- self.x_u = []
- self.y_u = []
- # Lower coordinates
- self.x_l = []
- self.y_l = []
- # Coordinates x_u, y_u, x_l, y_l packed in single list
- self.coord = []
- # The airfoil components know the Coordinates instance's coords
- global parent
- parent = self
-
- def __str__(self):
- return type(self).__name__
-
- def print_coord(self, round):
- """
- Print all the component's coordinates to the terminal.
-
- This function's output is piped to the 'save_coord' function below.
- """
- print('============================')
- print('Component:', str(self))
- print('Chord length:', self.chord)
- print('Semi-span:', self.semi_span)
- print('============================')
- print('x_u the upper x-coordinates:\n', np.around(self.x_u, round))
- print('y_u the upper y-coordinates:\n', np.around(self.y_u, round))
- print('x_l the lower x-coordinates:\n', np.around(self.x_l, round))
- print('y_l the lower y-coordinates:\n', np.around(self.y_l, round))
- # print('\n')
- return None
-
- def save_coord(self, save_dir_path, number):
- """
- Save all the object's coordinates (must be full path).
- """
-
- file_name = '{}_{}.txt'.format(self, number)
- full_path = os.path.join(save_dir_path, file_name)
- # sys.stdout = open(full_path, 'w')
- # self.print_coord(2)
- with open(full_path, 'w') as sys.stdout:
- self.print_coord(2)
- # Following line required to reset value of sys.stdout
- sys.stdout = sys.__stdout__
- # It is cleaner to use this context guard to ensure file is closed
-
- return None
-
- def pack_coord(self):
- self.coord.append(self.x_u)
- self.coord.append(self.y_u)
- self.coord.append(self.x_l)
- self.coord.append(self.y_l)
-
-
-class Airfoil(Coordinates):
- """This class enables the creation of a single NACA airfoil."""
-
- def __init__(self):
- global parent
- # Run 'Coordinates' super class init method with same chord & 1/2 span.
- super().__init__(parent.chord, parent.semi_span)
- # NACA number
- self.naca_num = int()
- # Mean camber line
- self.x_c = [] # Contains only integers from 0 to self.chord
- self.y_c = [] # Contains floats
- # Thickness
- self.y_t = []
- # dy_c / d_x
- self.dy_c = []
- # Theta
- self.theta = []
-
- def add_naca(self, naca_num):
- """
- This function generates geometry for our chosen NACA airfoil shape.
- The nested functions perform the required steps to generate geometry,
- and can be called to solve the geometry y-coordinate for any 'x' input.
- Equation coefficients were retrieved from Wikipedia.org.
-
- Parameters:
- naca_num: 4-digit NACA wing
-
- Return:
- None
- """
-
- # Variables extracted from 'naca_num' argument passed to the function
- self.naca_num = naca_num
- m = int(str(naca_num)[0]) / 100
- p = int(str(naca_num)[1]) / 10
- t = int(str(naca_num)[2:]) / 100
- # x-coordinate of maximum camber
- p_c = p * self.chord
-
- def get_camber(x):
- """
- Returns 1 camber y-coordinate from 1 'x' along the airfoil chord.
- """
- x_c = x
- y_c = float()
- if 0 <= x < p_c:
- y_c = (m / (p**2)) * (2 * p * (x / self.chord) -
- (x / self.chord)**2)
- elif p_c <= x <= self.chord:
- y_c = (m /
- ((1 - p)**2)) * ((1 - 2 * p) + 2 * p *
- (x / self.chord) - (x / self.chord)**2)
- else:
- print('x-coordinate for camber is out of bounds. '
- 'Check that 0 < x <= chord.')
- return (x_c, y_c * self.chord)
-
- def get_thickness(x):
- """
- Returns thickness from 1 'x' along the airfoil chord.
- """
- y_t = float()
- if 0 <= x <= self.chord:
- y_t = 5 * t * self.chord * (0.2969 * sqrt(x / self.chord) -
- 0.1260 *
- (x / self.chord) - 0.3516 *
- (x / self.chord)**2 + 0.2843 *
- (x / self.chord)**3 - 0.1015 *
- (x / self.chord)**4)
- else:
- print('x-coordinate for thickness is out of bounds. '
- 'Check that 0 < x <= chord.')
- return y_t
-
- def get_dy_c(x):
- """
- Returns dy_c/dx from 1 'x' along the airfoil chord.
- """
- dy_c = float()
- if 0 <= x < p_c:
- dy_c = ((2 * m) / p**2) * (p - x / self.chord)
- elif p_c <= x <= self.chord:
- dy_c = (2 * m) / ((1 - p)**2) * (p - x / self.chord)
- return dy_c
-
- def get_theta(dy_c):
- theta = atan(dy_c)
- return theta
-
- def get_upper_coordinates(x):
- x_u = float()
- y_u = float()
- if 0 <= x < self.chord:
- x_u = x - self.y_t[x] * sin(self.theta[x])
- y_u = self.y_c[x] + self.y_t[x] * cos(self.theta[x])
- elif x == self.chord:
- x_u = x - self.y_t[x] * sin(self.theta[x])
- y_u = 0 # Make upper curve finish at y = 0
- return (x_u, y_u)
-
- def get_lower_coordinates(x):
- x_l = float()
- y_l = float()
- if 0 <= x < self.chord:
- x_l = (x + self.y_t[x] * sin(self.theta[x]))
- y_l = (self.y_c[x] - self.y_t[x] * cos(self.theta[x]))
- elif x == self.chord:
- x_l = (x + self.y_t[x] * sin(self.theta[x]))
- y_l = 0 # Make lower curve finish at y = 0
- return (x_l, y_l)
-
- # Generate all our wing geometries from previous sub-functions
- for x in range(0, self.chord + 1):
- self.x_c.append(get_camber(x)[0])
- self.y_c.append(get_camber(x)[1])
- self.y_t.append(get_thickness(x))
- self.dy_c.append(get_dy_c(x))
- self.theta.append(get_theta(self.dy_c[x]))
- self.x_u.append(get_upper_coordinates(x)[0])
- self.y_u.append(get_upper_coordinates(x)[1])
- self.x_l.append(get_lower_coordinates(x)[0])
- self.y_l.append(get_lower_coordinates(x)[1])
-
- super().pack_coord()
- return None
-
-
-class Spar(Coordinates):
- """Contains a single spar's location."""
- global parent
-
- def __init__(self):
- super().__init__(parent.chord, parent.semi_span)
-
- def add(self, airfoil_coord, spar_x):
- """
- Add a single spar at the % chord location given to function.
-
- Parameters:
- coordinates: provided by Airfoil.coordinates[x_u, y_u, x_l, y_l].
- material: spar's material. Assumes homogeneous material.
- spar_x: spar's location as a % of total chord length.
-
- Return:
- None
- """
- # Airfoil surface coordinates
- # unpacked from 'coordinates' (list of lists in 'Coordinates').
- x_u = airfoil_coord[0]
- y_u = airfoil_coord[1]
- x_l = airfoil_coord[2]
- y_l = airfoil_coord[3]
- # Scaled spar location with regards to chord
- loc = spar_x * self.chord
- # bisect_left: returns index of first value in x_u > loc.
- # This ensures that the spar coordinates intersect with airfoil surface.
- spar_x_u = bi.bisect_left(x_u, loc) # index of spar's x_u
- spar_x_l = bi.bisect_left(x_l, loc) # index of spar's x_l
- # These x and y coordinates are assigned to the spar, NOT airfoil.
- self.x_u.append(x_u[spar_x_u])
- self.y_u.append(y_u[spar_x_u])
- self.x_l.append(x_l[spar_x_l])
- self.y_l.append(y_l[spar_x_l])
-
- super().pack_coord()
- return None
-
-
-class Stringer(Coordinates):
- """Contains the coordinates of all stringers."""
- global parent
-
- def __init__(self):
- super().__init__(parent.chord, parent.semi_span)
-
- def add(self, airfoil_coord, spar_coord, stringer_u_1, stringer_u_2,
- stringer_l_1, stringer_l_2):
- """
- Add equally distributed stringers to four airfoil locations
- (upper nose, lower nose, upper surface, lower surface).
-
- Parameters:
- stringer_u_1: upper nose number of stringers
- stringer_u_2: upper surface number of stringers
- stringer_l_1: lower nose number of stringers
- stringer_l_2: lower surface number of stringers
-
- Returns:
- None
- """
-
- # Airfoil surface coordinates
- # unpacked from 'coordinates' (list of lists in 'Coordinates').
- airfoil_x_u = airfoil_coord[0]
- airfoil_y_u = airfoil_coord[1]
- airfoil_x_l = airfoil_coord[2]
- airfoil_y_l = airfoil_coord[3]
- # Spar coordinates
- # unpacked from 'coordinates' (list of lists in 'Coordinates').
- try:
- spar_x_u = spar_coord[0]
- spar_y_u = spar_coord[1]
- spar_x_l = spar_coord[2]
- spar_y_l = spar_coord[3]
- except:
- print('Unable to initialize stringers. Were spars created?')
- # Find distance between leading edge and first upper stringer
- interval = spar_x_u[0] / (stringer_u_1 + 1)
- # initialise first self.stringer_x_u at first interval
- x = interval
- # Add upper stringers from leading edge until first spar.
- for _ in range(0, stringer_u_1):
- # Index of the first value of airfoil_x_u > x
- index = bi.bisect_left(airfoil_x_u, x)
- self.x_u.append(airfoil_x_u[index])
- self.y_u.append(airfoil_y_u[index])
- x += interval
- # Add upper stringers from first spar until last spar
- interval = (spar_x_u[-1] - spar_x_u[0]) / (stringer_u_2 + 1)
- x = interval + spar_x_u[0]
- for _ in range(0, stringer_u_2):
- index = bi.bisect_left(airfoil_x_u, x)
- self.x_u.append(airfoil_x_u[index])
- self.y_u.append(airfoil_y_u[index])
- x += interval
-
- # Find distance between leading edge and first lower stringer
- interval = spar_x_l[0] / (stringer_l_1 + 1)
- x = interval
- # Add lower stringers from leading edge until first spar.
- for _ in range(0, stringer_l_1):
- index = bi.bisect_left(airfoil_x_l, x)
- self.x_l.append(airfoil_x_l[index])
- self.y_l.append(airfoil_y_l[index])
- x += interval
- # Add lower stringers from first spar until last spar
- interval = (spar_x_l[-1] - spar_x_l[0]) / (stringer_l_2 + 1)
- x = interval + spar_x_l[0]
- for _ in range(0, stringer_l_2):
- index = bi.bisect_left(airfoil_x_l, x)
- self.x_l.append(airfoil_x_l[index])
- self.y_l.append(airfoil_y_l[index])
- x += interval
-
- super().pack_coord()
- return None
-
-
-def plot(airfoil, spar, stringer):
- """This function plots the elements passed as arguments."""
-
- print('Plotting airfoil.')
- # Plot chord
- x_chord = [0, airfoil.chord]
- y_chord = [0, 0]
- plt.plot(x_chord, y_chord, linewidth='1')
- # Plot mean camber line
- plt.plot(airfoil.x_c,
- airfoil.y_c,
- '-.',
- color='r',
- linewidth='2',
- label='mean camber line')
- # Plot upper surface
- plt.plot(airfoil.x_u, airfoil.y_u, '', color='b', linewidth='1')
- # Plot lower surface
- plt.plot(airfoil.x_l, airfoil.y_l, '', color='b', linewidth='1')
-
- # Plot spars
- try:
- for _ in range(0, len(spar.x_u)):
- x = (spar.x_u[_], spar.x_l[_])
- y = (spar.y_u[_], spar.y_l[_])
- plt.plot(x, y, '.-', color='b')
- plt.legend()
- except:
- print('Did not plot spars. Were they added?')
-
- # Plot stringers
- try:
- # Upper stringers
- for _ in range(0, len(stringer.x_u)):
- x = stringer.x_u[_]
- y = stringer.y_u[_]
- plt.plot(x, y, '.', color='y')
- # Lower stringers
- for _ in range(0, len(stringer.x_l)):
- x = stringer.x_l[_]
- y = stringer.y_l[_]
- plt.plot(x, y, '.', color='y')
- except:
- print('Unable to plot stringers. Were they created?')
-
- # Graph formatting
- plt.gcf().set_size_inches(9, 2.2)
- plt.xlabel('X axis')
- plt.ylabel('Y axis')
- # plt.gcf().set_size_inches(self.chord, max(self.y_u) - min(self.y_l))
- plt.grid(axis='both', linestyle=':', linewidth=1)
- plt.show()
- return None
-
-
-def main():
- return None
-
-
-if __name__ == '__main__':
- main()
+# This file is part of Marius Peter's airfoil analysis package (this program). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +import sys +import os.path +import numpy as np +from math import sin, cos, tan, atan, sqrt, ceil +import bisect as bi +import matplotlib.pyplot as plt + +# This variable is required for main.py constant wing dimensions +# to be passed to inheriting classes (Airfoil, Spar, Stringer, Rib). +# This way, we don't have to redeclare our coordinates as parameters for +# our spars, stringers and ribs. This makes for more elegant code. +global parent + + +class Coordinates: + """ + All airfoil components need the following: + + Parameters: + * Component material + * Coordinates relative to the chord & semi-span. + + Methods: + * Print component coordinates + * Save component coordinates to file specified in main.py + + So, all component classes inherit from class Coordinates. + """ + + def __init__(self, chord, semi_span): + # Global dimensions + self.chord = chord + if chord < 10: + self.chord = 10 + self.semi_span = semi_span + # mass and area + self.mass = float() + self.area = float() + # Component material + self.material = str() + # Upper coordinates + self.x_u = [] + self.z_u = [] + # Lower coordinates + self.x_l = [] + self.z_l = [] + # Coordinates x_u, z_u, x_l, z_l packed in single list + self.coord = [] + + # The airfoil components know the Coordinates instance's coords + global parent + parent = self + + def __str__(self): + return type(self).__name__ + + def print_info(self, round): + """ + Print all the component's coordinates to the terminal. + + This function's output is piped to the 'save_coord' function below. + """ + print('============================') + print('Component:', str(self)) + print('Chord length:', self.chord) + print('Semi-span:', self.semi_span) + print('Mass:', self.mass) + print('============================') + print('x_u the upper x-coordinates:\n', np.around(self.x_u, round)) + print('z_u the upper y-coordinates:\n', np.around(self.z_u, round)) + print('x_l the lower x-coordinates:\n', np.around(self.x_l, round)) + print('z_l the lower y-coordinates:\n', np.around(self.z_l, round)) + return None + + def save_info(self, save_dir_path, number): + """ + Save all the object's coordinates (must be full path). + """ + + file_name = '{}_{}.txt'.format(self, number) + full_path = os.path.join(save_dir_path, file_name) + try: + with open(full_path, 'w') as sys.stdout: + self.print_info(2) + # This line required to reset behavior of sys.stdout + sys.stdout = sys.__stdout__ + print('Successfully wrote to file {}'.format(full_path)) + except: + print('Unable to write {} to specified directory.\n' + .format(file_name), + 'Was the full path passed to the function?') + # It is cleaner to use this context guard to ensure file is closed + return None + + def pack_info(self): + self.coord.append(self.x_u) + self.coord.append(self.z_u) + self.coord.append(self.x_l) + self.coord.append(self.z_l) + return None + + +class Airfoil(Coordinates): + """This class enables the creation of a single NACA airfoil.""" + + def __init__(self): + global parent + # Run 'Coordinates' super class init method with same chord & 1/2 span. + super().__init__(parent.chord, parent.semi_span) + # NACA number + self.naca_num = int() + # Mean camber line + self.x_c = [] # Contains only integers from 0 to self.chord + self.y_c = [] # Contains floats + # Thickness + self.y_t = [] + # dy_c / d_x + self.dy_c = [] + # Theta + self.theta = [] + + def add_naca(self, naca_num): + """ + This function generates geometry for our chosen NACA airfoil shape. + The nested functions perform the required steps to generate geometry, + and can be called to solve the geometry y-coordinate for any 'x' input. + Equation coefficients were retrieved from Wikipedia.org. + + Parameters: + naca_num: 4-digit NACA wing + + Return: + None + """ + + # Variables extracted from 'naca_num' argument passed to the function + self.naca_num = naca_num + m = int(str(naca_num)[0]) / 100 + p = int(str(naca_num)[1]) / 10 + t = int(str(naca_num)[2:]) / 100 + # x-coordinate of maximum camber + p_c = p * self.chord + + def get_camber(x): + """ + Returns 1 camber y-coordinate from 1 'x' along the airfoil chord. + """ + x_c = x + y_c = float() + if 0 <= x < p_c: + y_c = (m / (p**2)) * (2 * p * (x / self.chord) - + (x / self.chord)**2) + elif p_c <= x <= self.chord: + y_c = (m / + ((1 - p)**2)) * ((1 - 2 * p) + 2 * p * + (x / self.chord) - (x / self.chord)**2) + else: + print('x-coordinate for camber is out of bounds. ' + 'Check that 0 < x <= chord.') + return (x_c, y_c * self.chord) + + def get_thickness(x): + """ + Returns thickness from 1 'x' along the airfoil chord. + """ + y_t = float() + if 0 <= x <= self.chord: + y_t = 5 * t * self.chord * (0.2969 * sqrt(x / self.chord) - + 0.1260 * + (x / self.chord) - 0.3516 * + (x / self.chord)**2 + 0.2843 * + (x / self.chord)**3 - 0.1015 * + (x / self.chord)**4) + else: + print('x-coordinate for thickness is out of bounds. ' + 'Check that 0 < x <= chord.') + return y_t + + def get_dy_c(x): + """ + Returns dy_c/dx from 1 'x' along the airfoil chord. + """ + dy_c = float() + if 0 <= x < p_c: + dy_c = ((2 * m) / p**2) * (p - x / self.chord) + elif p_c <= x <= self.chord: + dy_c = (2 * m) / ((1 - p)**2) * (p - x / self.chord) + return dy_c + + def get_theta(dy_c): + theta = atan(dy_c) + return theta + + def get_upper_coordinates(x): + x_u = float() + z_u = float() + if 0 <= x < self.chord: + x_u = x - self.y_t[x] * sin(self.theta[x]) + z_u = self.y_c[x] + self.y_t[x] * cos(self.theta[x]) + elif x == self.chord: + x_u = x - self.y_t[x] * sin(self.theta[x]) + z_u = 0 # Make upper curve finish at y = 0 + return (x_u, z_u) + + def get_lower_coordinates(x): + x_l = float() + z_l = float() + if 0 <= x < self.chord: + x_l = (x + self.y_t[x] * sin(self.theta[x])) + z_l = (self.y_c[x] - self.y_t[x] * cos(self.theta[x])) + elif x == self.chord: + x_l = (x + self.y_t[x] * sin(self.theta[x])) + z_l = 0 # Make lower curve finish at y = 0 + return (x_l, z_l) + + # Generate all our wing geometries from previous sub-functions + for x in range(0, self.chord + 1): + self.x_c.append(get_camber(x)[0]) + self.y_c.append(get_camber(x)[1]) + self.y_t.append(get_thickness(x)) + self.dy_c.append(get_dy_c(x)) + self.theta.append(get_theta(self.dy_c[x])) + self.x_u.append(get_upper_coordinates(x)[0]) + self.z_u.append(get_upper_coordinates(x)[1]) + self.x_l.append(get_lower_coordinates(x)[0]) + self.z_l.append(get_lower_coordinates(x)[1]) + + super().pack_info() + return None + + def add_mass(self, mass): + self.mass = mass + + +class Spar(Coordinates): + """Contains a single spar's location.""" + global parent + + def __init__(self): + super().__init__(parent.chord, parent.semi_span) + + def add_coord(self, airfoil_coord, spar_x): + """ + Add a single spar at the % chord location given to function. + + Parameters: + coordinates: provided by Airfoil.coordinates[x_u, z_u, x_l, z_l]. + material: spar's material. Assumes homogeneous material. + spar_x: spar's location as a % of total chord length. + + Return: + None + """ + # Airfoil surface coordinates + # unpacked from 'coordinates' (list of lists in 'Coordinates'). + x_u = airfoil_coord[0] + z_u = airfoil_coord[1] + x_l = airfoil_coord[2] + z_l = airfoil_coord[3] + # Scaled spar location with regards to chord + loc = spar_x * self.chord + # bisect_left: returns index of first value in x_u > loc. + # This ensures that the spar coordinates intersect with airfoil surface. + spar_x_u = bi.bisect_left(x_u, loc) # index of spar's x_u + spar_x_l = bi.bisect_left(x_l, loc) # index of spar's x_l + # These x and y coordinates are assigned to the spar, NOT airfoil. + self.x_u.append(x_u[spar_x_u]) + self.z_u.append(z_u[spar_x_u]) + self.x_l.append(x_l[spar_x_l]) + self.z_l.append(z_l[spar_x_l]) + + super().pack_info() + return None + + def add_mass(self, mass): + self.mass = len(self.x_u) * mass + + +class Stringer(Coordinates): + """Contains the coordinates of all stringers.""" + global parent + + def __init__(self): + super().__init__(parent.chord, parent.semi_span) + self.area = float() + + def add_coord(self, airfoil_coord, spar_coord, + stringer_u_1, stringer_u_2, stringer_l_1, stringer_l_2): + """ + Add equally distributed stringers to four airfoil locations + (upper nose, lower nose, upper surface, lower surface). + + Parameters: + stringer_u_1: upper nose number of stringers + stringer_u_2: upper surface number of stringers + stringer_l_1: lower nose number of stringers + stringer_l_2: lower surface number of stringers + + Returns: + None + """ + + # Airfoil surface coordinates + # unpacked from 'coordinates' (list of lists in 'Coordinates'). + airfoil_x_u = airfoil_coord[0] + airfoil_z_u = airfoil_coord[1] + airfoil_x_l = airfoil_coord[2] + airfoil_z_l = airfoil_coord[3] + # Spar coordinates + # unpacked from 'coordinates' (list of lists in 'Coordinates'). + try: + spar_x_u = spar_coord[0] + spar_z_u = spar_coord[1] + spar_x_l = spar_coord[2] + spar_z_l = spar_coord[3] + except: + print('Unable to initialize stringers. Were spars created?') + + # Find distance between leading edge and first upper stringer + interval = spar_x_u[0] / (stringer_u_1 + 1) + # initialise first self.stringer_x_u at first interval + x = interval + # Add upper stringers from leading edge until first spar. + for _ in range(0, stringer_u_1): + # Index of the first value of airfoil_x_u > x + index = bi.bisect_left(airfoil_x_u, x) + self.x_u.append(airfoil_x_u[index]) + self.z_u.append(airfoil_z_u[index]) + x += interval + # Add upper stringers from first spar until last spar + interval = (spar_x_u[-1] - spar_x_u[0]) / (stringer_u_2 + 1) + x = interval + spar_x_u[0] + for _ in range(0, stringer_u_2): + index = bi.bisect_left(airfoil_x_u, x) + self.x_u.append(airfoil_x_u[index]) + self.z_u.append(airfoil_z_u[index]) + x += interval + + # Find distance between leading edge and first lower stringer + interval = spar_x_l[0] / (stringer_l_1 + 1) + x = interval + # Add lower stringers from leading edge until first spar. + for _ in range(0, stringer_l_1): + index = bi.bisect_left(airfoil_x_l, x) + self.x_l.append(airfoil_x_l[index]) + self.z_l.append(airfoil_z_l[index]) + x += interval + # Add lower stringers from first spar until last spar + interval = (spar_x_l[-1] - spar_x_l[0]) / (stringer_l_2 + 1) + x = interval + spar_x_l[0] + for _ in range(0, stringer_l_2): + index = bi.bisect_left(airfoil_x_l, x) + self.x_l.append(airfoil_x_l[index]) + self.z_l.append(airfoil_z_l[index]) + x += interval + super().pack_info() + return None + + def add_area(self, area): + self.area = area + return None + + def add_mass(self, mass): + self.mass = len(self.x_u) * mass + len(self.x_l) * mass + return None + + def print_info(self, round): + super().print_info(round) + print('Stringer Area:\n', np.around(self.area, round)) + return None + + +def plot(airfoil, spar, stringer): + """This function plots the elements passed as arguments.""" + + print('Plotting airfoil.') + # Plot chord + x_chord = [0, airfoil.chord] + y_chord = [0, 0] + plt.plot(x_chord, y_chord, linewidth='1') + # Plot mean camber line + plt.plot(airfoil.x_c, + airfoil.y_c, + '-.', + color='r', + linewidth='2') + # label='mean camber line') + # Plot upper surface + plt.plot(airfoil.x_u, airfoil.z_u, '', color='b', linewidth='1') + # Plot lower surface + plt.plot(airfoil.x_l, airfoil.z_l, '', color='b', linewidth='1') + + # Plot spars + try: + for _ in range(0, len(spar.x_u)): + x = (spar.x_u[_], spar.x_l[_]) + y = (spar.z_u[_], spar.z_l[_]) + plt.plot(x, y, '.-', color='b') + # plt.legend() + except: + print('Did not plot spars. Were they added?') + + # Plot stringers + try: + # Upper stringers + for _ in range(0, len(stringer.x_u)): + x = stringer.x_u[_] + y = stringer.z_u[_] + plt.plot(x, y, '.', color='y') + # Lower stringers + for _ in range(0, len(stringer.x_l)): + x = stringer.x_l[_] + y = stringer.z_l[_] + plt.plot(x, y, '.', color='y') + except: + print('Unable to plot stringers. Were they created?') + + # Graph formatting + plt.gca().set_aspect('equal', adjustable='box') + plt.xlabel('X axis') + plt.ylabel('Z axis') + plt.grid(axis='both', linestyle=':', linewidth=1) + plt.show() + return None + + +def main(): + return None + + +if __name__ == '__main__': + main() diff --git a/evaluator.py b/evaluator.py index 6a87b7e..69a589a 100644 --- a/evaluator.py +++ b/evaluator.py @@ -13,4 +13,22 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. -import creator +# F_z = + + +def get_centroid(airfoil): + area = airfoil.stringer.area + numerator = float() + for _ in airfoil.stringer.x_u: + numerator += _ * area + for _ in airfoil.stringer.x_l: + numerator += _ * area + denominator + # z_c = + + +def get_total_mass(self, *component): + total_mass = float() + for _ in component: + total_mass += _.mass + return total_mass @@ -15,7 +15,7 @@ import creator # Create geometry import evaluator # Evaluate geometry -import generator # Iteratevely evaluate instances of geometry +import generator # Iteratevely evaluate instances of geometry and optimize import random import time @@ -24,43 +24,69 @@ start_time = time.time() CHORD_LENGTH = 100 SEMI_SPAN = 200 +# m=Mass +AIRFOIL_MASS = 100 # lbs +SPAR_MASS = 10 # lbs +STRINGER_MASS = 5 # lbs + +# Area +STRINGER_AREA = 0.1 # sqin + +# population information POP_SIZE = 1 SAVE_PATH = 'C:/Users/blend/github/UCLA_MAE_154B/save' def main(): + ''' + Create an airfoil; + Evaluate an airfoil; + Generate a population of airfoils & optimize. + ''' + # Create coordinate system specific to our airfoil dimensions. + # TODO: imperial + metric unit setting creator.Coordinates(CHORD_LENGTH, SEMI_SPAN) # Interate through all wings in population. for _ in range(1, POP_SIZE + 1): + # Create airfoil instance af = creator.Airfoil() - # Define NACA airfoil coordinates + # Define NACA airfoil coordinates and mass af.add_naca(2412) - af.print_coord(2) + af.add_mass(AIRFOIL_MASS) + af.print_info(2) # Create spar instance af.spar = creator.Spar() - # Define the spar coordinates, stored in single spar object - af.spar.add(af.coord, 0.15) - af.spar.add(af.coord, 0.55) - af.spar.print_coord(2) + # Define the spar coordinates and mass, stored in single spar object + af.spar.add_coord(af.coord, 0.15) + af.spar.add_coord(af.coord, 0.55) + af.spar.add_mass(SPAR_MASS) + af.spar.print_info(2) # Create stringer instance af.stringer = creator.Stringer() - # Define the stringer coordinates from their amount - af.stringer.add(af.coord, af.spar.coord, 4, 7, 5, 6) - # Print coordinates of af.stringer to terminal - af.stringer.print_coord(2) + # Compute the stringer coordinates from their quantity in each zone + af.stringer.add_coord(af.coord, af.spar.coord, 4, 7, 5, 6) + af.stringer.add_area(STRINGER_AREA) + af.stringer.add_mass(STRINGER_MASS) + af.stringer.print_info(2) # Plot components with matplotlib creator.plot(af, af.spar, af.stringer) - # Save component coordinates - af.save_coord(SAVE_PATH, _) - af.spar.save_coord(SAVE_PATH, _) - af.stringer.save_coord(SAVE_PATH, _) + # Save component info + af.save_info(SAVE_PATH, _) + af.spar.save_info(SAVE_PATH, _) + af.stringer.save_info(SAVE_PATH, _) + + # Evaluate previously created airfoil(s). + total_mass = evaluator.get_total_mass(af, af.spar, af.stringer) + + # Iteratively evaluate airfoils by defining genetic generations. + # pass # Print final execution time print("--- %s seconds ---" % (time.time() - start_time)) |