diff options
Diffstat (limited to 'evaluator.py')
| -rw-r--r-- | evaluator.py | 360 | 
1 files changed, 360 insertions, 0 deletions
| diff --git a/evaluator.py b/evaluator.py new file mode 100644 index 0000000..0307079 --- /dev/null +++ b/evaluator.py @@ -0,0 +1,360 @@ +""" +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 numpy as np +from math import sqrt +import matplotlib.pyplot as plt +import concurrent.futures +import logging + +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 _get_lift_rectangular(aircraft, lift=50): +        L_prime = [ +            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 = [ +            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) / 2 +            for i in range(aircraft.wing.semi_span) +        ] +        return F_z + +    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_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_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) + +    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 + +    def analysis(self): +        """Perform all analysis calculations and store in self.results.""" +        # with concurrent.futures.ProcessPoolExecutor() as executor: +        #     for aircraft in self.aircrafts: +        #         lift = executor.submit(self.get_lift_total(aircraft)) +        #         drag = executor.submit(self.get_drag_total(aircraft)) +        #         mass = executor.submit(self.get_mass_total(aircraft)) +        #         thrust = executor.submit(self.get_thrust_total(aircraft)) + +        #     for aircraft in self.aircrafts: +        #         print(lift.result()) +        #         print(drag.result()) +        #         print(mass.result()) +        #         print(thrust.result()) + +        #     for f in concurrent.futures.as_completed(l, d, m, t): +        #         print(f.result()) + +        for aircraft in self.aircrafts: +            # lift = self.get_lift_total(aircraft), +            # drag = self.get_drag(aircraft.wing), +            # centroid = self.get_centroid(aircraft.wing) +            results = {"Lift": 400, "Drag": 20, "Centroid": [0.2, 4.5]} +            self.results.append(results) +        #     results = { +        #         "Lift": self.get_lift_total(aircraft), +        #         "Drag": self.get_drag(aircraft), +        #         "Centroid": self.get_centroid(aircraft) +        #     } +        return results + +    # 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 info_save(self, save_path, number): +        """Save all the object's coordinates (must be full path).""" +        file_name = 'airfoil_{}_eval.txt'.format(number) +        full_path = os.path.join(save_path, file_name) +        try: +            with open(full_path, 'w') as sys.stdout: +                self.info_print(6) +                # This line required to reset behavior of sys.stdout +                sys.stdout = sys.__stdout__ +                print('Successfully wrote to file {}'.format(full_path)) +        except IOError: +            print( +                'Unable to write {} to specified directory.\n'.format( +                    file_name), '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 | 
