Beispiel #1
0
    def __init__(self,
                 airplane,  # type: Airplane
                 op_point,  # type: OperatingPoint
                 ):
        ### Initialize
        self.airplane = airplane
        self.op_point = op_point

        ### Check assumptions
        assumptions = np.array([
            self.op_point.beta == 0,
            self.op_point.p == 0,
            self.op_point.q == 0,
            self.op_point.r == 0,
        ])
        if not assumptions.all():
            raise ValueError("The assumptions to use an aero buildup method are not met!")

        ### Fuselages
        for fuselage in self.airplane.fuselages:
            fuselage.Re = self.op_point.reynolds(fuselage.length())
            fuselage.CLA = 0
            fuselage.CDA = aero.Cf_flat_plate(
                fuselage.Re * fuselage.area_wetted()) * 1.2  # wetted area with form factor

            fuselage.lift_force = fuselage.CLA * self.op_point.dynamic_pressure()
            fuselage.drag_force = fuselage.CDA * self.op_point.dynamic_pressure()
            fuselage.pitching_moment = 0

        ### Wings
        for wing in self.airplane.wings:
            wing.alpha = op_point.alpha + wing.mean_twist_angle()  # TODO add in allmoving deflections
            wing.Re = self.op_point.reynolds(wing.mean_aerodynamic_chord())
            wing.airfoil = wing.xsecs[0].airfoil

            ## Lift calculation
            wing.Cl_incompressible = wing.airfoil.CL_function(
                alpha=wing.alpha,
                Re=wing.Re,  # TODO finish
                mach=0,  # TODO revisit this - is this right?
                deflection=0
            )
            CL_over_Cl = aero.CL_over_Cl(
                aspect_ratio=wing.aspect_ratio(),
                mach=op_point.mach(),
                sweep=wing.mean_sweep_angle()
            )
            wing.CL = wing.Cl_incompressible * CL_over_Cl

            ## Drag calculation
            wing.CD_profile = wing.airfoil.CD_function(
                alpha=wing.alpha,
                Re=wing.Re,
                mach=op_point.mach(),
                deflection=0
            )

            wing.oswalds_efficiency = aero.oswalds_efficiency(
                taper_ratio=wing.taper_ratio(),
                aspect_ratio=wing.aspect_ratio(),
                sweep=wing.mean_sweep_angle(),
            )
            wing.CD_induced = wing.CL ** 2 / (pi * wing.oswalds_efficiency * wing.aspect_ratio())

            ## Moment calculation
            wing.Cm_incompressible = wing.airfoil.CM_function(
                alpha=wing.alpha,
                Re=wing.Re,
                mach=0,  # TODO revisit this - is this right?
                deflection=0,
            )
            wing.CM = wing.Cm_incompressible * CL_over_Cl

            ## Force and moment calculation
            qS = op_point.dynamic_pressure() * wing.area()
            wing.lift_force = wing.CL * qS
            wing.drag_force_profile = wing.CD_profile * qS
            wing.drag_force_induced = wing.CD_induced * qS
            wing.drag_force = wing.drag_force_profile + wing.drag_force_induced
            wing.pitching_moment = wing.CM * qS * wing.mean_aerodynamic_chord()

        ### Total the forces
        self.lift_force = 0
        self.drag_force = 0
        self.pitching_moment = 0

        for fuselage in self.airplane.fuselages:
            self.lift_force += fuselage.lift_force
            self.drag_force += fuselage.drag_force
            self.pitching_moment += fuselage.pitching_moment

        for wing in self.airplane.wings:
            if wing.symmetric:  # Only add lift force if the wing is symmetric; a surrogate for "horizontal".
                self.lift_force += wing.lift_force
            self.drag_force += wing.drag_force
            self.pitching_moment += wing.pitching_moment # Raw pitching moment
            self.pitching_moment += -wing.aerodynamic_center()[0] * wing.lift_force # Pitching moment due to lift

        ### Calculate nondimensional forces
        qS = op_point.dynamic_pressure() * self.airplane.s_ref

        self.CL = self.lift_force / qS
        self.CD = self.drag_force / qS
        self.CM = self.pitching_moment / qS / self.airplane.c_ref
Beispiel #2
0
    def setup(
        self,
        verbose=True,  # Choose whether or not you want verbose output
        run_symmetric_if_possible=True,
        # Choose whether or not you want to run a symmetric_problem analysis about XZ (~4x faster)
    ):
        # Runs a point analysis at the specified op-point.

        ### Fuselages
        self.fuse_Res = [
            self.op_point.compute_reynolds(fuse.length())
            for i, fuse in enumerate(self.airplane.fuselages)
        ]
        self.CLA_fuses = [0 for i, fuse in enumerate(self.airplane.fuselages)]
        self.CDA_fuses = [
            aero.Cf_flat_plate(self.fuse_Res[i]) * fuse.area_wetted() *
            1.2  # wetted area with form factor
            for i, fuse in enumerate(self.airplane.fuselages)
        ]

        self.lift_fuses = [
            self.CLA_fuses[i] * self.op_point.dynamic_pressure()
            for i, fuse in enumerate(self.airplane.fuselages)
        ]
        self.drag_fuses = [
            self.CDA_fuses[i] * self.op_point.dynamic_pressure()
            for i, fuse in enumerate(self.airplane.fuselages)
        ]

        ### Wings
        self.wing_Res = [
            self.op_point.compute_reynolds(wing.mean_geometric_chord())
            for i, wing in enumerate(self.airplane.wings)
        ]
        self.wing_airfoils = [
            wing.xsecs[0].airfoil  # type: asb.Airfoil
            for i, wing in enumerate(self.airplane.wings)
        ]

        self.wing_Cl_incs = [
            self.wing_airfoils[i].CL_function(
                self.op_point.alpha + wing.mean_twist_angle(),
                self.wing_Res[i], 0, 0)
            for i, wing in enumerate(self.airplane.wings)
        ]  # Incompressible 2D lift coefficient
        self.wing_CLs = [
            self.wing_Cl_incs[i] *
            aero.CL_over_Cl(wing.aspect_ratio(),
                            mach=self.op_point.mach,
                            sweep=wing.mean_sweep_angle())
            for i, wing in enumerate(self.airplane.wings)
        ]  # Compressible 3D lift coefficient
        self.lift_wings = [
            self.wing_CLs[i] * self.op_point.dynamic_pressure() * wing.area()
            for i, wing in enumerate(self.airplane.wings)
        ]

        self.wing_Cd_profiles = [
            self.wing_airfoils[i].CDp_function(
                self.op_point.alpha + wing.mean_twist_angle(),
                self.wing_Res[i], self.op_point.mach, 0)
            for i, wing in enumerate(self.airplane.wings)
        ]
        self.drag_wing_profiles = [
            self.wing_Cd_profiles[i] * self.op_point.dynamic_pressure() *
            wing.area() for i, wing in enumerate(self.airplane.wings)
        ]

        self.wing_oswalds_efficiencies = [
            0.95  # TODO make this a function of taper ratio
            for i, wing in enumerate(self.airplane.wings)
        ]
        self.drag_wing_induceds = [
            self.lift_wings[i]**2 /
            (self.op_point.dynamic_pressure() * np.pi * wing.span()**2 *
             self.wing_oswalds_efficiencies[i])
            for i, wing in enumerate(self.airplane.wings)
        ]

        self.drag_wings = [
            self.drag_wing_profiles[i] + self.drag_wing_induceds[i]
            for i, wing in enumerate(self.airplane.wings)
        ]

        self.wing_Cm_incs = [
            self.wing_airfoils[i].Cm_function(
                self.op_point.alpha + wing.mean_twist_angle(),
                self.wing_Res[i], 0, 0)
            for i, wing in enumerate(self.airplane.wings)
        ]  # Incompressible 2D moment coefficient
        self.wing_CMs = [
            self.wing_Cm_incs[i] *
            aero.CL_over_Cl(wing.aspect_ratio(),
                            mach=self.op_point.mach,
                            sweep=wing.mean_sweep_angle())
            for i, wing in enumerate(self.airplane.wings)
        ]  # Compressible 3D moment coefficient
        self.local_moment_wings = [
            self.wing_CMs[i] * self.op_point.dynamic_pressure() * wing.area() *
            wing.mean_geometric_chord()
            for i, wing in enumerate(self.airplane.wings)
        ]
        self.body_moment_wings = [
            self.local_moment_wings[i] +
            wing.approximate_center_of_pressure()[0] * self.lift_wings[i]
            for i, wing in enumerate(self.airplane.wings)
        ]

        # Force totals
        lift_forces = self.lift_fuses + self.lift_wings
        drag_forces = self.drag_fuses + self.drag_wings
        self.lift_force = cas.sum1(cas.vertcat(*lift_forces))
        self.drag_force = cas.sum1(cas.vertcat(*drag_forces))
        self.side_force = 0

        # Moment totals
        self.pitching_moment = cas.sum1(cas.vertcat(*self.body_moment_wings))

        # Calculate nondimensional forces
        q = self.op_point.dynamic_pressure()
        s_ref = self.airplane.s_ref
        b_ref = self.airplane.b_ref
        c_ref = self.airplane.c_ref
        self.CL = self.lift_force / q / s_ref
        self.CD = self.drag_force / q / s_ref
        self.Cm = self.pitching_moment / q / s_ref / c_ref
Beispiel #3
0
    def wing_aerodynamics(
        self,
        wing: Wing,
    ):
        """
        Estimates the aerodynamic forces, moments, and derivatives on a wing in isolation.

        Moments are given with the reference at Wing [0, 0, 0].

        Args:

            wing: A Wing object that you wish to analyze.

            op_point: The OperatingPoint that you wish to analyze the fuselage at.

        TODO account for wing airfoil pitching moment

        Returns:

        """
        ##### Alias a few things for convenience
        op_point = self.op_point
        wing_options = self.get_options(wing)

        ##### Compute general wing properties and things to be used in sectional analysis.
        sweep = wing.mean_sweep_angle()
        AR = wing.aspect_ratio()
        mach = op_point.mach()
        q = op_point.dynamic_pressure()
        CL_over_Cl = aerolib.CL_over_Cl(aspect_ratio=AR,
                                        mach=mach,
                                        sweep=sweep)
        oswalds_efficiency = aerolib.oswalds_efficiency(
            taper_ratio=wing.taper_ratio(),
            aspect_ratio=AR,
            sweep=sweep,
            fuselage_diameter_to_span_ratio=0  # an assumption
        )
        areas = wing.area(_sectional=True)
        aerodynamic_centers = wing.aerodynamic_center(_sectional=True)

        F_g = [0, 0, 0]
        M_g = [0, 0, 0]

        ##### Iterate through the wing sections.
        for sect_id in range(len(wing.xsecs) - 1):

            ##### Identify the wing cross sections adjacent to this wing section.
            xsec_a = wing.xsecs[sect_id]
            xsec_b = wing.xsecs[sect_id + 1]

            ##### When linearly interpolating, weight things by the relative chord.
            a_weight = xsec_a.chord / (xsec_a.chord + xsec_b.chord)
            b_weight = xsec_b.chord / (xsec_a.chord + xsec_b.chord)

            ##### Compute the local frame of this section, and put the z (normal) component into wind axes.
            xg_local, yg_local, zg_local = wing._compute_frame_of_section(
                sect_id)
            sect_aerodynamic_center = aerodynamic_centers[sect_id]

            sect_z_w = op_point.convert_axes(
                x_from=zg_local[0],
                y_from=zg_local[1],
                z_from=zg_local[2],
                from_axes="geometry",
                to_axes="wind",
            )

            ##### Compute the generalized angle of attack, so the geometric alpha that the wing section "sees".
            velocity_vector_b_from_freestream = op_point.convert_axes(
                x_from=-op_point.velocity,
                y_from=0,
                z_from=0,
                from_axes="wind",
                to_axes="body")
            velocity_vector_b_from_rotation = np.cross(op_point.convert_axes(
                sect_aerodynamic_center[0],
                sect_aerodynamic_center[1],
                sect_aerodynamic_center[2],
                from_axes="geometry",
                to_axes="body"), [op_point.p, op_point.q, op_point.r],
                                                       manual=True)
            velocity_vector_b = [
                velocity_vector_b_from_freestream[i] +
                velocity_vector_b_from_rotation[i] for i in range(3)
            ]
            velocity_mag_b = np.sqrt(
                sum([comp**2 for comp in velocity_vector_b]))
            velocity_dir_b = [
                velocity_vector_b[i] / velocity_mag_b for i in range(3)
            ]
            sect_z_b = op_point.convert_axes(
                x_from=zg_local[0],
                y_from=zg_local[1],
                z_from=zg_local[2],
                from_axes="geometry",
                to_axes="body",
            )
            vel_dot_normal = np.dot(velocity_dir_b, sect_z_b, manual=True)

            sect_alpha_generalized = 90 - np.arccosd(vel_dot_normal)

            def get_deflection(xsec):
                n_surfs = len(xsec.control_surfaces)
                if n_surfs == 0:
                    return 0
                elif n_surfs == 1:
                    surf = xsec.control_surfaces[0]
                    return surf.deflection
                else:
                    raise NotImplementedError(
                        "AeroBuildup currently cannot handle multiple control surfaces attached to a given WingXSec."
                    )

            ##### Compute sectional lift at cross sections using lookup functions. Merge them linearly to get section CL.
            xsec_a_Cl_incompressible = xsec_a.airfoil.CL_function(
                alpha=sect_alpha_generalized,
                Re=op_point.reynolds(xsec_a.chord),
                mach=
                0,  # Note: this is correct, mach correction happens in 2D -> 3D step
                deflection=get_deflection(xsec_a))
            xsec_b_Cl_incompressible = xsec_b.airfoil.CL_function(
                alpha=sect_alpha_generalized,
                Re=op_point.reynolds(xsec_b.chord),
                mach=
                0,  # Note: this is correct, mach correction happens in 2D -> 3D step
                deflection=get_deflection(xsec_b))
            sect_CL = (xsec_a_Cl_incompressible * a_weight +
                       xsec_b_Cl_incompressible * b_weight) * CL_over_Cl

            ##### Compute sectional drag at cross sections using lookup functions. Merge them linearly to get section CD.
            xsec_a_Cd_profile = xsec_a.airfoil.CD_function(
                alpha=sect_alpha_generalized,
                Re=op_point.reynolds(xsec_a.chord),
                mach=mach,
                deflection=get_deflection(xsec_a))
            xsec_b_Cd_profile = xsec_b.airfoil.CD_function(
                alpha=sect_alpha_generalized,
                Re=op_point.reynolds(xsec_b.chord),
                mach=mach,
                deflection=get_deflection(xsec_b))
            sect_CDp = (xsec_a_Cd_profile * a_weight +
                        xsec_b_Cd_profile * b_weight)

            ##### Compute induced drag from local CL and full-wing properties (AR, e)
            sect_CDi = (sect_CL**2 / (np.pi * AR * oswalds_efficiency))

            ##### Total the drag.
            sect_CD = sect_CDp + sect_CDi

            ##### Go to dimensional quantities using the area.
            area = areas[sect_id]
            sect_L = q * area * sect_CL
            sect_D = q * area * sect_CD

            ##### Compute the direction of the lift by projecting the section's normal vector into the plane orthogonal to the freestream.
            sect_L_direction_w = (np.zeros_like(sect_z_w[0]), sect_z_w[1] /
                                  np.sqrt(sect_z_w[1]**2 + sect_z_w[2]**2),
                                  sect_z_w[2] /
                                  np.sqrt(sect_z_w[1]**2 + sect_z_w[2]**2))
            sect_L_direction_g = op_point.convert_axes(*sect_L_direction_w,
                                                       from_axes="wind",
                                                       to_axes="geometry")

            ##### Compute the direction of the drag by aligning the drag vector with the freestream vector.
            sect_D_direction_w = (-1, 0, 0)
            sect_D_direction_g = op_point.convert_axes(*sect_D_direction_w,
                                                       from_axes="wind",
                                                       to_axes="geometry")

            ##### Compute the force vector in geometry axes.
            sect_F_g = [
                sect_L * sect_L_direction_g[i] + sect_D * sect_D_direction_g[i]
                for i in range(3)
            ]

            ##### Compute the moment vector in geometry axes.
            sect_M_g = np.cross(sect_aerodynamic_center, sect_F_g, manual=True)

            ##### Add section forces and moments to overall forces and moments
            F_g = [F_g[i] + sect_F_g[i] for i in range(3)]
            M_g = [M_g[i] + sect_M_g[i] for i in range(3)]

            ##### Treat symmetry
            if wing.symmetric:
                ##### Compute the local frame of this section, and put the z (normal) component into wind axes.

                sym_sect_aerodynamic_center = aerodynamic_centers[sect_id]
                sym_sect_aerodynamic_center[1] *= -1

                sym_sect_z_w = op_point.convert_axes(
                    x_from=zg_local[0],
                    y_from=-zg_local[1],
                    z_from=zg_local[2],
                    from_axes="geometry",
                    to_axes="wind",
                )

                ##### Compute the generalized angle of attack, so the geometric alpha that the wing section "sees".
                sym_velocity_vector_b_from_freestream = op_point.convert_axes(
                    x_from=-op_point.velocity,
                    y_from=0,
                    z_from=0,
                    from_axes="wind",
                    to_axes="body")
                sym_velocity_vector_b_from_rotation = np.cross(
                    op_point.convert_axes(sym_sect_aerodynamic_center[0],
                                          sym_sect_aerodynamic_center[1],
                                          sym_sect_aerodynamic_center[2],
                                          from_axes="geometry",
                                          to_axes="body"),
                    [op_point.p, op_point.q, op_point.r],
                    manual=True)
                sym_velocity_vector_b = [
                    sym_velocity_vector_b_from_freestream[i] +
                    sym_velocity_vector_b_from_rotation[i] for i in range(3)
                ]
                sym_velocity_mag_b = np.sqrt(
                    sum([comp**2 for comp in sym_velocity_vector_b]))
                sym_velocity_dir_b = [
                    sym_velocity_vector_b[i] / sym_velocity_mag_b
                    for i in range(3)
                ]
                sym_sect_z_b = op_point.convert_axes(
                    x_from=zg_local[0],
                    y_from=-zg_local[1],
                    z_from=zg_local[2],
                    from_axes="geometry",
                    to_axes="body",
                )
                sym_vel_dot_normal = np.dot(sym_velocity_dir_b,
                                            sym_sect_z_b,
                                            manual=True)

                sym_sect_alpha_generalized = 90 - np.arccosd(
                    sym_vel_dot_normal)

                def get_deflection(xsec):
                    n_surfs = len(xsec.control_surfaces)
                    if n_surfs == 0:
                        return 0
                    elif n_surfs == 1:
                        surf = xsec.control_surfaces[0]
                        return surf.deflection if surf.symmetric else -surf.deflection
                    else:
                        raise NotImplementedError(
                            "AeroBuildup currently cannot handle multiple control surfaces attached to a given WingXSec."
                        )

                ##### Compute sectional lift at cross sections using lookup functions. Merge them linearly to get section CL.
                sym_xsec_a_Cl_incompressible = xsec_a.airfoil.CL_function(
                    alpha=sym_sect_alpha_generalized,
                    Re=op_point.reynolds(xsec_a.chord),
                    mach=
                    0,  # Note: this is correct, mach correction happens in 2D -> 3D step
                    deflection=get_deflection(xsec_a))
                sym_xsec_b_Cl_incompressible = xsec_b.airfoil.CL_function(
                    alpha=sym_sect_alpha_generalized,
                    Re=op_point.reynolds(xsec_b.chord),
                    mach=
                    0,  # Note: this is correct, mach correction happens in 2D -> 3D step
                    deflection=get_deflection(xsec_b))
                sym_sect_CL = (
                    sym_xsec_a_Cl_incompressible * a_weight +
                    sym_xsec_b_Cl_incompressible * b_weight) * CL_over_Cl

                ##### Compute sectional drag at cross sections using lookup functions. Merge them linearly to get section CD.
                sym_xsec_a_Cd_profile = xsec_a.airfoil.CD_function(
                    alpha=sym_sect_alpha_generalized,
                    Re=op_point.reynolds(xsec_a.chord),
                    mach=mach,
                    deflection=get_deflection(xsec_a))
                sym_xsec_b_Cd_profile = xsec_b.airfoil.CD_function(
                    alpha=sym_sect_alpha_generalized,
                    Re=op_point.reynolds(xsec_b.chord),
                    mach=mach,
                    deflection=get_deflection(xsec_b))
                sym_sect_CDp = (sym_xsec_a_Cd_profile * a_weight +
                                sym_xsec_b_Cd_profile * b_weight)

                ##### Compute induced drag from local CL and full-wing properties (AR, e)
                sym_sect_CDi = (sym_sect_CL**2 /
                                (np.pi * AR * oswalds_efficiency))

                ##### Total the drag.
                sym_sect_CD = sym_sect_CDp + sym_sect_CDi

                ##### Go to dimensional quantities using the area.
                area = areas[sect_id]
                sym_sect_L = q * area * sym_sect_CL
                sym_sect_D = q * area * sym_sect_CD

                ##### Compute the direction of the lift by projecting the section's normal vector into the plane orthogonal to the freestream.
                sym_sect_L_direction_w = (
                    np.zeros_like(sym_sect_z_w[0]), sym_sect_z_w[1] /
                    np.sqrt(sym_sect_z_w[1]**2 + sym_sect_z_w[2]**2),
                    sym_sect_z_w[2] /
                    np.sqrt(sym_sect_z_w[1]**2 + sym_sect_z_w[2]**2))
                sym_sect_L_direction_g = op_point.convert_axes(
                    *sym_sect_L_direction_w,
                    from_axes="wind",
                    to_axes="geometry")

                ##### Compute the direction of the drag by aligning the drag vector with the freestream vector.
                sym_sect_D_direction_w = (-1, 0, 0)
                sym_sect_D_direction_g = op_point.convert_axes(
                    *sym_sect_D_direction_w,
                    from_axes="wind",
                    to_axes="geometry")

                ##### Compute the force vector in geometry axes.
                sym_sect_F_g = [
                    sym_sect_L * sym_sect_L_direction_g[i] +
                    sym_sect_D * sym_sect_D_direction_g[i] for i in range(3)
                ]

                ##### Compute the moment vector in geometry axes.
                sym_sect_M_g = np.cross(sym_sect_aerodynamic_center,
                                        sym_sect_F_g,
                                        manual=True)

                ##### Add section forces and moments to overall forces and moments
                F_g = [F_g[i] + sym_sect_F_g[i] for i in range(3)]
                M_g = [M_g[i] + sym_sect_M_g[i] for i in range(3)]

        ##### Convert F_g and M_g to body and wind axes for reporting.
        F_b = op_point.convert_axes(*F_g, from_axes="geometry", to_axes="body")
        F_w = op_point.convert_axes(*F_b, from_axes="body", to_axes="wind")
        M_b = op_point.convert_axes(*M_g, from_axes="geometry", to_axes="body")
        M_w = op_point.convert_axes(*M_b, from_axes="body", to_axes="wind")

        return {
            "F_g": F_g,
            "F_b": F_b,
            "F_w": F_w,
            "M_g": M_g,
            "M_b": M_b,
            "M_w": M_w,
            "L": -F_w[2],
            "Y": F_w[1],
            "D": -F_w[0],
            "l_b": M_b[0],
            "m_b": M_b[1],
            "n_b": M_b[2]
        }