def test_cross_1D_input():
    a = np.array([1, 1, 1])
    b = np.array([1, 2, 3])

    cas_a = cas.DM(a)
    cas_b = cas.DM(b)

    correct_result = np.cross(a, b)
    cas_correct_result = cas.DM(correct_result)

    assert np.all(np.cross(a, cas_b) == cas_correct_result)
    assert np.all(np.cross(cas_a, b) == cas_correct_result)
    assert np.all(np.cross(cas_a, cas_b) == cas_correct_result)
def test_cross_2D_input_first_axis():
    a = np.tile(np.array([1, 1, 1]), (3, 1)).T
    b = np.tile(np.array([1, 2, 3]), (3, 1)).T

    cas_a = cas.DM(a)
    cas_b = cas.DM(b)

    correct_result = np.cross(a, b, axis=0)
    cas_correct_result = cas.DM(correct_result)

    assert np.all(np.cross(a, cas_b, axis=0) == cas_correct_result)
    assert np.all(np.cross(cas_a, b, axis=0) == cas_correct_result)
    assert np.all(np.cross(cas_a, cas_b, axis=0) == cas_correct_result)
Example #3
0
    def _compute_frame_of_WingXSec(self, index: int):

        twist = self.xsecs[index].twist

        if index == len(self.xsecs) - 1:
            index = len(self.xsecs) - 2  # The last WingXSec has the same frame as the last section.

        ### Compute the untwisted reference frame

        xg_local = np.array([1, 0, 0])

        xyz_le_a = self.xsecs[index].xyz_le
        xyz_le_b = self.xsecs[index + 1].xyz_le
        vector_between = xyz_le_b - xyz_le_a
        vector_between[0] = 0  # Project it onto the YZ plane.
        yg_local = vector_between / np.linalg.norm(vector_between)

        zg_local = np.cross(xg_local, yg_local)

        ### Twist the reference frame by the WingXSec twist angle
        rot = np.rotation_matrix_3D(
            twist * pi / 180,
            yg_local
        )
        xg_local = rot @ xg_local
        zg_local = rot @ zg_local

        return xg_local, yg_local, zg_local
Example #4
0
    def _compute_frame_of_section(self, index: int) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
        """
        Computes the local reference frame associated with a particular section. (Note that sections and cross
        sections are different! Cross sections, or xsecs, are the vertices, and sections are the parts in between. In
        other words, a wing with N cross sections (xsecs) will always have N-1 sections.

        Args:

            index: Which section should we get the frame of? If given `i`, this retrieves the frame of the section
            between xsecs `i` and `i+1`.

        Returns:

            A tuple of (xg_local, yg_local, zg_local), where each entry refers to the respective (normalized) axis
            of the local reference frame of the section. Given in geometry axes.

        """
        in_front = self._compute_xyz_le_of_WingXSec(index)
        in_back = self._compute_xyz_te_of_WingXSec(index)
        out_front = self._compute_xyz_le_of_WingXSec(index + 1)
        out_back = self._compute_xyz_te_of_WingXSec(index + 1)

        diag1 = out_back - in_front
        diag2 = out_front - in_back
        cross = np.cross(diag1, diag2)

        zg_local = cross / np.linalg.norm(cross)

        quarter_chord_vector = (
                                       0.75 * out_front + 0.25 * out_back
                               ) - (
                                       0.75 * in_front + 0.25 * in_back
                               )
        quarter_chord_vector[0] = 0

        yg_local = quarter_chord_vector / np.linalg.norm(quarter_chord_vector)

        xg_local = np.cross(yg_local, zg_local)

        return xg_local, yg_local, zg_local
Example #5
0
    def _compute_frame_of_WingXSec(
            self, index: int) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
        """
        Computes the local reference frame associated with a particular cross section (XSec) of this wing.

        Args:

            index: Which cross section (as indexed in Wing.xsecs) should we get the frame of?

        Returns:

            A tuple of (xg_local, yg_local, zg_local), where each entry refers to the respective (normalized) axis of
            the local reference frame of the WingXSec. Given in geometry axes.

        """
        ### Compute the untwisted reference frame
        xg_local = np.array([1, 0, 0])
        if index == 0:
            span_vector = self.xsecs[1].xyz_le - self.xsecs[0].xyz_le
            span_vector[0] = 0
            yg_local = span_vector / np.linalg.norm(span_vector)
            z_scale = 1
        elif index == len(self.xsecs) - 1 or index == -1:
            span_vector = self.xsecs[-1].xyz_le - self.xsecs[-2].xyz_le
            span_vector[0] = 0
            yg_local = span_vector / np.linalg.norm(span_vector)
            z_scale = 1
        else:
            vector_before = self.xsecs[index].xyz_le - self.xsecs[index -
                                                                  1].xyz_le
            vector_after = self.xsecs[index +
                                      1].xyz_le - self.xsecs[index].xyz_le
            vector_before[0] = 0  # Project onto YZ plane.
            vector_after[0] = 0  # Project onto YZ plane.
            vector_before = vector_before / np.linalg.norm(vector_before)
            vector_after = vector_after / np.linalg.norm(vector_after)
            span_vector = (vector_before + vector_after) / 2
            yg_local = span_vector / np.linalg.norm(span_vector)
            cos_vectors = np.linalg.inner(vector_before, vector_after)
            z_scale = np.sqrt(2 / (cos_vectors + 1))

        zg_local = np.cross(xg_local, yg_local) * z_scale

        ### Twist the reference frame by the WingXSec twist angle
        rot = np.rotation_matrix_3D(self.xsecs[index].twist * pi / 180,
                                    yg_local)
        xg_local = rot @ xg_local
        zg_local = rot @ zg_local

        return xg_local, yg_local, zg_local
Example #6
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]
        }