Plotting on the Bloch Sphere

Important

Updated in QuTiP version 3.0.

Introduction

When studying the dynamics of a two-level system, it is often convent to visualize the state of the system by plotting the state-vector or density matrix on the Bloch sphere. In QuTiP, we have created two different classes to allow for easy creation and manipulation of data sets, both vectors and data points, on the Bloch sphere. The qutip.Bloch class, uses Matplotlib to render the Bloch sphere, where as qutip.Bloch3d uses the Mayavi rendering engine to generate a more faithful 3D reconstruction of the Bloch sphere.

The Bloch and Bloch3d Classes

In QuTiP, creating a Bloch sphere is accomplished by calling either:

In [1]: b = Bloch()

which will load an instance of the qutip.Bloch class, or using:

>>> b3d = Bloch3d()

that loads the qutip.Bloch3d version. Before getting into the details of these objects, we can simply plot the blank Bloch sphere associated with these instances via:

In [2]: b.show()
../images/bloch-empty.png

or

../images/bloch3d-blank.png

In addition to the show() command, the Bloch class has the following functions:

Name Input Parameters (#=optional) Description
add_points(pnts,#meth) pnts list/array of (x,y,z) points, meth=’m’ (default meth=’s’) will plot a collection of points as multi-colored data points. Adds a single or set of data points to be plotted on the sphere.
add_states(state,#kind) state Qobj or list/array of Qobj’s representing state or density matrix of a two-level system, kind (optional) string specifying if state should be plotted as point (‘point’) or vector (default). Input multiple states as a list or array
add_vectors(vec) vec list/array of (x,y,z) points giving direction and length of state vectors. adds single or multiple vectors to plot.
clear()   Removes all data from Bloch sphere. Keeps customized figure properties.
save(#format,#dirc) format format (default=’png’) of output file, dirc (default=cwd) output directory Saves Bloch sphere to a file.
show()   Generates Bloch sphere with given data.

As an example, we can add a single data point:

In [3]: pnt = [1/np.sqrt(3),1/np.sqrt(3),1/np.sqrt(3)]

In [4]: b.add_points(pnt)

In [5]: b.show()
../images/bloch-1pnt.png

and then a single vector:

In [6]: vec = [0,1,0]

In [7]: b.add_vectors(vec)

In [8]: b.show()
../images/bloch-1pnt+1vec.png

and then add another vector corresponding to the \(\left|\rm up \right>\) state:

In [9]: up = basis(2,0)

In [10]: b.add_states(up)

In [11]: b.show()
../images/bloch-1pnt+1vec+1state.png

Notice that when we add more than a single vector (or data point), a different color will automatically be applied to the later data set (mod 4). In total, the code for constructing our Bloch sphere with one vector, one state, and a single data point is:

In [12]: pnt = [1/np.sqrt(3),1/np.sqrt(3),1/np.sqrt(3)]

In [13]: b.add_points(pnt)

In [14]: b.add_vectors(vec)

In [15]: b.add_states(up)

In [16]: b.show()
../images/bloch-pnt-vec-state.png

where we have removed the extra show() commands. Replacing b=Bloch() with b=Bloch3d() in the above code generates the following 3D Bloch sphere.

../images/bloch3d+data.png

We can also plot multiple points, vectors, and states at the same time by passing list or arrays instead of individual elements. Before giving an example, we can use the clear() command to remove the current data from our Bloch sphere instead of creating a new instance:

In [17]: b.clear()

In [18]: b.show()
../images/bloch-clear.png

Now on the same Bloch sphere, we can plot the three states associated with the x, y, and z directions:

In [19]: x = (basis(2,0)+(1+0j)*basis(2,1)).unit()

In [20]: y = (basis(2,0)+(0+1j)*basis(2,1)).unit()

In [21]: z = (basis(2,0)+(0+0j)*basis(2,1)).unit()

In [22]: b.add_states([x,y,z])

In [23]: b.show()
../images/bloch-xyz-states.png

a similar method works for adding vectors:

In [24]: b.clear()

In [25]: vec = [[1,0,0],[0,1,0],[0,0,1]]

In [26]: b.add_vectors(vec)

In [27]: b.show()
../images/bloch-xyz-vecs.png

Adding multiple points to the Bloch sphere works slightly differently than adding multiple states or vectors. For example, lets add a set of 20 points around the equator (after calling clear()):

In [28]: xp = [np.cos(th) for th in np.linspace(0, 2*pi, 20)]

In [29]: yp = [np.sin(th) for th in np.linspace(0, 2*pi, 20)]

In [30]: zp = np.zeros(20)

In [31]: pnts = [xp, yp, zp]

In [32]: b.add_points(pnts)

In [33]: b.show()
../images/bloch-20pnts.png

Notice that, in contrast to states or vectors, each point remains the same color as the initial point. This is because adding multiple data points using the add_points function is interpreted, by default, to correspond to a single data point (single qubit state) plotted at different times. This is very useful when visualizing the dynamics of a qubit. An example of this is given in the example . If we want to plot additional qubit states we can call additional add_points functions:

In [34]: xz = np.zeros(20)

In [35]: yz = [np.sin(th) for th in np.linspace(0, pi, 20)]

In [36]: zz = [np.cos(th) for th in np.linspace(0, pi, 20)]

In [37]: b.add_points([xz, yz, zz])

In [38]: b.show()
../images/bloch-40pnts.png

The color and shape of the data points is varied automatically by the Bloch class. Notice how the color and point markers change for each set of data. Again, we have had to call add_points twice because adding more than one set of multiple data points is not supported by the add_points function.

What if we want to vary the color of our points. We can tell the qutip.Bloch class to vary the color of each point according to the colors listed in the b.point_color list (see Configuring the Bloch sphere below). Again after clear():

In [39]: xp = [np.cos(th) for th in np.linspace(0, 2*pi, 20)]

In [40]: yp = [sin(th) for th in np.linspace(0, 2*pi, 20)]

In [41]: zp = np.zeros(20)

In [42]: pnts = [xp, yp, zp]

In [43]: b.add_points(pnts,'m') # <-- add a 'm' string to signify 'multi' colored points

In [44]: b.show()
../images/bloch-multipnts.png

Now, the data points cycle through a variety of predefined colors. Now lets add another set of points, but this time we want the set to be a single color, representing say a qubit going from the \(\left|\rm up\right>\) state to the \(\left|\rm down\right>\) state in the y-z plane:

In [45]: xz = np.zeros(20)

In [46]: yz = [np.sin(th) for th in np.linspace(0, pi ,20)]

In [47]: zz = [np.cos(th) for th in np.linspace(0, pi, 20)]

In [48]: b.add_points([xz, yz, zz]) # no 'm'

In [49]: b.show()
../images/bloch-mpnts+pts.png

Again, the same plot can be generated using the qutip.Bloch3d class by replacing Bloch with Bloch3d:

../images/bloch3d+points.png

A more slick way of using this ‘multi’ color feature is also given in the example, where we set the color of the markers as a function of time.

Differences Between Bloch and Bloch3d

While in general the Bloch and Bloch3d classes are interchangeable, there are some important differences to consider when choosing between them.

  • The Bloch class uses Matplotlib to generate figures. As such, the data plotted on the sphere is in reality just a 2D object. In contrast the Bloch3d class uses the 3D rendering engine from VTK via mayavi to generate the sphere and the included data. In this sense the Bloch3d class is much more advanced, as objects are rendered in 3D leading to a higher quality figure.
  • Only the Bloch class can be embedded in a Matplotlib figure window. Thus if you want to combine a Bloch sphere with another figure generated in QuTiP, you can not use Bloch3d. Of course you can always post-process your figures using other software to get the desired result.
  • Due to limitations in the rendering engine, the Bloch3d class does not support LaTex for text. Again, you can get around this by post-processing.
  • The user customizable attributes for the Bloch and Bloch3d classes are not identical. Therefore, if you change the properties of one of the classes, these changes will cause an exception if the class is switched.

Configuring the Bloch sphere

Bloch Class Options

At the end of the last section we saw that the colors and marker shapes of the data plotted on the Bloch sphere are automatically varied according to the number of points and vectors added. But what if you want a different choice of color, or you want your sphere to be purple with different axes labels? Well then you are in luck as the Bloch class has 22 attributes which one can control. Assuming b=Bloch():

Attribute Function Default Setting
b.axes Matplotlib axes instance for animations. Set by axes keyword arg. None
b.fig User supplied Matplotlib Figure instance. Set by fig keyword arg. None
b.font_color Color of fonts ‘black’
b.font_size Size of fonts 20
b.frame_alpha Transparency of wireframe 0.1
b.frame_color Color of wireframe ‘gray’
b.frame_width Width of wireframe 1
b.point_color List of colors for Bloch point markers to cycle through [‘b’,’r’,’g’,’#CC6600’]
b.point_marker List of point marker shapes to cycle through [‘o’,’s’,’d’,’^’]
b.point_size List of point marker sizes (not all markers look the same size when plotted) [55,62,65,75]
b.sphere_alpha Transparency of Bloch sphere 0.2
b.sphere_color Color of Bloch sphere ‘#FFDDDD’
b.size Sets size of figure window [7,7] (700x700 pixels)
b.vector_color List of colors for Bloch vectors to cycle through [‘g’,’#CC6600’,’b’,’r’]
b.vector_width Width of Bloch vectors 4
b.view Azimuthal and Elevation viewing angles [-60,30]
b.xlabel Labels for x-axis [‘$x$’,’‘] +x and -x (labels use LaTeX)
b.xlpos Position of x-axis labels [1.1,-1.1]
b.ylabel Labels for y-axis [‘$y$’,’‘] +y and -y (labels use LaTeX)
b.ylpos Position of y-axis labels [1.2,-1.2]
b.zlabel Labels for z-axis [‘$left|0\right>$’,’$left|1\right>$’] +z and -z (labels use LaTeX)
b.zlpos Position of z-axis labels [1.2,-1.2]

Bloch3d Class Options

The Bloch3d sphere is also customizable. Note however that the attributes for the Bloch3d class are not in one-to-one correspondence to those of the Bloch class due to the different underlying rendering engines. Assuming b=Bloch3d():

Attribute Function Default Setting
b.fig User supplied Mayavi Figure instance. Set by fig keyword arg. None
b.font_color Color of fonts ‘black’
b.font_scale Scale of fonts 0.08
b.frame Draw wireframe for sphere? True
b.frame_alpha Transparency of wireframe 0.05
b.frame_color Color of wireframe ‘gray’
b.frame_num Number of wireframe elements to draw 8
b.frame_radius Radius of wireframe lines 0.005
b.point_color List of colors for Bloch point markers to cycle through [‘r’, ‘g’, ‘b’, ‘y’]
b.point_mode Type of point markers to draw sphere
b.point_size Size of points 0.075
b.sphere_alpha Transparency of Bloch sphere 0.1
b.sphere_color Color of Bloch sphere ‘#808080’
b.size Sets size of figure window [500,500] (500x500 pixels)
b.vector_color List of colors for Bloch vectors to cycle through [‘r’, ‘g’, ‘b’, ‘y’]
b.vector_width Width of Bloch vectors 3
b.view Azimuthal and Elevation viewing angles [45,65 ]
b.xlabel Labels for x-axis [‘|x>’, ‘’] +x and -x
b.xlpos Position of x-axis labels [1.07,-1.07]
b.ylabel Labels for y-axis [‘$y$’,’‘] +y and -y
b.ylpos Position of y-axis labels [1.07,-1.07]
b.zlabel Labels for z-axis [‘|0>’, ‘|1>’] +z and -z
b.zlpos Position of z-axis labels [1.07,-1.07]

These properties can also be accessed via the print command:

In [50]: b = Bloch()

In [51]: print(b)
Bloch data:
-----------
Number of points:  0
Number of vectors: 0

Bloch sphere properties:
------------------------
font_color:      black
font_size:       20
frame_alpha:     0.2
frame_color:     gray
frame_width:     1
point_color:     ['b', 'r', 'g', '#CC6600']
point_marker:    ['o', 's', 'd', '^']
point_size:      [25, 32, 35, 45]
sphere_alpha:    0.2
sphere_color:    #FFDDDD
figsize:         [5, 5]
vector_color:    ['g', '#CC6600', 'b', 'r']
vector_width:    3
vector_style:    -|>
vector_mutation: 20
view:            [-60, 30]
xlabel:          ['$x$', '']
xlpos:           [1.2, -1.2]
ylabel:          ['$y$', '']
ylpos:           [1.2, -1.2]
zlabel:          ['$\\left|0\\right>$', '$\\left|1\\right>$']
zlpos:           [1.2, -1.2]

Animating with the Bloch sphere

The Bloch class was designed from the outset to generate animations. To animate a set of vectors or data points the basic idea is: plot the data at time t1, save the sphere, clear the sphere, plot data at t2,... The Bloch sphere will automatically number the output file based on how many times the object has been saved (this is stored in b.savenum). The easiest way to animate data on the Bloch sphere is to use the save() method and generate a series of images to convert into an animation. However, as of Matplotlib version 1.1, creating animations is built-in. We will demonstrate both methods by looking at the decay of a qubit on the bloch sphere.

Example: Qubit Decay

The code for calculating the expectation values for the Pauli spin operators of a qubit decay is given below. This code is common to both animation examples.

from qutip import *
from scipy import *
def qubit_integrate(w, theta, gamma1, gamma2, psi0, tlist):
    # operators and the hamiltonian
    sx = sigmax(); sy = sigmay(); sz = sigmaz(); sm = sigmam()
    H = w * (cos(theta) * sz + sin(theta) * sx)
    # collapse operators
    c_op_list = []
    n_th = 0.5 # temperature
    rate = gamma1 * (n_th + 1)
    if rate > 0.0: c_op_list.append(sqrt(rate) * sm)
    rate = gamma1 * n_th
    if rate > 0.0: c_op_list.append(sqrt(rate) * sm.dag())
    rate = gamma2
    if rate > 0.0: c_op_list.append(sqrt(rate) * sz)


    # evolve and calculate expectation values
    output = mesolve(H, psi0, tlist, c_op_list, [sx, sy, sz])  
    return output.expect[0], output.expect[1], output.expect[2]
    
## calculate the dynamics
w     = 1.0 * 2 * pi   # qubit angular frequency
theta = 0.2 * pi       # qubit angle from sigma_z axis (toward sigma_x axis)
gamma1 = 0.5      # qubit relaxation rate
gamma2 = 0.2      # qubit dephasing rate
# initial state
a = 1.0
psi0 = (a* basis(2,0) + (1-a)*basis(2,1))/(sqrt(a**2 + (1-a)**2))
tlist = linspace(0,4,250)
#expectation values for ploting
sx, sy, sz = qubit_integrate(w, theta, gamma1, gamma2, psi0, tlist)

Generating Images for Animation

An example of generating images for generating an animation outside of Python is given below:

b = Bloch()
b.vector_color = ['r']
b.view = [-40,30]
for i in range(len(sx)):
    b.clear()
    b.add_vectors([np.sin(theta),0,np.cos(theta)])
    b.add_points([sx[:i+1],sy[:i+1],sz[:i+1]])
    b.save(dirc='temp') #saving images to temp directory in current working directory

Generating an animation using ffmpeg (for example) is fairly simple:

ffmpeg -r 20 -b 1800 -i bloch_%01d.png bloch.mp4

Directly Generating an Animation

Important

Generating animations directly from Matplotlib requires installing either mencoder or ffmpeg. While either choice works on linux, it is best to choose ffmpeg when running on the Mac. If using macports just do: sudo port install ffmpeg.

The code to directly generate an mp4 movie of the Qubit decay is as follows:

from pylab import *
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D

fig = figure()
ax = Axes3D(fig,azim=-40,elev=30)
sphere = Bloch(axes=ax)

def animate(i):
    sphere.clear()
    sphere.add_vectors([np.sin(theta),0,np.cos(theta)])
    sphere.add_points([sx[:i+1],sy[:i+1],sz[:i+1]])
    sphere.make_sphere()
    return ax

def init():
    sphere.vector_color = ['r']
    return ax

ani = animation.FuncAnimation(fig, animate, np.arange(len(sx)),
                            init_func=init, blit=True, repeat=False)
ani.save('bloch_sphere.mp4', fps=20, clear_temp=True)

The resulting movie may be viewed here: Bloch_Decay.mp4