# Stepwise $SU(2)$ Rotations on the Bloch Sphere from $\pi$ to $2\pi$

Based on the useful straight-forward answers to both of my former questions, on multiple rotations of a qubit and bloch sphere subplots, I was able to implement the following $$SU(2)$$ rotations: At this point it is worth mentioning that (as a learner) I am really grateful for the high-quality support. The code looks as follows (I mainly used the sources "A Lie Group: Rotations in Quantum Mechanics", p. 67 from Jean-Marie Normand or equivalently A Representations of SU(2) and Lecture notes: Qubit representations and rotations, p. 3):

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import colors
from qiskit.visualization.bloch import Bloch
from qiskit.visualization import plot_bloch_vector
from sympy.physics.matrices import msigma
from sympy.physics.quantum.dagger import Dagger
from sympy import Matrix
from sympy import I, N, re, exp, sin, cos, pi, eye
import numpy as np

def to_spherical(vec):
x = np.real(vec)
y = np.real(vec)
z = np.real(vec)
hxy = np.hypot(x, y)
r = np.hypot(hxy, z)
ϕ = np.arctan2(y, x) #az
θ = np.arctan2(hxy, z) #el
return [r, ϕ, θ]

def to_cartesian(polar):
r = polar
ϕ = polar
θ = polar

x = r * np.sin(θ) * np.cos(ϕ)
y = r * np.sin(θ) * np.sin(ϕ)
z = r * np.cos(θ)
return [np.real(x), np.real(y), np.real(z)]

def rn_su2_euler(vec, rx, ry, rz):
spherical_vec = to_spherical(vec)
ϕ = spherical_vec
θ = spherical_vec

sx = msigma(1)
sy = msigma(2)
sz = msigma(3)
M_q = (np.sin(θ)*np.cos(ϕ)*sx + np.sin(θ)*np.sin(ϕ)*sy + np.cos(θ)*sz)
U_n = Matrix([[exp(-I*(rx+rz)/2)*cos(ry/2), -exp(-I*(rx-rz)/2)*sin(ry/2)], [exp(I*(rx-rz)/2)*sin(ry/2), exp(I*(rx+rz)/2)*cos(ry/2)]])
M_q_rotated = U_n*M_q*Dagger(U_n)
return M_q_rotated

def extract_angles(M_q_rotated):
cos_θ_rotated = float(N(re(M_q_rotated[0,0])))
θ_rotated = np.arccos(cos_θ_rotated)
temp = float(N(re(M_q_rotated[1,0])))
temp = temp/np.sin(θ_rotated)
ϕ_rotated = np.arccos(temp)
return (ϕ_rotated, θ_rotated)

red=rgb
yel=rgb
blu=rgb
result = [colors.to_hex([red,yel,blu])]
cr = red/n
cy = yel/n
cb = blu/n
for i in range(n):
if(red!=0):
red -= cr
if(yel!=0):
yel -= cy
if(blu!=0):
blu -= cb
result.append(colors.to_hex([red,yel,blu]))
return result

fig, ax = plt.subplots(figsize = [8, 12], nrows=3, ncols=2)
fig.patch.set_facecolor('white')
[axis.set_axis_off() for axis in ax.ravel()]

rotations = [[0, 0, pi/8], [0, 0, pi/8], [0, pi/8, 0], [0.00001, -pi/8, 0], [0, pi/8, pi/8], [0, pi/8, -pi/8]]
start_vec = [1, 0, 0]
num_iterations = 8

for m, rotation in enumerate(rotations):
ax = fig.add_subplot(320+(m+1), axes_class = Axes3D)

rot_x = rotation
rot_y = rotation
rot_z = rotation
_bloch = Bloch(axes=ax)
_bloch.vector_color = get_gradient_colors([0, 0, 1], num_iterations)
_bloch.vector_width = 1
sv = []
vec = start_vec
sv.append(vec)
for i in range(num_iterations):
M_q_rotated = rn_su2_euler(vec, rot_x, rot_y, rot_z)
(ϕ_rotated, θ_rotated) = extract_angles(M_q_rotated)
vec = np.array(to_cartesian([1, ϕ_rotated, θ_rotated]))
sv.append(vec)

_bloch.render()


My question: How can I achieve that the vectors (mirrored) cover the second half of the hemisphere? Analogous to this: How can I make the vectors in the lower right image cover the other half of the hemisphere? What I understood from the sources is that in the function rn_su2_euler the parameters that are the Euler angles rx can take a value from $$0$$ to $$2\pi$$ and ry a value from $$0$$ to $$\pi$$ and rz from $$0$$ to $$4\pi$$, see for example A Representations of SU(2). But somewhere it seems that there is still a minor bug.

For the sake of completeness, I uploaded the notebook here to GitHub.

The function rn_su2_euler needs some adjustments.

def rn_su2_euler(vec, rx, ry, rz, plot=False):
stabilized_vec = np.array(vec)+0.000001
spherical_vec = to_spherical(stabilized_vec)
ϕ = spherical_vec
θ = spherical_vec
if plot: print(f'Initial vector: \t{vec}.')
if plot: print(f'Spherical coords: \t{spherical_vec}.')

# https://www.phys.hawaii.edu/~yepez/Spring2013/lectures/Lecture1_Qubits_Notes.pdf (p. 3)

sx = msigma(1)
sy = msigma(2)
sz = msigma(3)
M_q = (np.sin(θ)*np.cos(ϕ)*sx + np.sin(θ)*np.sin(ϕ)*sy + np.cos(θ)*sz)

if plot: print(f'#{i}: M_q={M_q}')
r_hat = np.array([rx, ry, rz])
r = np.sqrt(float(np.tensordot(r_hat, r_hat, axes=1)))

if plot: print(f'Rotation angle = {r}')
n_hat = r_hat/(r)
sigma_hat = np.array([sx, sy, sz])
n_sigma_product = Matrix(np.tensordot(n_hat,sigma_hat, axes=1))

U_n = N(exp(-1j*n_sigma_product*r/2))
#U_n_syntetic = [[np.cos(r/2), -1j*np.sin(r/2)],[-1j*np.sin(r/2), np.cos(r/2)]]

M_q_rotated = N(U_n*M_q*Dagger(U_n))
# Source: https://en.wikipedia.org/wiki/Pauli_matrices#Pauli_vector

q_1 = re((M_q_rotated[0,1]+M_q_rotated[1,0])/2)
q_2 = re((M_q_rotated[1,0]-M_q_rotated[0,1])/2j)
q_3 = re(M_q_rotated[0,0])
q_rotated = [np.real(N(q_1)), np.real(N(q_2)), np.real(N(q_3))]
return q_rotated


Thus, the rotation works in both directions and reaches all parts of the circle.

The call of this would have to be revised accordingly.

The output at the end can then look like this: I have provided a possible and working implementation on this GitHub page.

To elaborate the concrete error in my OP, I refer to Fations answer. The key is how he translates the matrix form of a qubit state M_q at the end of his algorithm. The original rotation function is not the problem.

I ammemded the code as follows:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import colors
from qiskit.visualization.bloch import Bloch
from sympy.physics.matrices import msigma
from sympy.physics.quantum.dagger import Dagger
from sympy import Matrix
from sympy import I, N, re, exp, sin, cos, sqrt, pi, eye
import numpy as np

def cartesian_to_spherical(vec):
x = np.real(vec)
y = np.real(vec)
z = np.real(vec)
hxy = np.hypot(x, y)
r = np.hypot(hxy, z)
ϕ = np.arctan2(y, x) #az
θ = np.arctan2(hxy, z) #el
return [r, ϕ, θ]

def qubitmatrix_to_cartesian(M_q):
M_q = N(M_q)
q_1 = re((M_q[0,1] + M_q[1,0]) / 2)
q_2 = re((M_q[0,1] - M_q[1,0]) / 2*I)
q_3 = re(M_q[0,0])
return np.array([q_1, q_2, q_3], dtype=np.float64)

red=rgb
yel=rgb
blu=rgb
result = [colors.to_hex([red,yel,blu])]
cr = red/n
cy = yel/n
cb = blu/n
for i in range(n):
if(red!=0):
red -= cr
if(yel!=0):
yel -= cy
if(blu!=0):
blu -= cb
result.append(colors.to_hex([red,yel,blu]))
return result

def rn_su2_euler(vec, rx, ry, rz):
spherical_vec = cartesian_to_spherical(vec)
ϕ = spherical_vec
θ = spherical_vec
sx = msigma(1)
sy = msigma(2)
sz = msigma(3)
M_q = (np.sin(θ)*np.cos(ϕ)*sx + np.sin(θ)*np.sin(ϕ)*sy + np.cos(θ)*sz)
U_n = Matrix([[exp(-I*(rx+rz)/2)*cos(ry/2), -exp(-I*(rx-rz)/2)*sin(ry/2)], [exp(I*(rx-rz)/2)*sin(ry/2), exp(I*(rx+rz)/2)*cos(ry/2)]])
M_q_rotated = U_n*M_q*Dagger(U_n)
return M_q_rotated

fig, ax = plt.subplots(figsize = [8, 12], nrows=3, ncols=2)
fig.patch.set_facecolor('white')
[axis.set_axis_off() for axis in ax.ravel()]

rotations = [[0, 0, pi/8], [0, 0, -pi/8], [0, pi/8, 0], [0, -pi/8, 0], [0, pi/8, pi/8], [0, -pi/8, -pi/8]]
start_vec = [1, 0, 0]
num_iterations = 8

for m, rotation in enumerate(rotations):
ax = fig.add_subplot(320+(m+1), axes_class = Axes3D)

rot_x = rotation
rot_y = rotation
rot_z = rotation
_bloch = Bloch(axes=ax)
_bloch.vector_color = get_gradient_colors([0, 0, 1], num_iterations)
_bloch.vector_width = 1
sv = []
vec = start_vec
sv.append(vec)
for i in range(num_iterations):
M_q_rotated = rn_su2_euler(vec, rot_x, rot_y, rot_z)
vec = qubitmatrix_to_cartesian(M_q_rotated)
sv.append(vec)


The key was to add and use the fucntion qubitmatrix_to_cartesian The other code remains almost untouched. The result looks now as follows: 