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)
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
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
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)