Studying Eigenvalues of Rotation Group Matrices
Table of Contents
- $ \text{SU}(2) $ Matrices
- $ \text{SO}(3) $ Matrices
- Spectra comparison
- Determinants In this exploration, I mess around with the spectra of two different rotation groups. I wanted to develop intuition for the elements of each group, and especially to discover through experimentation how \( \text{SU}(2) \) is a double cover of \( \text{SO}(3) \). Because my background is in numerical methods and not mathematics, I like to develop this through code, empirical visualization, and experimentation.
This is also a live test of some tweaking I’ve been doing to my tweaks to Org-Jekyll-Lite in order to run code and \(\LaTeX\) in Org documents and export it to web page.
\( \text{SU}(2) \) Matrices
Pulling random elements of the quaternions
Since elements of the group \( \text{SU}(2) \) are isomorphic to the unit quaternions, \(\mathbb{H}\), we will draw a unit quaternion and map it onto the matrix representation of SU(2).
import numpy as np
def random_quaternion() -> np.ndarray:
u1, u2, u3 = np.random.random(3)
q0 = np.sqrt(1 - u1) * np.sin(2 * np.pi * u2)
q1 = np.sqrt(1 - u1) * np.cos(2 * np.pi * u2)
q2 = np.sqrt(u1) * np.sin(2 * np.pi * u3)
q3 = np.sqrt(u1) * np.cos(2 * np.pi * u3)
return np.array([q0, q1, q2, q3])
Mapping to \( \text{SU}(2) \) matrices
The general form of the \( \text{SU}(2) \) matrix for a quaternion \( (q_0, q_1, q_2, q_3) \) is given by:
\( \begin{bmatrix} q_0 + iq_3 & q_2 + iq_1 \ -q_2 + iq_1 & q_0 - iq_3 \end{bmatrix} \)
def quaternion_to_su2(quaternion: np.ndarray) -> np.ndarray:
# Convert a quaternion into its SU(2) matrix representation.
q0, q1, q2, q3 = quaternion
return np.array([[q0 + 1j*q3, q2 + 1j*q1],
[-q2 + 1j*q1, q0 - 1j*q3]])
def draw_random_su2() -> np.ndarray:
return quaternion_to_su2(random_quaternion())
import pandas as pd
pd.DataFrame(quaternion_to_su2(random_quaternion()))
0 | 1 | |
---|---|---|
0 | -0.706679+0.640604j | 0.141797+0.264810j |
1 | -0.141797+0.264810j | -0.706679-0.640604j |
pd.DataFrame(quaternion_to_su2(random_quaternion()))
0 | 1 | |
---|---|---|
0 | 0.020562+0.599385j | -0.636772+0.484599j |
1 | 0.636772+0.484599j | 0.020562-0.599385j |
\( \text{SO}(3) \) Matrices
\( \text{SO}(3) \) matrices are 3x3 orthogonal matrices with determinant 1. They represent rotations in 3D space.
def quaternion_to_so3(quat: np.ndarray) -> np.ndarray:
w, x, y, z = quat
return np.array([
[1 - 2*y**2 - 2*z**2, 2*x*y - 2*z*w, 2*x*z + 2*y*w],
[2*x*y + 2*z*w, 1 - 2*x**2 - 2*z**2, 2*y*z - 2*x*w],
[2*x*z - 2*y*w, 2*y*z + 2*x*w, 1 - 2*x**2 - 2*y**2]
])
def draw_random_so3() -> np.ndarray:
quat = random_quaternion()
return quaternion_to_so3(quat)
pd.DataFrame(draw_random_so3())
0 | 1 | 2 | |
---|---|---|---|
0 | -0.387516 | -0.605739 | -0.694918 |
1 | 0.468365 | 0.519912 | -0.714371 |
2 | 0.794019 | -0.602305 | 0.082233 |
pd.DataFrame(draw_random_so3())
0 | 1 | 2 | |
---|---|---|---|
0 | -0.965783 | -0.034937 | -0.256986 |
1 | -0.036723 | 0.999323 | 0.002155 |
2 | 0.256737 | 0.011518 | -0.966413 |
Spectra comparison
\( \text{SU}(2) \)
from numpy.linalg import eigvals
su2_eigs =np.array([eigvals(draw_random_su2()) for _ in range(10)])
pd.DataFrame(su2_eigs)
0 | 1 | |
---|---|---|
0 | -0.206546+0.978437j | -0.206546-0.978437j |
1 | -0.388936+0.921265j | -0.388936-0.921265j |
2 | 0.024305-0.999705j | 0.024305+0.999705j |
3 | 0.025415+0.999677j | 0.025415-0.999677j |
4 | -0.629142-0.777290j | -0.629142+0.777290j |
5 | 0.483896-0.875125j | 0.483896+0.875125j |
6 | -0.498570+0.866850j | -0.498570-0.866850j |
7 | -0.136583+0.990629j | -0.136583-0.990629j |
8 | -0.331002+0.943630j | -0.331002-0.943630j |
9 | -0.224566+0.974459j | -0.224566-0.974459j |
So the eigenvalues of \( \text{SU}(2) \) matrices are always complex conjugates of each other, and lie on the unit circle.
import pandas as pd
import numpy as np
pd.DataFrame(np.abs(su2_eigs[:5]))
0 | 1 | |
---|---|---|
0 | 1.0 | 1.0 |
1 | 1.0 | 1.0 |
2 | 1.0 | 1.0 |
3 | 1.0 | 1.0 |
4 | 1.0 | 1.0 |
\( \text{SO}(3) \)
from numpy.linalg import eigvals
so3_eigs = np.array([eigvals(draw_random_so3()) for _ in range(10)])
pd.DataFrame(so3_eigs)
0 | 1 | 2 | |
---|---|---|---|
0 | -0.368758+0.929525j | -0.368758-0.929525j | 1.000000+0.000000j |
1 | -0.197081+0.980387j | -0.197081-0.980387j | 1.000000+0.000000j |
2 | 0.436682+0.899616j | 0.436682-0.899616j | 1.000000+0.000000j |
3 | 1.000000+0.000000j | -0.935815+0.352491j | -0.935815-0.352491j |
4 | -0.318919+0.947782j | -0.318919-0.947782j | 1.000000+0.000000j |
5 | -0.739021+0.673682j | -0.739021-0.673682j | 1.000000+0.000000j |
6 | 1.000000+0.000000j | -0.997412+0.071897j | -0.997412-0.071897j |
7 | 1.000000+0.000000j | -0.019389+0.999812j | -0.019389-0.999812j |
8 | -0.816935+0.576729j | -0.816935-0.576729j | 1.000000+0.000000j |
9 | 1.000000+0.000000j | -0.692390+0.721524j | -0.692390-0.721524j |
Two of the eigenvalues of \( \text{SO}(3) \) matrices are also always complex conjugates of each other, with the other unity.
import numpy as np
np.abs(pd.DataFrame(so3_eigs[:5]))
0 | 1 | 2 | |
---|---|---|---|
0 | 1.0 | 1.0 | 1.0 |
1 | 1.0 | 1.0 | 1.0 |
2 | 1.0 | 1.0 | 1.0 |
3 | 1.0 | 1.0 | 1.0 |
4 | 1.0 | 1.0 | 1.0 |
Also, the eigenvalues of \( \text{SO}(3) \) also lie on the unit circle. So far, though, I have not yet seen how \( \text{SU}(2) \) is a covers \( \text{SO}(3) \) doubly.
Determinants
\( \text{SU}(2) \) matrices have determinant 1 by definition (“Special”). \( \text{SO}(3) \) matrices also have determinant 1 by definition (“Orthogonal”). So this is not a good way to distinguish between the two groups.
pd.DataFrame({"SU2":np.linalg.det([draw_random_su2() for _ in range(5)]), "SO3":np.linalg.det([draw_random_so3() for _ in range(5)])})
SU2 | SO3 | |
---|---|---|
0 | 1.0+0.0j | 1.0 |
1 | 1.0+0.0j | 1.0 |
2 | 1.0+0.0j | 1.0 |
3 | 1.0+0.0j | 1.0 |
4 | 1.0-0.0j | 1.0 |
Enjoy Reading This Article?
Here are some more articles you might like to read next: