rotate(tensor, axis, angle)
.Let →v denote a vector in R2. We recall from linear algebra that →v rotated through an angle θ about the x-axis, denoted →v′, has the following matrix formula
→v′=[cosθ−sinθsinθcosθ]→v.Let z=a+bi∈C for some a,b∈R. Consider the corresponding (or isomorphic) vector →z=(a,b)∈R2. We observe from the rotation formula that →v′=a(cosθ,sinθ)+b(−sinθ,cosθ) after expanding the matrix product. Let w=c+di∈C for some c,d∈R. We recall the definition of the complex product as zw=(ac−bd)+i(ad+bc) where i2=−1. Hence, z′=(a+bi)(cosθ+isinθ)=(a+bi)eiθ after comparing the rotated vector →v′ with the complex product as defined above. Therefore, the following compact formula arises for vector rotation (given the correspondence between C and R2):
z′=eiθz.However, for vector rotation in three-dimensional space, we curiously require a four-dimensional, non-commutative extension of the complex number system. The following discussion will provide an overview of the connection with vector rotation. For further reading, we suggest the document (quaternion_rotation).
H={a+bi+cj+dk:a,b,c,d∈R and i2=j2=k2=ijk=−1}Consider the special case of rotating a vector →v∈R3 through an angle θ about a normalized rotation axis →n perpendicular to the vector →v. We typically decompose a quaternion into a scalar and vector component whenever performing vector rotation, such as the decomposition of q=a+bi+cj+dk into q=(a,→w) where →w=(b,c,d). For future convenience, we define the following quaternions for vector rotation: v=(0,→v), v′=(0,→v′), and n=(0,→n).
From the fundamental formula of quaternion algebra, i2=j2=k2=ijk=−1, we could derive quaternion multiplication, known as the Hamilton product. Let q1,q2∈H where both q1 and q2 have zero scalar component. The Hamilton product of q1 and q2, after some straightforward verification, has the form q1q2=(−→q1⋅→q2,→q1×→q2). Hence, nv=(0,→n×→v) since →n and →v are orthogonal. We observe that the projection of the rotated vector →v′ onto →v is cosθ→v and the projection of →v′ onto →n×→v is sinθ(→n×→v), and hence →v′=cosθ→v+sinθ(→n×→v). We define the quaternion exponential as enθ=cosθ+nsinθ, analogous to the complex exponential. Finally, we identify the formula for vector rotation in R3 by comparing the rotated vector →v′ with the Hamilton product:
v′=(cosθ+nsinθ)v=enθvThe tensor rotation algorithm defined by the function rotate(tensor, axis, angle)
in tensor_rotation.py
does general vector and tensor rotation in R3 about an arbitrary rotation axis, not necessarily orthogonal to the original vector or tensor. For general three-dimensional rotation, we define the rotation quaternion as q=en(θ/2) and the conjugate of q as q∗=e−n(θ/2). The quaternion rotation operator has the following form for general vector and tensor rotation.
We should remark that quaternion-matrix multiplication is defined as column-wise quaternion multiplication (source). Furthermore, we claim that vq∗=qv whenever the rotation axis →n and the vector →v are perpendicular (straightforward verification), which will recover the rotation formula for the special case.
function rotate(tensor, axis, angle):
initialize quaternion q from rotation axis and angle
if tensor is a nested list (i.e. matrix)
convert tensor to a symbolic matrix
if not (size of symbolic matrix is (3, 3))
throw error for invalid matrix size
end if
initialize empty vector M
for column in tensor
append quaternion(column) onto M
end for
M = q * M *(conjugate of q) // rotate each column
replace each column of tensor with the vector part
of the associated column quaternion in M
initialize empty vector M
for row in tensor
append quaternion(row) onto M
end for
M = q * M *(conjugate of q) // rotate each row
replace each row of tensor with the vector part
of the associated row quaternion in M
convert tensor to a nested list
return tensor
else if tensor is a list (i.e. vector)
if not (length of vector is 3):
throw error for invalid vector length
v = q * quaternion(tensor) * (conjugate of q)
replace each element of tensor with the vector part
of the associated vector quaternion v
return tensor
end if
throw error for unsupported tensor type
end function
""" Symbolic Tensor (Quaternion) Rotation
The following script will perform symbolic tensor rotation using quaternions.
"""
from sympy import Quaternion as quat
from sympy import Matrix
from sympy.functions import transpose
# Input: tensor = 3-vector or (3x3)-matrix
# axis = rotation axis (normal 3-vector)
# angle = rotation angle (in radians)
# Output: rotated tensor (of original type)
def rotate(tensor, axis, angle):
# Quaternion-Matrix Multiplication
def mul(*args):
if isinstance(args[0], list):
q, M = args[1], args[0]
for i, col in enumerate(M):
M[i] = col * q
else:
q, M = args[0], args[1]
for i, col in enumerate(M):
M[i] = q * col
return M
# Rotation Quaternion (Axis, Angle)
q = quat.from_axis_angle(axis, angle)
if isinstance(tensor[0], list):
tensor = Matrix(tensor)
if tensor.shape != (3, 3):
raise Exception('Invalid Matrix Size')
# Rotation Formula: M' = (q.(q.M.q*)^T.q*)^T
M = [quat(0, *tensor[:, i]) for i in range(tensor.shape[1])]
M = mul(q, mul(M, q.conjugate()))
for i in range(tensor.shape[1]):
tensor[:, i] = [M[i].b, M[i].c, M[i].d]
M = [quat(0, *tensor[i, :]) for i in range(tensor.shape[0])]
M = mul(q, mul(M, q.conjugate()))
for i in range(tensor.shape[0]):
tensor[i, :] = [[M[i].b, M[i].c, M[i].d]]
return tensor.tolist()
if isinstance(tensor, list):
if len(tensor) != 3:
raise Exception('Invalid Vector Length')
# Rotation Formula: v' = q.v.q*
v = q * quat(0, *tensor) * q.conjugate()
return [v.b, v.c, v.d]
raise Exception('Unsupported Tensor Type')
In the following section, we demonstrate the rotation algorithm, specifically for symbolic tensor rotation, and validate that the numeric or symbolic output from the algorithm does agree with a known result, usually obtained from a rotation matrix. The format of each code cell has the structure: generate expected result from a rotation matrix or NRPy+, generate received result from the rotation algorithm, and assert that these are both equivalent.
We recall that any general three-dimensional rotation can be expressed as the composition of a rotation about each coordinate axis (see rotation theorem). Therefore, we validate our rotation algorithm using only rotations about each coordinate axis rather than about an arbitrary axis in three-dimensional space. Consider the following vector →v and matrix M defined below using the SymPy package.
from sympy.matrices import rot_axis1, rot_axis2, rot_axis3
from sympy import latex, pi
from IPython.display import Math
v, angle = [1, 0, 1], pi/2
M = [[1, 2, 1], [0, 1, 0], [2, 1, 2]]
Math('\\vec{v}=%s,\\,M=%s' % (latex(Matrix(v)), latex(Matrix(M))))
We further recall that for any rotation matrix R and vector →v, the rotated vector →v′ has the formula →v′=R→v and the rotated matrix M′ has the formula M′=RMR-1, where R−1=RT since every rotation matrix is an orthogonal matrix.
# vector rotation about x-axis
expected = rot_axis1(-angle) * Matrix(v)
received = Matrix(rotate(v, [1, 0, 0], angle))
assert expected == received; v_ = received
# matrix rotation about x-axis
expected = rot_axis1(-angle) * Matrix(M) * transpose(rot_axis1(-angle))
received = Matrix(rotate(M, [1, 0, 0], angle))
assert expected == received; M_ = received
Math('\\vec{v}\'=%s,\\,M\'=%s' % (latex(Matrix(v_)), latex(Matrix(M_))))
# vector rotation about y-axis
expected = rot_axis2(-angle) * Matrix(v)
received = Matrix(rotate(v, [0, 1, 0], angle))
assert expected == received; v_ = received
# matrix rotation about y-axis
expected = rot_axis2(-angle) * Matrix(M) * transpose(rot_axis2(-angle))
received = Matrix(rotate(M, [0, 1, 0], angle))
assert expected == received; M_ = received
Math('\\vec{v}\'=%s,\\,M\'=%s' % (latex(Matrix(v_)), latex(Matrix(M_))))
# vector rotation about z-axis
expected = rot_axis3(-angle) * Matrix(v)
received = Matrix(rotate(v, [0, 0, 1], angle))
assert expected == received; v_ = received
# matrix rotation about z-axis
expected = rot_axis3(-angle) * Matrix(M) * transpose(rot_axis3(-angle))
received = Matrix(rotate(M, [0, 0, 1], angle))
assert expected == received; M_ = received
Math('\\vec{v}\'=%s,\\,M\'=%s' % (latex(Matrix(v_)), latex(Matrix(M_))))
from sympy import symbols, simplify, cse
import indexedexp as ixp # NRPy+: symbolic indexed expressions
angle = symbols('theta', real=True)
vU = ixp.declarerank1("vU")
hUU = ixp.declarerank2("hUU", "sym01")
rotatedhUU, N = rotate(hUU, vU, angle), len(hUU)
Math('hUU=%s' % latex(Matrix(hUU)))
We demonstrate that a completely symbolic rotation applied to the tensor hμν does preserve the index symmetry (hμν=hνμ).
for i in range(N):
for j in range(N):
if j >= i: continue
assert simplify(rotatedhUU[i][j] - rotatedhUU[j][i]) == 0
print('Assertion Passed: rotatedhUU[{i}][{j}] == rotatedhUU[{j}][{i}]'.format(i=i, j=j))
Assertion Passed: rotatedhUU[1][0] == rotatedhUU[0][1] Assertion Passed: rotatedhUU[2][0] == rotatedhUU[0][2] Assertion Passed: rotatedhUU[2][1] == rotatedhUU[1][2]
cse_rotatedhUU = cse(Matrix(rotatedhUU))[1][0]
Math('\\text{cse}(hUU)=%s' % latex(Matrix(cse_rotatedhUU)))
The following code cell converts this Jupyter notebook into a proper, clickable LATEX-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename Tutorial-Symbolic_Tensor_Rotation.pdf. (Note that clicking on this link may not work; you may need to open the PDF file through another means.)
import cmdline_helper as cmd # NRPy+: Multi-platform Python command-line interface
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-Symbolic_Tensor_Rotation")
Created Tutorial-Symbolic_Tensor_Rotation.tex, and compiled LaTeX file to PDF file Tutorial-Symbolic_Tensor_Rotation.pdf