2
$\begingroup$

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:

enter image description here

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[0])
    y = np.real(vec[1])
    z = np.real(vec[2])
    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[0]
    ϕ = polar[1]
    θ = polar[2]

    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[1]
    θ = spherical_vec[2]

    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)

def get_gradient_colors(rgb, n):
    red=rgb[0] 
    yel=rgb[1]
    blu=rgb[2]
    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[0]
    rot_y = rotation[1]
    rot_z = rotation[2]
    _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.add_vectors(sv)
    _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.

$\endgroup$

2 Answers 2

1
$\begingroup$

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[1]
θ = spherical_vec[2]
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:

enter image description here

I have provided a possible and working implementation on this GitHub page.

$\endgroup$
1
$\begingroup$

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[0])
    y = np.real(vec[1])
    z = np.real(vec[2])
    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)

def get_gradient_colors(rgb, n):
    red=rgb[0] 
    yel=rgb[1]
    blu=rgb[2]
    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[1]
    θ = spherical_vec[2]
    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[0]
    rot_y = rotation[1]
    rot_z = rotation[2]
    _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)

    _bloch.add_vectors(sv)
    _bloch.render()

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

enter image description here

Here ist the complete Notebook on GitHub.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.