00: Introduction to QuTiP#

In this first tutorial, you’ll play around with the core of QuTiP.

This includes constructing quantum states (e.g. qubits), quantum operators (e.g. gates in a quantum circuit), Hamiltonians, and density matrices. Near the end you’ll simulate the evolution of a quantum system using QuTiP’s numerical solver for the Schrödinger equation and visualize the result. Along the way you’ll find eigenvalues and eigenvectors and plot states on the Bloch sphere.

Individually none of the tasks should require more than a few lines of code, but there is a lot to explore. Take your time. Tackle individual steps in multiple ways. Read the documentation on the functions you’re using. Explore the objects you’re creating. Ask questions.

The topics covered in this tutorial will form the foundation we use during the rest of the week – and that you’ll use when using QuTiP yourself once the Summer school is over.

Tasks#

Pre-flight check#

If you haven’t installed Python, QuTiP, Jupyter and the other requirements yet, do so now by following one of the sets of instructions.

You can test that your setup is working by running the smoke test notebook.

Imports#

%matplotlib inline
import matplotlib.pyplot as plt
import qutip
import numpy as np

Helper functions#

def display_list(a, title=None):
    """ Display a list of items using Jupyter's display function. """
    if title:
        print(title)
        print("=" * len(title))
    for item in a:
        display(item)
def print_attrs(obj, name, attrs):
    """ Print the listed attributes of an object. """
    for attr in attrs:
        print(f"{name}.{attr}: {getattr(obj, attr)}")
def show_bloch(states, vector_color=None, vector_alpha=None):
    """ Show states or density matrices on the Bloch sphere. """
    bloch = qutip.Bloch()
    bloch.add_states(states)
    if vector_color is not None:
        if vector_color == "rb":
            vector_color = [
                (c, 0, 1 - c) for c in np.linspace(0.1, 1.0, len(states))
            ]
        bloch.vector_color = vector_color
    if vector_alpha is not None:
        bloch.vector_alpha = vector_alpha
    bloch.show()
def display_eigenstates(op):
    """ Display the eigenvalues and eigenstates of an operator. """
    evals, evecs = op.eigenstates()
    print("Eigenvalues:", evals)
    print()
    print("Eigenstates")
    print("===========")
    for v in evecs:
        display(v)

Accessing documentation#

Once you’re back home after the summer school, the QuTiP documentation will be your primary means of learning how to use QuTiP. In these tutorials, we’ll encourage you to read the documentation first, so that by the end of the week, you’ll be familiar with it and able to answer many of your own questions by reading it.

You’ll read the documentation in two main ways:

  • You can type a ? after any Python object to read its documentation. You can try it right now by typing qutip.ket? into notebook cell and pressing enter.

  • You can find the documentation online at qutip.org. Open it now and have a look. You can also download it as a PDF in case you need to work offline.

States#

  • Creating and examining quantum states

  • Arithmetic with states

  • Checking whether states are equal and whether states with a different global phase are equal

  • What is a Qobj?

Functions you’ll want to try out in this section are:

  • qutip.ket

  • qutip.bra

  • qutip.basis

These functions all return a “quantum object” or Qobj which is the data type QuTiP uses to store all kinds of quantum objects (states, operators, density matrices). A Qobj has some many methods and attributes that are useful.

Some attributes to look at include type, dims, shape, isket, and isbra.

A few important methods are .norm(), .unit(), .dag(), and .overlap(other).

You can use the arithmetic operators * and + as you would expect. Try out some simple numerical expressions from this morning’s theory lecture.

There are many more methods and attributes, but these are enough to start with.

# a qubit in the |0> state
ket = qutip.ket("0")
# show some of the properties relevant to kets
display(ket)
print_attrs(ket, "ket", ["type", "dims", "shape", "isket", "isbra"])
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}1.0\\0.0\\\end{matrix}\right)$\end{split}\]
ket.type: ket
ket.dims: [[2], [1]]
ket.shape: (2, 1)
ket.isket: True
ket.isbra: False
# show the adjoint of the ket -- a bra
ket.dag()
\[\begin{split}Quantum object: dims = [[1], [2]], shape = (1, 2), type = bra $ \\ \left(\begin{matrix}1.0 & 0.0\\\end{matrix}\right)$\end{split}\]
# construct the bra directly
bra = qutip.bra("0")
# a qubit in the |1> state
qutip.ket("1")
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}0.0\\1.0\\\end{matrix}\right)$\end{split}\]
# construct |0> and |1> using qutip.basis
q0 = qutip.basis(2, 0)
q1 = qutip.basis(2, 1)
display_list([q0, q1])
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}1.0\\0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}0.0\\1.0\\\end{matrix}\right)$\end{split}\]
# show that Qobj's with the same values are equal
q0 == q0
True
# show that Qobj's with different global phases are not equal
display(q0 * 1j)
print("Equal:", q0 == q0 * 1j)
print("Overlap:", q0.overlap(q0 * 1j))
print("Overlap is 1:", np.abs(q0.overlap(q0 * 1j)) == 1.0)
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}1.0j\\0.0\\\end{matrix}\right)$\end{split}\]
Equal: False
Overlap: 1j
Overlap is 1: True
# orthogonal vectors have 0 overlap
q0.overlap(q1)
0j
# for states, the overlap is the inner product
print("q0 · q1:", (q0.dag() * q1).full()[0, 0])
print("q0 · q1:", (q1.dag() * q0).full()[0, 0])
print("(1j * q0) · q0:", ((1j * q0).dag() * q0).full()[0, 0])
q0 · q1: 0j
q0 · q1: 0j
(1j * q0) · q0: -1j
# adding states
q0 + q1
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}1.0\\1.0\\\end{matrix}\right)$\end{split}\]
# calculating the norm
(q0 + q1).norm()
1.4142135623730951
# manually normalizing a state to unit length
(q0 + q1) / (q0 + q1).norm()
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}0.707\\0.707\\\end{matrix}\right)$\end{split}\]
# using .unit() to normalize the length of a state
(q0 + q1).unit()
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}0.707\\0.707\\\end{matrix}\right)$\end{split}\]

Bloch sphere#

  • Plotting 2-level states on the Bloch sphere

  • Plotting many states at once

  • Changing the colors

  • Changing the transparency

QuTiP provides a class called qutip.Bloch. You can create an instance of it and add states to plot with .add_states(...). Once you’ve added all the states you want to, you can all .show() to render it.

The Bloch sphere is very useful for visualizing the state of a two-level system, and you’ll use it regularly.

You can change the colors and transparency of the arrow shown for each state by setting the .vector_color attribute to a list of colors, and the .vector_alpha attribute to a list of transparency values.

# show a single state
show_bloch(q0)
../_images/00-qutip-intro_30_0.png
# show both basis states
show_bloch([q0, q1])
../_images/00-qutip-intro_31_0.png
# show many states
theta = np.linspace(0, np.pi, 20)
states = [q0 + np.exp(1j * th) * q1 for th in theta]
states = [s.unit() for s in states]
show_bloch(states)
../_images/00-qutip-intro_32_0.png
# How to remove the colours:
show_bloch(
    states,
    vector_color=[(0, 0, 1.0)] * len(states),
)
../_images/00-qutip-intro_33_0.png
# Use transparency to show where states are in the list
show_bloch(
    states,
    vector_color=[(0, 0, 1.0)] * len(states),
    vector_alpha=np.linspace(0.1, 1.0, len(states)),
)
../_images/00-qutip-intro_34_0.png
# Use color map to show where states are in the list
show_bloch(
    states,
    vector_color=[(c, 0, 1 - c) for c in np.linspace(0.1, 1.0, len(states))],
)
../_images/00-qutip-intro_35_0.png

Operators#

  • Creating and examining quantum operators

  • Calculating expectation values

  • Making projectors

Functions to read about and try out in this section are:

  • qutip.sigmax, qutip.sigmay, qutip.sigmaz

  • qutip.qeye

  • qutip.qip.operations.hadamard_transform

Operators are also represented by Qobj instances and there are some attitional Qobj attributes that are relevant now: type, isoper, dims, shape, isherm. Look at those you’ve seen already and compare them with what you’ve already seen for states.

There are some additional Qobj methods to try out on operators – .diag(), .tr() – and some to revisit – .dag(), .norm(), .unit().

You can create a projection operator by calling .proj() on a state. Try this out. Also try creating the projector yourself without calling .proj().

# the Pauli X operator
sx = qutip.sigmax()
# show some of the properties relevant to operators
display(sx)
print_attrs(sx, "sx", ["type", "isoper", "dims", "shape", "isherm"])
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 1.0\\1.0 & 0.0\\\end{matrix}\right)$\end{split}\]
sx.type: oper
sx.isoper: True
sx.dims: [[2], [2]]
sx.shape: (2, 2)
sx.isherm: True
# show the adjoint
sx.dag()
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 1.0\\1.0 & 0.0\\\end{matrix}\right)$\end{split}\]
# show the diagonal entries
sx.diag()
array([0., 0.])
# show the trace
sx.tr()
0.0
# show the (trace) norm
sx.norm()
2.0
# apply sx to the basis states -- it swaps them
display_list([sx * q0, sx * q1])
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}0.0\\1.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}1.0\\0.0\\\end{matrix}\right)$\end{split}\]
# construct the other Pauli matrices and the identity matrix
sy = qutip.sigmay()
sz = qutip.sigmaz()
I = qutip.qeye(2)
display_list([I, sx, sy, sz])
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 1.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 1.0\\1.0 & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & -1.0j\\1.0j & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & -1.0\\\end{matrix}\right)$\end{split}\]
# Hadamard gate
H = qutip.qip.operations.hadamard_transform()
H
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.707 & 0.707\\0.707 & -0.707\\\end{matrix}\right)$\end{split}\]
# Making projectors
q0.proj()
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]
q0 * q0.dag()
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]

Eigenvalues and eigenstates#

  • Eigenvalues

  • Eigenstates

It’s often important to know the eigenvalues and eigenstates of operators. QuTiP makes this easy. Read about and try out the .eigenenergies() and .eigenstates() methods of Qobj.

display_eigenstates(sz)
Eigenvalues: [-1.  1.]

Eigenstates
===========
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}0.0\\-1.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}-1.0\\0.0\\\end{matrix}\right)$\end{split}\]
display_eigenstates(sx)
Eigenvalues: [-1.  1.]

Eigenstates
===========
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}-0.707\\0.707\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}0.707\\0.707\\\end{matrix}\right)$\end{split}\]
display_eigenstates(sy)
Eigenvalues: [-1.  1.]

Eigenstates
===========
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}-0.707\\0.707j\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket $ \\ \left(\begin{matrix}-0.707\\-0.707j\\\end{matrix}\right)$\end{split}\]

Density matrices#

  • Creating density matrices

  • Plotting 2-level density matrices on the Bloch sphere

  • Plot some pure states

  • Plot some mixed states

QuTiP of course supports density matrices in addition to pure states. You can convert a state to a density matrix by calling qutip.ket2dm. You should also try construct some density matrices directly using the expression you saw in the theory lectures earlier.

One can also plot density matrices on the Bloch. Try it out! Once you’ve tried a few, attempt to predict how each density matrix will be represented before calling .show() and see if you are right. If you’re not right, read the Wikipedia article and try again.

rho0 = qutip.ket2dm(q0)
rho0
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]
qutip.ket2dm(q0 * np.exp(0.1j))
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]
q0 * q0.dag()  # global phases cancel
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]
rho = q0 * q0.dag() + q1 * q1.dag()
rho
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 1.0\\\end{matrix}\right)$\end{split}\]
rho.norm()
2.0
rho = rho.unit()
rho
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.500 & 0.0\\0.0 & 0.500\\\end{matrix}\right)$\end{split}\]
bloch = qutip.Bloch()

bloch.add_states([qutip.ket2dm(q0), qutip.ket2dm(q1)])
bloch.add_states([rho])

bloch.show()
../_images/00-qutip-intro_59_0.png
# p = 0.5 * (I + n . (sx, sy, sz))
# if p == 0.5 I, then n = (0, 0, 0)
rho == 0.5 * qutip.qeye(2)
True
basis = [qutip.qeye(2), sx, sy, sz]
for v in basis:
    display(v)
for v in basis:
    assert (v * v).tr() == 2.0
for v in basis:
    for w in basis:
        if v != w:
            assert (v * w).tr() == 0.0
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 1.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 1.0\\1.0 & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & -1.0j\\1.0j & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & -1.0\\\end{matrix}\right)$\end{split}\]

Time-dependent operators#

  • Create a time-dependent operator

  • What is a QobjEvo?

  • Adding arguments

In QuTiP, time-dependent operators are represented by qutip.QobjEvo and not by Qobj. QobjEvo takes in a list of terms that are added together. Each term consists of a Qobj (the constant part) and a time-dependent coefficient function, like so:

sigmax_t = QobjEvo([[sx, "t"]])

Here sx is the operator and "t" is the time-dependent coefficient. Note that "t" is a string. QobjEvo turns this string into a very fast function for you. It’s also just very convenient to be able to write the coefficient function compactly.

Try out some other operators and some other coefficients, for example, "cos(t)" and "sin(t)".

You can evaluate a QobjEvo at a particular time by calling, for example, sigmax_t(2) or sigmax_t(0.1), or any other time. Create a state object that depends on time and plot its evolution on the Bloch sphere.

Lastly you can define coefficients that depend on arguments, such as "cos(w * t)". You need to supply an initial value for the arguments when creating the QobjEvo, like so:

sigmax_cos_t = QobjEvo([[sx, "cos(w * t)"]], args={"w": np.pi})

You can supply a different argument when calling the QobjEvo, for example, sigmax_cos_t(0.1, {"w": np.pi / 4}).

# Create a simple quantum object that represents sx * t
sigmax_t = qutip.QobjEvo([[sx, "t"]])
display(sigmax_t)
display_list([
    sigmax_t(t) for t in [0.0, 0.25, 0.5, 1.0]
])
<qutip.qobjevo.QobjEvo at 0x7f84888420e0>
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 0.250\\0.250 & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 0.500\\0.500 & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 1.0\\1.0 & 0.0\\\end{matrix}\right)$\end{split}\]
# Multiplication:
display_list([
    (0.1 * sigmax_t)(1.0),
    (0.5 * sigmax_t)(1.0),
])
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 0.100\\0.100 & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 0.500\\0.500 & 0.0\\\end{matrix}\right)$\end{split}\]
# Addition:
sigmay_t = qutip.QobjEvo([[sy, "t"]])
display_list([
    (sigmax_t * sigmay_t)(1.0),
])
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = False $ \\ \left(\begin{matrix}1.0j & 0.0\\0.0 & -1.0j\\\end{matrix}\right)$\end{split}\]
# Take the conjugate and the adjoint:
display_list([
    sigmay_t.conj()(0.5),
    sigmay_t.dag()(0.5),
])
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 0.500j\\-0.500j & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & -0.500j\\0.500j & 0.0\\\end{matrix}\right)$\end{split}\]
# Plotting the evolution under sigmax_t
states = [(1j * sigmax_t(t)).expm() * q0 for t in np.linspace(0, np.pi / 2, 10)]
show_bloch(states, vector_color="rb")
../_images/00-qutip-intro_68_0.png
# Including a constant term
sz_plus_sy_t = qutip.QobjEvo([sz, [sy, "t"]])
display_list([
    sz_plus_sy_t(0.1),
    sz_plus_sy_t(1.0),
])
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & -0.100j\\0.100j & -1.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & -1.0j\\1.0j & -1.0\\\end{matrix}\right)$\end{split}\]
# Using a function like sin()
sz_sin_t = qutip.QobjEvo([[sz, "sin(t)"]])
display_list([
    sz_sin_t(t) for t in np.linspace(0, np.pi / 2, 4)
])
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.500 & 0.0\\0.0 & -0.500\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.866 & 0.0\\0.0 & -0.866\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & -1.0\\\end{matrix}\right)$\end{split}\]
# Supplying arguments other than time
sz_g_sin_t = qutip.QobjEvo([[sz, "g * sin(t)"]], args={"g": 2.0})
display_list([
    sz_g_sin_t(np.pi / 2, args={"g" : g}) for g in [0, 0.2, 0.4]
])
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.200 & 0.0\\0.0 & -0.200\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.400 & 0.0\\0.0 & -0.400\\\end{matrix}\right)$\end{split}\]

Tensor products and partial traces#

  • Construct a state with two or three qubits

  • Construct an operator on multiple qubits

  • Construct an entangled state

  • Take the partial trace of a product state

  • Take the partial trace of an entangled state

QuTiP has two very useful methods for combining and separating sub-systems of a larger quantum state. Read about qutip.tensor and Qobj.ptrace and try them out.

q00 = qutip.tensor([q0, q0])
q00
\[\begin{split}Quantum object: dims = [[2, 2], [1, 1]], shape = (4, 1), type = ket $ \\ \left(\begin{matrix}1.0\\0.0\\0.0\\0.0\\\end{matrix}\right)$\end{split}\]
pt_q0 = q00.ptrace(0)
pt_q0
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]
q01 = qutip.tensor([q0, q1])
q01
\[\begin{split}Quantum object: dims = [[2, 2], [1, 1]], shape = (4, 1), type = ket $ \\ \left(\begin{matrix}0.0\\1.0\\0.0\\0.0\\\end{matrix}\right)$\end{split}\]
display_list([
    q01.ptrace(0),
    q01.ptrace(1),
])
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}1.0 & 0.0\\0.0 & 0.0\\\end{matrix}\right)$\end{split}\]
\[\begin{split}Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper, isherm = True $ \\ \left(\begin{matrix}0.0 & 0.0\\0.0 & 1.0\\\end{matrix}\right)$\end{split}\]

Hamiltonians#

  • Building Hamiltonians

  • Plotting eigenvalues

Now use everything you’ve learned so far to construct a time-dependent Hamiltonian and plot the evolution of its eigenvalues over time.

H = qutip.QobjEvo([[sx, "a * cos(t)"], [sy, "b * sin(t)"]], args={"a": 0.2, "b": 0.1})
tlist = np.linspace(0, np.pi, 40)
evals = np.array([H(t).eigenenergies() for t in tlist])

plt.plot(tlist, evals[:, 0])
plt.plot(tlist, evals[:, 1])
plt.ylabel("Energy level")
plt.xlabel("t");
../_images/00-qutip-intro_79_0.png

Solving the Schrödinger Equation using sesolve#

  • Plotting the states

  • Plotting the states on the Bloch sphere

  • Plotting expectation values

QuTiP’s function for solving the Schrödinger equation is qutip.sesolve. sesolve returns a result object that has a .states attribute containing the states of the system at each requested time.

Or you can pass a list of expectation operators and then result will have a .expect attribute that contains the expectation values of each operator at each time. .expect[0] will contain the values of the first operator, .expect[1] all the values of the second operator, and so on.

Try it out and plot the results!

If you need to plot values, you can use plt.plot(x, y, label="Label for this plot"). You can read the help for plt.plot to find out more.

tlist = np.linspace(0, np.pi, 40)
result = qutip.sesolve(H, q0, tlist)
show_bloch(result.states, vector_color="rb")
../_images/00-qutip-intro_82_0.png

Using functions to make your notebooks neater#

  • Write a function for displaying the eigenstates of an operator

  • Write a function for displaying states on the Bloch sphere

# See the helper functions at the top for examples.