Source code for pymead.core.transformation
import numpy as np
[docs]
class Transformation2D:
[docs]
def __init__(self, tx: list = None, ty: list = None, r: list = None, sx: list = None,
sy: list = None, rotation_units: str = 'rad', order='r,s,t'):
"""Allows for arbitrary 2D transformations on a set of coordinates of size 2 x N"""
self.tx = tx if tx is not None else [0.0]
self.ty = ty if ty is not None else [0.0]
self.r = r if r is not None else [0.0]
self.sx = sx if sx is not None else [1.0]
self.sy = sy if sy is not None else [1.0]
if rotation_units == 'rad':
self.r_rad = self.r
self.r_deg = np.rad2deg(np.array(self.r)).tolist()
elif rotation_units == 'deg':
self.r_deg = self.r
self.r_rad = np.deg2rad(np.array(self.r)).tolist()
self.order = order
self.r_mat = None
self.s_mat = None
self.t_mat = None
self.M = np.eye(3) # 3 x 3 identity matrix
self.generate_rotation_matrix()
self.generate_scale_matrix()
self.generate_translation_matrix()
self.generate_transformation_matrix()
def generate_rotation_matrix(self):
self.r_mat = np.array([np.array([[np.cos(r), -np.sin(r), 0],
[np.sin(r), np.cos(r), 0],
[0, 0, 1]]) for r in self.r_rad])
def generate_scale_matrix(self):
self.s_mat = np.array([np.array([[self.sx[idx], 0, 0],
[0, self.sy[idx], 0],
[0, 0, 1]]) for idx in range(len(self.sx))])
def generate_translation_matrix(self):
self.t_mat = np.array([np.array([[1, 0, self.tx[idx]],
[0, 1, self.ty[idx]],
[0, 0, 1]]) for idx in range(len(self.tx))])
def generate_transformation_matrix(self):
r_count = 0
s_count = 0
t_count = 0
for idx, operation in enumerate(self.order.split(',')):
if operation == 'r':
self.M = self.M @ self.r_mat[r_count].T
r_count += 1
elif operation == 's':
self.M = self.M @ self.s_mat[s_count].T
s_count += 1
elif operation == 't':
self.M = self.M @ self.t_mat[t_count].T
t_count += 1
else:
raise TransformationError(f'Invalid value \'{operation}\' found in 2-D transformation '
f'(must be \'r\', \'s\', or \'t\'')
[docs]
def transform(self, coordinates: np.ndarray):
"""Computes the transformation of the coordinates.
Parameters
==========
coordinates: np.ndarray
Size N x 2, where N is the number of coordinates. The columns represent :math:`x` and :math:`y`.
"""
return (np.column_stack((coordinates, np.ones(len(coordinates)))) @ self.M)[:, :2] # x' = x * M
[docs]
class Transformation3D:
[docs]
def __init__(self, tx: list = None, ty: list = None, tz: list = None, rx: list = None, ry: list = None,
rz: list = None, sx: list = None, sy: list = None, sz: list = None,
rotation_units: str = 'rad', order='rx,ry,rz,s,t'):
"""Allows for arbitrary 3D transformations on a set of coordinates of size 3 x N"""
self.tx = tx if tx is not None else [0.0]
self.ty = ty if ty is not None else [0.0]
self.tz = tz if tz is not None else [0.0]
self.rx = rx if rx is not None else [0.0]
self.ry = ry if ry is not None else [0.0]
self.rz = rz if rz is not None else [0.0]
self.sx = sx if sx is not None else [1.0]
self.sy = sy if sy is not None else [1.0]
self.sz = sz if sz is not None else [1.0]
if rotation_units == 'rad':
self.rx_rad = self.rx
self.rx_deg = np.rad2deg(np.array(self.rx)).tolist()
self.ry_rad = self.ry
self.ry_deg = np.rad2deg(np.array(self.ry)).tolist()
self.rz_rad = self.rz
self.rz_deg = np.rad2deg(np.array(self.rz)).tolist()
elif rotation_units == 'deg':
self.rx_deg = self.rx
self.rx_rad = np.deg2rad(np.array(self.rx)).tolist()
self.ry_deg = self.ry
self.ry_rad = np.deg2rad(np.array(self.ry)).tolist()
self.rz_deg = self.rz
self.rz_rad = np.deg2rad(np.array(self.rz)).tolist()
self.order = order.split(',')
self.rx_mat = None
self.ry_mat = None
self.rz_mat = None
self.s_mat = None
self.t_mat = None
self.M = np.eye(4) # 4 x 4 identity matrix
self.generate_rotation_matrix_x()
self.generate_rotation_matrix_y()
self.generate_rotation_matrix_z()
self.generate_scale_matrix()
self.generate_translation_matrix()
self.generate_transformation_matrix()
def generate_rotation_matrix_x(self):
self.rx_mat = np.array([np.array([[1, 0, 0, 0],
[0, np.cos(r), np.sin(r), 0],
[0, -np.sin(r), np.cos(r), 0],
[0, 0, 0, 1]]
) for r in self.rx_rad])
def generate_rotation_matrix_y(self):
self.ry_mat = np.array([np.array([[np.cos(r), 0, -np.sin(r), 0],
[0, 1, 0, 0],
[np.sin(r), 0, np.cos(r), 0],
[0, 0, 0, 1]]
) for r in self.ry_rad])
def generate_rotation_matrix_z(self):
self.rz_mat = np.array([np.array([[np.cos(r), -np.sin(r), 0, 0],
[np.sin(r), np.cos(r), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]]
) for r in self.rz_rad])
def generate_scale_matrix(self):
self.s_mat = np.array([np.array([[self.sx[idx], 0, 0, 0],
[0, self.sy[idx], 0, 0],
[0, 0, self.sz[idx], 0],
[0, 0, 0, 1]]) for idx in range(len(self.sx))])
def generate_translation_matrix(self):
self.t_mat = np.array([np.array([[1, 0, 0, self.tx[idx]],
[0, 1, 0, self.ty[idx]],
[0, 0, 1, self.tz[idx]],
[0, 0, 0, 1]]) for idx in range(len(self.tx))])
def generate_transformation_matrix(self):
rx_count = 0
ry_count = 0
rz_count = 0
s_count = 0
t_count = 0
for idx, operation in enumerate(self.order):
if operation == 'rx':
self.M = self.M @ self.rx_mat[rx_count].T
rx_count += 1
elif operation == 'ry':
self.M = self.M @ self.ry_mat[ry_count].T
ry_count += 1
elif operation == 'rz':
self.M = self.M @ self.rz_mat[rz_count].T
rz_count += 1
elif operation == 's':
self.M = self.M @ self.s_mat[s_count].T
s_count += 1
elif operation == 't':
self.M = self.M @ self.t_mat[t_count].T
t_count += 1
else:
raise TransformationError(f'Invalid value \'{operation}\' found in 3-D transformation order '
f'(must be \'rx\', \'ry\', \'rz\', \'s\', or \'t\'')
[docs]
def transform(self, coordinates: np.ndarray):
"""Computes the transformation of the coordinates.
Parameters
==========
coordinates: np.ndarray
Size N x 3, where N is the number of coordinates. The columns represent :math:`x` and :math:`y`.
"""
return (np.column_stack((coordinates, np.ones(len(coordinates)))) @ self.M)[:, :3] # x' = x * M
[docs]
class AirfoilTransformation:
"""Convenience class for computing transformations of coordinate pairs :math:`(x,y)` to and from the
airfoil-relative coordinate system."""
[docs]
def __init__(self, dx, dy, alf, c):
self.transform_abs_obj = Transformation2D(tx=[dx], ty=[dy], r=[-alf], sx=[c], sy=[c],
rotation_units='rad', order='r,s,t')
self.transform_rel_obj = Transformation2D(tx=[-dx], ty=[-dy], r=[alf], sx=[1.0 / c], sy=[1.0 / c],
rotation_units='rad', order='t,s,r')
[docs]
def transform_abs(self, coordinates: np.ndarray):
"""Computes the transformation of the coordinates from the airfoil-relative coordinate system to the absolute
coordinate system.
Parameters
==========
coordinates: np.ndarray
Size N x 2, where N is the number of coordinates. The columns represent :math:`x` and :math:`y`.
"""
return self.transform_abs_obj.transform(coordinates)
[docs]
def transform_rel(self, coordinates: np.ndarray):
"""Computes the transformation of the coordinates from the absolute coordinate system to the airfoil-relative
coordinate system.
Parameters
==========
coordinates: np.ndarray
Size N x 2, where N is the number of coordinates. The columns represent :math:`x` and :math:`y`.
"""
return self.transform_rel_obj.transform(coordinates)