diff options
Diffstat (limited to 'evaluator')
-rw-r--r-- | evaluator/__init__.py | 1 | ||||
-rw-r--r-- | evaluator/dP.py | 18 | ||||
-rw-r--r-- | evaluator/drag.py | 16 | ||||
-rw-r--r-- | evaluator/evaluator.py | 206 | ||||
-rw-r--r-- | evaluator/inertia.py | 64 | ||||
-rw-r--r-- | evaluator/lift.py | 30 | ||||
-rw-r--r-- | evaluator/mass.py | 8 |
7 files changed, 343 insertions, 0 deletions
diff --git a/evaluator/__init__.py b/evaluator/__init__.py new file mode 100644 index 0000000..6eedafc --- /dev/null +++ b/evaluator/__init__.py @@ -0,0 +1 @@ +from .evaluator import Evaluator diff --git a/evaluator/dP.py b/evaluator/dP.py new file mode 100644 index 0000000..b6aaa3b --- /dev/null +++ b/evaluator/dP.py @@ -0,0 +1,18 @@ +def get_dx(self, component): + return [x - self.centroid[0] for x in component.x_start] + + +def get_dz(self, component): + return [x - self.centroid[1] for x in component.x_start] + + +def get_dP(self, xDist, zDist, V_x, V_z, area): + I_x = self.I_['x'] + I_z = self.I_['z'] + I_xz = self.I_['xz'] + denom = float(I_x * I_z - I_xz**2) + z = float() + for _ in range(len(xDist)): + z += float(-area * xDist[_] * (I_x * V_x - I_xz * V_z) / denom - + area * zDist[_] * (I_z * V_z - I_xz * V_x) / denom) + return z diff --git a/evaluator/drag.py b/evaluator/drag.py new file mode 100644 index 0000000..df79e6a --- /dev/null +++ b/evaluator/drag.py @@ -0,0 +1,16 @@ +def get_drag(aircraft, drag): + # Transform semi-span integer into list + semi_span = [x for x in range(0, aircraft.wing.semi_span)] + + # Drag increases after 80% of the semi_span + cutoff = round(0.8 * aircraft.wing.span) + + # Drag increases by 25% after 80% of the semi_span + F_x = [drag for x in semi_span[0:cutoff]] + F_x.extend([1.25 * drag for x in semi_span[cutoff:]]) + return F_x + + +def get_drag_total(self, aircraft): + """Get total drag force acting on the aircraft.""" + return 500 diff --git a/evaluator/evaluator.py b/evaluator/evaluator.py new file mode 100644 index 0000000..b2b6e18 --- /dev/null +++ b/evaluator/evaluator.py @@ -0,0 +1,206 @@ +""" +The evaluator.py module contains a single Evaluator class, +which knows all the attributes of a specified Aircraft instance, +and contains functions to analyse the airfoil's geometrical +& structural properties. +""" + +import sys +import os.path +import matplotlib.pyplot as plt +import concurrent.futures +import logging + +from . import drag, inertia, lift, mass +import generator + +logging.basicConfig(filename='log_eval.txt', + level=logging.DEBUG, + format='%(asctime)s - %(levelname)s - %(message)s') + + +class Evaluator: + """Performs structural evaluations on aircrafts. + Individual aircrafts must claim an Evaluator object as parent.""" + def __init__(self, name): + self.name = name + self.aircrafts = [] + self.results = [] + + self.I_ = {'x': 0, 'z': 0, 'xz': 0} + + def analyze(self, aircraft): + """Analyze a single aircraft.""" + aircraft.results.update({'Lift': lift.get_lift_total(aircraft)}) + aircraft.results.update({'Drag': drag.get_drag_total(aircraft)}) + aircraft.results.update({'Mass': mass.get_mass_total(aircraft)}) + aircraft.results.update({'Centroid': inertia.get_centroid(aircraft)}) + return aircraft.results + + def analyze_all(self): + """Perform all analysis calculations on a all aircraft in evaluator.""" + with concurrent.futures.ProcessPoolExecutor() as executor: + executor.map(self.analyze, self.aircrafts) + + return None + + # def analysis(self, V_x, V_z): + # """Perform all analysis calculations and store in class instance.""" + + # self.drag = self.get_drag(10) + # self.lift_rectangular = self.get_lift_rectangular(13.7) + # self.lift_elliptical = self.get_lift_elliptical(15) + # self.lift_total = self.get_lift_total() + # self.mass_dist = self.get_mass_distribution(self.mass_total) + # self.centroid = self.get_centroid() + # self.I_['x'] = self.get_inertia_terms()[0] + # self.I_['z'] = self.get_inertia_terms()[1] + # self.I_['xz'] = self.get_inertia_terms()[2] + # spar_dx = self.get_dx(self.spar) + # spar_dz = self.get_dz(self.spar) + # self.spar.dP_x = self.get_dP(spar_dx, spar_dz, V_x, 0, + # self.spar.cap_area) + # self.spar.dP_z = self.get_dP(spar_dx, spar_dz, 0, V_z, + # self.spar.cap_area) + # print("yayyyyy") + # return None + + # print(f"Analysis results for {aircraft.name}:\n", results) + # self.results = self.get_lift_total(aircraft) + + # self.drag = self.get_drag(10) + # self.lift_rectangular = self.get_lift_rectangular(13.7) + # self.lift_elliptical = self.get_lift_elliptical(15) + # self.lift_total = self.get_lift_total() + # self.mass_dist = self.get_mass_distribution(self.mass_total) + # self.centroid = self.get_centroid() + # self.I_['x'] = self.get_inertia_terms()[0] + # self.I_['z'] = self.get_inertia_terms()[1] + # self.I_['xz'] = self.get_inertia_terms()[2] + # spar_dx = self.get_dx(self.spar) + # spar_dz = self.get_dz(self.spar) + # self.spar.dP_x = self.get_dP(spar_dx, spar_dz, V_x, 0, + # self.spar.cap_area) + # self.spar.dP_z = self.get_dP(spar_dx, spar_dz, 0, V_z, + # self.spar.cap_area) + # return None + + def tree_print(self, *aircrafts): + """Print the list of subcomponents.""" + name = f" TREE FOR {[i.name for i in aircrafts]} IN {self.name} " + num_of_dashes = len(name) + print(num_of_dashes * '-') + print(name) + for aircraft in aircrafts: + print(".") + print(f"`-- {aircraft}") + print(f" |--{aircraft.wing}") + print(f" | |-- {aircraft.wing.stringers}") + for spar in aircraft.wing.spars[:-1]: + print(f" | |-- {spar}") + print(f" | `-- {aircraft.wing.spars[-1]}") + print(f" |-- {aircraft.fuselage}") + print(f" `-- {aircraft.propulsion}") + print(num_of_dashes * '-') + return None + + def tree_save(self, + *aircrafts, + save_path='/home/blendux/Projects/Aircraft_Studio/save'): + """Save the evaluator's tree to a file.""" + for aircraft in aircrafts: + file_name = f"{aircraft.name}_tree.txt" + full_path = os.path.join(save_path, file_name) + with open(full_path, 'w') as f: + try: + f.write(".\n") + f.write(f"`-- {aircraft}\n") + f.write(f" |--{aircraft.wing}\n") + for spar in aircraft.wing.spars[:-1]: + f.write(f" | |-- {spar}\n") + f.write(f" | `-- {aircraft.wing.spars[-1]}\n") + f.write(f" |-- {aircraft.fuselage}\n") + f.write(f" `-- {aircraft.propulsion}\n") + logging.debug(f'Successfully wrote to file {full_path}') + + except IOError: + print( + f'Unable to write {file_name} to specified directory.', + 'Was the full path passed to the function?') + return None + + +def plot_geom(evaluator): + """This function plots analysis results over the airfoil's geometry.""" + # Plot chord + x_chord = [0, evaluator.chord] + y_chord = [0, 0] + plt.plot(x_chord, y_chord, linewidth='1') + # Plot quarter chord + plt.plot(evaluator.chord / 4, + 0, + '.', + color='g', + markersize=24, + label='Quarter-chord') + # Plot airfoil surfaces + x = [0.98 * x for x in evaluator.airfoil.x] + y = [0.98 * z for z in evaluator.airfoil.z] + plt.fill(x, y, color='w', linewidth='1', fill=False) + x = [1.02 * x for x in evaluator.airfoil.x] + y = [1.02 * z for z in evaluator.airfoil.z] + plt.fill(x, y, color='b', linewidth='1', fill=False) + + # Plot spars + try: + for _ in range(len(evaluator.spar.x)): + x = (evaluator.spar.x[_]) + y = (evaluator.spar.z[_]) + plt.plot(x, y, '-', color='b') + except AttributeError: + print('No spars to plot.') + # Plot stringers + try: + for _ in range(0, len(evaluator.stringer.x)): + x = evaluator.stringer.x[_] + y = evaluator.stringer.z[_] + plt.plot(x, y, '.', color='y', markersize=12) + except AttributeError: + print('No stringers to plot.') + + # Plot centroid + x = evaluator.centroid[0] + y = evaluator.centroid[1] + plt.plot(x, y, '.', color='r', markersize=24, label='centroid') + + # Graph formatting + plt.xlabel('X axis') + plt.ylabel('Z axis') + + plot_bound = max(evaluator.airfoil.x) + plt.xlim(-0.10 * plot_bound, 1.10 * plot_bound) + plt.ylim(-(1.10 * plot_bound / 2), (1.10 * plot_bound / 2)) + plt.gca().set_aspect('equal', adjustable='box') + plt.gca().legend() + plt.grid(axis='both', linestyle=':', linewidth=1) + plt.show() + return None + + +def plot_lift(evaluator): + x = range(evaluator.semi_span) + y_1 = evaluator.lift_rectangular + y_2 = evaluator.lift_elliptical + y_3 = evaluator.lift_total + plt.plot(x, y_1, '.', color='b', markersize=4, label='Rectangular lift') + plt.plot(x, y_2, '.', color='g', markersize=4, label='Elliptical lift') + plt.plot(x, y_3, '.', color='r', markersize=4, label='Total lift') + + # Graph formatting + plt.xlabel('Semi-span location') + plt.ylabel('Lift') + + plt.gca().legend() + plt.grid(axis='both', linestyle=':', linewidth=1) + plt.show() + return None diff --git a/evaluator/inertia.py b/evaluator/inertia.py new file mode 100644 index 0000000..fea728c --- /dev/null +++ b/evaluator/inertia.py @@ -0,0 +1,64 @@ +def get_centroid(aircraft): + """Return the coordinates of the centroid.""" + stringer_area = aircraft.stringer.area + cap_area = aircraft.spar.cap_area + + caps_x = [value for spar in aircraft.spar.x for value in spar] + caps_z = [value for spar in aircraft.spar.z for value in spar] + stringers_x = aircraft.stringer.x + stringers_z = aircraft.stringer.z + + denominator = float( + len(caps_x) * cap_area + len(stringers_x) * stringer_area) + + centroid_x = float( + sum([x * cap_area + for x in caps_x]) + sum([x * stringer_area for x in stringers_x])) + centroid_x = centroid_x / denominator + + centroid_z = float( + sum([z * cap_area + for z in caps_z]) + sum([z * stringer_area for z in stringers_z])) + centroid_z = centroid_z / denominator + + return (centroid_x, centroid_z) + + +def get_inertia_terms(self): + """Obtain all inertia terms.""" + stringer_area = self.stringer.area + cap_area = self.spar.cap_area + + # Adds upper and lower components' coordinates to list + x_stringers = self.stringer.x + z_stringers = self.stringer.z + x_spars = self.spar.x[:][0] + self.spar.x[:][1] + z_spars = self.spar.z[:][0] + self.spar.z[:][1] + stringer_count = range(len(x_stringers)) + spar_count = range(len(self.spar.x)) + + # I_x is the sum of the contributions of the spar caps and stringers + # TODO: replace list indices with dictionary value + I_x = sum( + [cap_area * (z_spars[i] - self.centroid[1])**2 for i in spar_count]) + I_x += sum([ + stringer_area * (z_stringers[i] - self.centroid[1])**2 + for i in stringer_count + ]) + + I_z = sum( + [cap_area * (x_spars[i] - self.centroid[0])**2 for i in spar_count]) + I_z += sum([ + stringer_area * (x_stringers[i] - self.centroid[0])**2 + for i in stringer_count + ]) + + I_xz = sum([ + cap_area * (x_spars[i] - self.centroid[0]) * + (z_spars[i] - self.centroid[1]) for i in spar_count + ]) + I_xz += sum([ + stringer_area * (x_stringers[i] - self.centroid[0]) * + (z_stringers[i] - self.centroid[1]) for i in stringer_count + ]) + return (I_x, I_z, I_xz) diff --git a/evaluator/lift.py b/evaluator/lift.py new file mode 100644 index 0000000..6b4363e --- /dev/null +++ b/evaluator/lift.py @@ -0,0 +1,30 @@ +import numpy as np +from math import sqrt + + +def _get_lift_rectangular(aircraft, lift=50): + L_prime = np.array([ + lift / (aircraft.wing.semi_span * 2) + for _ in range(aircraft.wing.semi_span) + ]) + return L_prime + + +def _get_lift_elliptical(aircraft, L_0=3.2): + L_prime = np.array([ + 0.5 * L_0 / (aircraft.wing.semi_span * 2) * + sqrt(1 - (y / aircraft.wing.semi_span)**2) + for y in range(aircraft.wing.semi_span) + ]) + return L_prime + + +def get_lift_total(self, aircraft): + """Combination of rectangular and elliptical lift.""" + # F_z = self._get_lift_rectangular(aircraft) + self._get_lift_elliptical( + # aircraft) + # F_z = self._get_lift_rectangular( + # aircraft) + self._get_lift_elliptical(aircraft) / 2 + # F_z = [i + j for i, j in self._get_lift_rectangular] + # return F_z + return 420 diff --git a/evaluator/mass.py b/evaluator/mass.py new file mode 100644 index 0000000..514c59b --- /dev/null +++ b/evaluator/mass.py @@ -0,0 +1,8 @@ +def get_mass_distribution(self, total_mass): + F_z = [total_mass / self.semi_span for x in range(0, self.semi_span)] + return F_z + + +def get_mass_total(self, aircraft): + """Get the total aircraft mass.""" + return 2000 |