コード例 #1
0
ファイル: electronics.py プロジェクト: FHHartog/KBE
class Electronics(Component):
    """  This class will instantiate the flight controller and speed_controller(s) and place them on top of one another.

    :return: ParaPy Geometry of the electronics.

    :param motor_in: This provides a link to the motor object instantiated in :class:`main.UAV`
    :type motor_in: Motor

    :param label: This names the Electronics to the specified string.
    :type label: str

    """

    __icon__ = os.path.join(DIRS['ICON_DIR'], 'electronics.png')

    #  If multiple motors put in, give [] within input parentheses
    motor_in = Input(Motor(), validator=val.Instance(Motor))

    label = Input('Electronics', validator=val.Instance(str))

    @Attribute
    def component_type(self):
        """ This attribute names the component 'electronics' for electronics.

        :return: String with electronics component name
        :rtype: str
        """
        return 'electronics'

    @Attribute
    def weight(self):
        """ Total mass of the electronics components

        :return: Mass in SI kilograms
        :rtype: float
        """
        return self.flight_controller.weight + self.speed_controller.weight

    @Attribute
    def amp_req(self):
        """ This is the required amperage for the engine(s).

        :return: Complete Amp draw from the engine(s) in SI Amps
        :rtype: float
        """
        amp_req = 0
        if self.number_engines == 1 and not isinstance(self.motor_in, Iterable):
            amp_req = self.motor_in.specs['esc_recommendation']
        else:
            for i in self.motor_in:
                amp_req = amp_req + i.specs['esc_recommendation']
        return amp_req

    @Attribute
    def number_engines(self):
        """ This is used to determine the number of engines from the input motor_in.

        :return: Number of Engines
        :rtype: int
        """
        length = 1
        if isinstance(self.motor_in, Iterable):
            length = len(self.motor_in)
        return length

    @Part
    def flight_controller(self):
        """ This an instantiation of the flight controller. It takes only the position input.

        :return: Flight controller geometry
        :rtype: TranslatedShape
        """
        return FlightController(position=self.position)

    @Part
    def speed_controller(self):
        """ This an instantiation of the speed controller class. It requires the amp draw and the number of engines and
        position as input.

        :return: Speed Controller(s) Geometry
        :rtype: Box
        """
        return SpeedController(position=self.position,
                               amp_recc=self.amp_req,
                               num_engines=self.number_engines)

    @Attribute
    def box_length(self):
        """ This returns the length of the flight controller box for calculations.

        :return: Flight Controller Length
        :rtype: float
        """
        return self.flight_controller.l_navio

    @Attribute
    def elec_joiner(self):
        """ This joins the ESC's together through a series of Fuse operations to be able to present a
        single `internal_shape` required for the fuselage frame sizing.

        :return: ParaPy Fused Boxes
        :rtype: Fused
        """
        return Fused(shape_in=self.speed_controller.esc_joiner, tool=self.flight_controller.solid).solids[0]

    @Attribute
    def center_of_gravity(self):
        """ This attribute finds the center of gravities of the separate ESCs, then finds the combined C.G.

          :return: ParaPy Point
          :rtype: Point
          """
        cogs = [self.speed_controller.center_of_gravity, self.flight_controller.center_of_gravity]
        weights = [self.speed_controller.weight, self.flight_controller.weight]

        # CG calculation through a weighted average utilizing list comprehension
        cg_x = sum([weights[i] * cogs[i].x for i in range(0, len(weights))]) / self.weight
        cg_y = sum([weights[i] * cogs[i].y for i in range(0, len(weights))]) / self.weight
        cg_z = sum([weights[i] * cogs[i].z for i in range(0, len(weights))]) / self.weight

        return Point(cg_x, cg_y, cg_z)

    @Part
    def internal_shape(self):
        """ This is creating a box for the fuselage frames. This is used to get around ParaPy errors.

        :return: Speed Controller(s) bounded box
        :rtype: ScaledShape
        """
        return ScaledShape(shape_in=self.elec_joiner, reference_point=self.speed_controller.center_of_gravity,
                           factor=1, transparency=0.7)
コード例 #2
0
class Performance(Base):
    """ This class calculates the end performance of the UAV created in the KBE GUI. This verifies that the UAV will
    fulfill the mission requirements.

    :param motor_in: The instantated motor (typically passed from myUAV)
    :type motor_in: Motor

    :param propeller: The instantated propeller (typically passed from myUAV)
    :type propeller: Propeller

    :param wing_in: The instantated wing passed (typically passed from myUAV)
    :type wing_in: Wing

    :param weight_mtow: Final Maximum Take-Off Weight in SI kilogram [kg]
    :type weight_mtow: float

    :param parasitic_drag: Computed Parasitic Drag Coefficient
    :type parasitic_drag: float

    :param oswald_factor: Oswald Span Efficiency Factor
    :type parasitic_drag: float

    :param cg_valid: Switch case used to judge if the performance prediction is valid
    :type cg_valid: bool

    :param stall_buffer: Safety Factor to create a buffer between Endurance/Cruise velocity and the Stall Speed
    :type stall_buffer: float
    """

    __initargs__ = ["parasitic_drag"]
    __icon__ = os.path.join(DIRS['ICON_DIR'], 'performance.png')

    #: Instantiated Motor Object
    motor_in = Input(Motor(), validator=val.Instance(Motor))

    #: Instantiated Propeller Object
    propeller_in = Input(Propeller(), validator=val.Instance(Propeller))

    #: Instantiated Battery Object
    battery_in = Input(Battery(), validator=val.Instance(Battery))

    #: Instantiated Wing Object
    wing_in = Input(Wing(), validator=val.Instance(Wing))

    #: The Maximum Take-Off Weight from bottoms-up Class II in SI kilogram [kg]
    weight_mtow = Input(5.0, validator=val.Instance(float))

    #: The Parasitic Drag Coefficient (C_D_0)
    parasitic_drag = Input(0.02, validator=val.Instance(float))

    #: Oswald Span Efficiency Factor
    oswald_factor = Input(0.85, validator=val.Instance(float))

    #: Switch Case to determine if the C.G. has converged prior to running the Performance Estimation
    cg_valid = Input(False)

    #: Safety Factor to create a buffer between flight speed and stall speed
    stall_buffer = Input(1.5, validator=val.Range(1.0, 1.5))

    @Attribute
    def stall_speed(self):
        """ Computes the new stall speed caused by change in MTOW in Class II (bottoms-up) as compared to the initial \
        estimate from Class I

        :return: Stall Speed in SI meter per second [m/s]
        :rtype: float
        """
        return sqrt((2 * 9.81 * self.weight_mtow) /
                    (self.wing_in.rho * self.wing_in.lift_coef_max *
                     self.wing_in.planform_area))

    @Attribute
    def power_available(self):
        """ Grabs the available shaft power of the engine from the provided motor. Index 0 refers to continuous power \
        and Index 1 refers to the burst power of the engine

        :rtype: list
        """
        return self.motor_in.power[0], self.motor_in.power[1]

    @Attribute
    def propeller_eta_curve(self):
        """ Fetches the propeller efficiency curve as a function of airspeed from the provided propeller

        :rtype: interpld
        """
        return self.propeller_in.propeller_selector[2]

    @Attribute
    def eta_curve_bounds(self):
        """ Defines the bounds of the propeller curve to not address values that are not covered by the propeller data \
        index 0 refers to the minimum velocity and index 1 refers to the maximum velocity

        :rtype: list
        """
        return self.propeller_in.propeller_selector[3]

    @Attribute
    def prop_speed_range(self):
        """ Defines the True Airspeed (TAS) range used for the analysis through use of the minimum and maximum values \
        available in propeller data so as to not force data extrapolation.

        :return: Range of True Airspeeds (TAS) in SI meter per second [m/s]
        :rtype: numpy array
        """
        return np.linspace(self.eta_curve_bounds[0], self.eta_curve_bounds[1],
                           100)

    @Attribute
    def speed_range(self):
        """ An auxillary range to the :attr:`prop_speed_range` to be able to better display the induced drag curve

        :return: Range of True Airspeeds (TAS) in SI meter per second [m/s]
        :rtype: numpy array
        """
        if not self.cg_valid:
            warn_window(
                'Please run the c.g. convergence attribute in the root, before estimating Performance'
            )
        return np.linspace(0.1, self.eta_curve_bounds[1], 100)

    @Attribute
    def dynamic_pressures(self):
        """ Creates a range of dynamic pressurs corresponding to the range of flight speeds from :attr:`speed_range`

        :return: Range of Dynamic Pressures in SI Pascal [Pa]
        :rtype: numpy array
        """
        return 0.5 * self.wing_in.rho * (self.speed_range**2)

    @Attribute
    def lift_coefficients(self):
        return (self.weight_mtow * 9.81) / (self.dynamic_pressures *
                                            self.wing_in.planform_area)

    @Attribute
    def drag_coefficients(self):
        return self.parasitic_drag + (
            self.lift_coefficients /
            (pi * self.wing_in.aspect_ratio * self.oswald_factor))

    @Attribute
    def parasite_power(self):
        return self.parasitic_drag \
               * self.dynamic_pressures * self.wing_in.planform_area * self.speed_range

    @Attribute
    def induced_power(self):
        return ((self.lift_coefficients ** 2) / (pi * self.wing_in.aspect_ratio * self.oswald_factor)) \
               * (self.dynamic_pressures * self.wing_in.planform_area * self.speed_range)

    @Attribute
    def power_required(self):
        return self.parasite_power + self.induced_power

    @Attribute
    def endurance_velocity(self):
        """ Computes the optimum endurance velocity utilizing the maximum positive difference between the power \
        available and required curves. If this value is too close to the stall speed (which is determined by the input \
        'stall_buffer', then the non-optimum but safe value is returned instead.

        :return: Optimum endurance velocity in SI meter per second [m/s]
        :rtype: float
        """
        diff = [
            self.power_available_cont[i] - self.power_required[i]
            for i in range(0, len(self.speed_range))
        ]
        idx_e = diff.index(max(diff))
        safe_speed = self.stall_buffer * self.stall_speed
        calc_speed = self.speed_range[idx_e]
        if calc_speed >= safe_speed:
            safe = True
        else:
            safe = False
            print 'Computed optimum endurance velocity of %1.2f [m/s] is too close to the stall speed! Instead a value'\
                  ' safety factor has been added to the stall speed and returned' % calc_speed
        return calc_speed if safe else safe_speed

    @Attribute
    def cruise_velocity(self):
        """ Computes the optimum cruise velocity utilizing the maximum tangent between velocity axis and the power \
        required curves. If this value is too close to the stall speed (which is determined by the input \
        'stall_buffer', then the non-optimum but safe value is returned instead.

        :return: Optimum cruise velocity in SI meter per second [m/s]
        :rtype: float
        """
        diff = []
        for i in range(0, len(self.speed_range) - 1):
            tangent = self.power_required[i + 1] / self.speed_range[i + 1]
            local_tangent = (self.power_required[i + 1] - self.power_required[i]) / \
                            (self.speed_range[i + 1] - self.speed_range[i])
            diff = diff + [(abs(tangent - local_tangent))]
        idx_c = diff.index(min(diff))
        safe_speed = self.stall_buffer * self.stall_speed
        calc_speed = self.speed_range[idx_c]
        if calc_speed >= safe_speed:
            safe = True
        else:
            safe = False
            print 'Computed optimum cruise velocity of %1.2f [m/s] is too close to the stall speed! Instead a value' \
                  ' safety factor has been added to the stall speed and returned' % calc_speed
        return calc_speed if safe else safe_speed

    @Attribute
    def maximum_velocity(self):
        """ Computes the maximum flight velocity utilizing the burst-power curve of the motor.

        :return: Maximum velocity in SI meter per second [m/s]
        :rtype: float
        """

        diff = [
            abs(self.power_available_burst[i] - self.power_required[i])
            for i in range(0, len(self.speed_range))
        ]
        idx_m = diff.index(min(diff))

        return self.speed_range[
            idx_m] if idx_m is not -1 else self.speed_range[-1]

    @Attribute
    def power_spline(self):
        """ Creates a linear-spline of the power required curve to be able to call any velocity value.

        :rtype: interp1d
        """
        return interp1d(self.speed_range,
                        self.power_required,
                        fill_value='extrapolate')

    @Attribute
    def endurance(self):
        velocity = self.endurance_velocity
        prop_eta = self.propeller_eta_curve(velocity)
        hours = (self.battery_in.total_energy * self.motor_in.efficiency *
                 prop_eta) / (self.power_spline(velocity))
        return hours

    @Attribute
    def range(self):
        velocity = self.cruise_velocity
        prop_eta = self.propeller_eta_curve(velocity)
        hours = (self.battery_in.total_energy * self.motor_in.efficiency *
                 prop_eta) / (self.power_spline(velocity))
        range_km = 3.6 * hours * velocity
        return range_km

    @Attribute
    def eta_values(self):
        return [
            self.propeller_eta_curve(float(i)) for i in self.prop_speed_range
        ]

    @Attribute
    def power_available_cont(self):
        return [self.power_available[0] * i for i in self.eta_values]

    @Attribute
    def power_available_burst(self):
        return [self.power_available[1] * i for i in self.eta_values]

    @Attribute
    def plot_airspeed_vs_power(self):
        fig = plt.figure('PowerDiagram')
        plt.style.use('ggplot')
        plt.title(
            'Power Available and Required as a Function of True Airspeed')

        plt.plot(self.prop_speed_range,
                 self.power_available_cont,
                 label='Continuous Power')
        plt.plot(self.prop_speed_range,
                 self.power_available_burst,
                 label='Burst Power')
        plt.plot(self.speed_range, self.parasite_power, label='Parasitic')
        plt.plot(self.speed_range, self.induced_power, label='Induced')
        plt.plot(self.speed_range, self.power_required, label='Required')

        # Plotting Velocities
        plt.axvline(self.stall_speed,
                    color='red',
                    linestyle='-.',
                    label=r'$V_{\mathrm{stall}}=%1.2f$' % self.stall_speed)
        plt.axvline(self.endurance_velocity,
                    color=rgb(MyColors.deep_green),
                    linestyle='-.',
                    label=r'$V_{\mathrm{end}}=%1.2f$' %
                    self.endurance_velocity)
        if self.cruise_velocity != self.endurance_velocity:
            plt.axvline(self.cruise_velocity,
                        color='blue',
                        linestyle='-.',
                        label=r'$V_{\mathrm{cruise}}=%1.2f$' %
                        self.cruise_velocity)

        plt.axvline(self.maximum_velocity,
                    color=rgb(MyColors.deep_purple),
                    linestyle='-.',
                    label=r'$V_{\mathrm{max}}=%1.2f$' % self.maximum_velocity)

        ax = fig.gca()
        stall_zone = ptch.Rectangle((0, 0),
                                    self.stall_speed,
                                    self.motor_in.power[1],
                                    color='red',
                                    alpha=0.2)
        ax.add_patch(stall_zone)

        plt.xlabel(r'$V_{\mathrm{TAS}}$ [m/s]')
        plt.ylabel(r'Power [W]')
        plt.axis([0, self.speed_range[-1], 0, self.motor_in.power[1]])
        plt.legend(loc='best')
        plt.show()
        fig.savefig(fname=os.path.join(DIRS['USER_DIR'], 'plots',
                                       '%s.pdf' % fig.get_label()),
                    format='pdf')
        return 'Figure Plotted and Saved'

    @Attribute
    def plot_lift_to_drag(self):
        fig = plt.figure('LiftvsDrag')
        plt.style.use('ggplot')
        plt.title('Lift to Drag Ratio as a Function of True Airspeed')

        plt.plot(self.speed_range,
                 self.lift_coefficients / self.drag_coefficients)

        plt.xlabel(r'$V_{\mathrm{TAS}}$ [m/s]')
        plt.ylabel(r'Lift to Drag Ratio [-]')
        plt.show()
        fig.savefig(fname=os.path.join(DIRS['USER_DIR'], 'plots',
                                       '%s.pdf' % fig.get_label()),
                    format='pdf')
        return 'Figure Plotted and Saved'
コード例 #3
0
# additional parameters the solver needs
atmosphere = Atmosphere(0)
launchAngle = 84 * math.pi / 180.0  #rad
launchrailLength = 8.0  #m

# Initial conditions
# y_0 is 0.01 instead of 0 so that the hit_ground event works. look into doing it another way
# x, vx, y, vy
y0 = [0., 0., 0.1, 0.]

rocketFilePath = "2019_rocket_doe.csv"
myRocket = Rocket(rocketFilePath)

motorFilePath = "Cesaroni_N5800.eng"
myMotor = Motor(motorFilePath)

solver = TwoDTrajectorySolver(
    myRocket, myMotor, atmosphere, y0, launchAngle, launchrailLength,\
    dragModel=DragModel.CoefficientsBox
)

parameterKeys = ['finRootChord', 'finTipChord', 'finSpan', 'finSweepLength']
baselineParameters = {}
doe_coeff = np.loadtxt('analysis/doe/lhc_n4_100samples.csv')
apogeeList = []
stabilityList = []

for key in parameterKeys:
    baselineParameters[key] = myRocket.geometry[key]