def test_euler_angles_equivalence_to_general_3D():
    phi = 1
    theta = 2
    psi = 3

    rot_euler = np.rotation_matrix_from_euler_angles(phi, theta, psi)
    rot_manual = (
            np.rotation_matrix_3D(psi, np.array([0, 0, 1])) @
            np.rotation_matrix_3D(theta, np.array([0, 1, 0])) @
            np.rotation_matrix_3D(phi, np.array([1, 0, 0]))
    )

    assert rot_euler == pytest.approx(rot_manual)
Beispiel #2
0
 def xyz_te(self) -> np.ndarray:
     """
     Returns the (wing-relative) coordinates of the trailing edge of the cross section.
     """
     rot = np.rotation_matrix_3D(self.twist * pi / 180, self.twist_axis)
     xyz_te = self.xyz_le + rot @ np.array([self.chord, 0, 0])
     return xyz_te
Beispiel #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
Beispiel #4
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
    def compute_rotation_matrix_wind_to_geometry(self) -> np.ndarray:
        """
        Computes the 3x3 rotation matrix that transforms from wind axes to geometry axes.

        Returns: a 3x3 rotation matrix.

        """

        alpha_rotation = np.rotation_matrix_3D(angle=np.radians(self.alpha),
                                               axis=np.array([0, 1, 0]),
                                               _axis_already_normalized=True)
        beta_rotation = np.rotation_matrix_3D(angle=np.radians(self.beta),
                                              axis=np.array([0, 0, 1]),
                                              _axis_already_normalized=True)
        axes_flip = np.rotation_matrix_3D(
            angle=np.pi,
            axis=np.array([0, 1, 0]),
            _axis_already_normalized=True
        )  # Since in geometry axes, X is downstream by convention, while in wind axes, X is upstream by convetion. Same with Z being up/down respectively.

        r = axes_flip @ alpha_rotation @ beta_rotation  # where "@" is the matrix multiplication operator

        return r
def test_general_3D_shorthands():
    rotx = np.rotation_matrix_3D(1, np.array([1, 0, 0]))
    assert pytest.approx(rotx) == np.rotation_matrix_3D(1, "x")

    roty = np.rotation_matrix_3D(1, np.array([0, 1, 0]))
    assert pytest.approx(roty) == np.rotation_matrix_3D(1, "y")

    rotz = np.rotation_matrix_3D(1, np.array([0, 0, 1]))
    assert pytest.approx(rotz) == np.rotation_matrix_3D(1, "z")
def test_rotation_matrices(types):
    for angle in types["scalar"]:
        for axis in types["vector"]:
            np.rotation_matrix_2D(angle)
            np.rotation_matrix_3D(angle, np.array([axis[0], axis[1], axis[0]]))
def test_validity_of_general_3D():
    rot = np.rotation_matrix_3D(
        angle=1,
        axis=[2, 3, 4]
    )
    assert np.is_valid_rotation_matrix(rot)