Part 2. Introduction to QuTiP#

The main objective of this part is to introduce the QuTiP library and its basic functionalities for simulating quantum systems, so you can develop some autonomy in using it for your own projects.

External resources:

2.1 What is QuTiP?#

QuTiP (Quantum Toolbox in Python) is an open-source software library for simulating the dynamics of open quantum systems. It provides a wide range of tools for creating, manipulating, and analyzing quantum objects, such as state vectors (wavefunctions), bras/ket/density matrices, quantum operators of single and composite systems, and superoperators (useful for defining master equations).

It supports various quantum systems, including qubits, harmonic oscillators, and spin chains, and allows users to model interactions with the environment through master equations and quantum trajectories. Some relevant applications are in quantum optics, quantum information, and condensed matter physics.

QuTiP is built on top of Python and leverages libraries such as NumPy, SciPy, and Matplotlib for numerical computations and visualizations.

Why QuTiP?#

In the context of quantum computing and quantum information, simulating quantum systems is crucial for understanding their behavior and designing new algorithms. Every quantum system in the real world is subject to non-negligible interactions with its surroundings, leading to phenomena such as decoherence and dissipation. Therefore, any realistic model of a quantum system must take into account these environmental effects. In addition, any measurement performed on the system necessarily involves coupling to the measuring device, therefore introducing an additional source of external influence to be accounted for when modelling the dynamics.

The dynamics of a closed quantum system is described by the Schrödinger equation, which governs the time evolution of the system’s wavefunction. However, when considering open quantum systems, where the system interacts with its environment, more sophisticated approaches are required. These include master equations (such as the Lindblad master equation) and quantum trajectory methods, which provide a framework for modeling the effects of decoherence and dissipation on the system’s dynamics.

The problem relies on the fact that for all but the most basic of Hamiltonians, an analytical description of the system dynamics is not possible. Then, one must resort to numerical simulations of the equations of motion. In absence of a quantum computer, these simulations must be carried out using classical computing techniques, but the exponentially increasing dimensionality of the underlying Hilbert space severely limits the size of system that can be efficiently simulated.

In order to tackle this problem, in many fields such as quantum optics, trapped ions, superconducting circuit devices, and most recently nanomechanical systems, it is possible to design systems using a small number of effective oscillator and spin components, excited by a limited number of quanta, that are amenable to classical simulation in a truncated Hilbert space. In this regard, QuTiP provides a user-friendly interface and a comprehensive set of tools that make it easier to model and analyze quantum systems. In this section, we will introduce some fundamental concepts and terminology used in QuTiP and quantum mechanics in general.

At its core, QuTiP is designed to facilitate the simulation of dynamics of open quantum systems. Its main strength lies in the series of solvers it provides for time evolution, including both unitary and non-unitary dynamics, as well as tools for visualizing quantum states and operators.

References#

Official website: http://qutip.org/

Publications:

  1. J. R. Johansson, P. D. Nation, and F. Nori, “QuTiP: An open-source Python framework for the dynamics of open quantum systems,” Computer Physics Communications, vol. 183, no. 8, pp. 1760-1772, 2012. DOI:10.1016/j.cpc.2012.02.021

  2. J. R. Johansson, P. D. Nation, and F. Nori, “QuTiP 2: A Python framework for the dynamics of open quantum systems,” Computer Physics Communications, vol. 184, no. 4, pp. 1234-1240, 2013. DOI:10.1016/j.cpc.2012.11.019

  3. F. Nori et al., “QuTiP: Quantum Toolbox in Python,” Quantum Science and Technology, vol. 3, no. 2, p. 020501, 2018. DOI:10.1088/2058-9565/aab9d8

  4. Lambert, N., Giguère, E., Menczel, P., Li, B., Hopf, P., Suárez, G., et al., Nori, F. (2024). Qutip 5: The quantum toolbox in python. arXiv preprint arXiv:2412.04705.

2.2 Basic usage and functionality#

The following documentation is a summary of Lecture 0 from the QuTiP Quantum Mechanics lectures.

2.2.1 Basic concepts and terminology#

Some fundamental concepts and terminology used in QuTiP and quantum mechanics:

  • Hilbert space: A Hilbert space is a mathematical framework used to describe quantum systems. It is a complex vector space equipped with an inner product, allowing for the representation of quantum states as vectors.

  • Quantum state: A quantum state is a mathematical object that describes the state of a quantum system. It contains all the information about the system and can be represented as a vector in a Hilbert space.

  • Operator: An operator is a mathematical object that acts on quantum states to produce new states. In quantum mechanics, observables (measurable quantities) are represented by operators.

  • Qubit: A qubit is the basic unit of quantum information. It is a two-level quantum system that can exist in a superposition of the |0⟩ and |1⟩ states.

  • Density matrix: The density matrix is a mathematical representation of a quantum state that can describe both pure and mixed states.

2.2.2 Creating quantum objects, qobj#

The key difference between classical and quantum mechanics is the use of operators instead of numbers as variables. Then, quantum states are represented as vectors in a complex vector space (Hilbert space), and operators act on these vectors to produce new vectors. Therefore, in computing the dynamical evolution of quantum systems, we need a data structure that encapsulates the properties of a quantum operator and ket/bra vectors.

QuTiP provides a class called Qobj to represent quantum objects such as states and operators. It contains all the information describing a quantum system, including its dimensions, type (state or operator), and data (matrix representation).

Note

By convention, the names of Python classes are capitalized, whereas the names of functions and variables are not.

To create a quantum object in QuTiP, you can use the Qobj class:

## Initial imports section
from qutip import Qobj
import numpy as np
import matplotlib.pyplot as plt

# Create a qubit state |0⟩
ket_0 = Qobj([[1], [0]])
print(ket_0)

# Create a qubit state |1⟩
ket_1 = Qobj([[0], [1]])
print("\n",ket_1)

# Create a Pauli-X operator
X = Qobj([[0, 1], [1, 0]])
print("\n",X)

# Create a density matrix for a mixed state
rho = Qobj([[0.5, 0], [0, 0.5]])
print("\n",rho)
Quantum object: dims=[[2], [1]], shape=(2, 1), type='ket', dtype=Dense
Qobj data =
[[1.]
 [0.]]

 Quantum object: dims=[[2], [1]], shape=(2, 1), type='ket', dtype=Dense
Qobj data =
[[0.]
 [1.]]

 Quantum object: dims=[[2], [2]], shape=(2, 2), type='oper', dtype=Dense, isherm=True
Qobj data =
[[0. 1.]
 [1. 0.]]

 Quantum object: dims=[[2], [2]], shape=(2, 2), type='oper', dtype=Dense, isherm=True
Qobj data =
[[0.5 0. ]
 [0.  0.5]]

We can also access various properties of the quantum object, such as its dimensions, type, and data:

# Qobj attributes example
ket_0 = Qobj([[1], [0]])  # Qubit state |0⟩
print("Ket |0⟩:", ket_0)
print(ket_0.dims)  # Dimensions of the quantum object
print(ket_0.type)  # Type of the quantum object (ket, bra, operator)
print(ket_0.data)  # Matrix representation of the quantum object

bra_0 = ket_0.dag()  # Bra state ⟨0|
print("\nBra ⟨0|:", bra_0)
print(bra_0.dims)  # Dimensions of the quantum object
print(bra_0.type)  # Type of the quantum object (ket, bra, operator)
print(bra_0.data)  # Matrix representation of the quantum object

X = Qobj([[0, 1], [1, 0]])  # Pauli-X operator
print("\nPauli-X operator X:", X)
print(X.dims)  # Dimensions of the quantum object
print(X.type)  # Type of the quantum object (ket, bra, operator)
print(X.data)  # Matrix representation of the quantum object
Ket |0⟩: Quantum object: dims=[[2], [1]], shape=(2, 1), type='ket', dtype=Dense
Qobj data =
[[1.]
 [0.]]
[[2], [1]]
ket
Dense(shape=(2, 1), fortran=True)

Bra ⟨0|: Quantum object: dims=[[1], [2]], shape=(1, 2), type='bra', dtype=Dense
Qobj data =
[[1. 0.]]
[[1], [2]]
bra
Dense(shape=(1, 2), fortran=False)

Pauli-X operator X: Quantum object: dims=[[2], [2]], shape=(2, 2), type='oper', dtype=Dense, isherm=True
Qobj data =
[[0. 1.]
 [1. 0.]]
[[2], [2]]
oper
Dense(shape=(2, 2), fortran=False)

It allows to create various quantum objects such as states, operators, and observables. Here are some examples:

  • Creating a qubit state:

from qutip import basis

# Create the |0⟩ and |1⟩ states
ket_0 = basis(2, 0)
ket_1 = basis(2, 1)

print(ket_0)
print(ket_1)
  • Creating a quantum operator:

from qutip import sigmax, sigmay, sigmaz

# Create the Pauli operators
X = sigmax()
Y = sigmay()
Z = sigmaz()

2.2.3 Operators and states#

As we have seen in the previous section, in QuTiP, operators and states are represented as Qobj instances. You can perform various operations on these objects, such as addition, multiplication, and tensor products.

  • Addition of operators: As well as in standard linear algebra, you can add two operators together:

C = X + Y
  • Multiplication of operators and states: You can multiply an operator by a state to obtain a new state:

new_state = X * ket_0
  • Tensor product: You can create composite quantum systems using the tensor product:

from qutip import tensor

# Create a composite system |0⟩ ⊗ |1⟩
composite_state = tensor(ket_0, ket_1)
  • Inner product and expectation values: You can compute the inner product between two states and the expectation value of an operator:

inner_product = ket_0.dag() * ket_1  # Inner product
expectation_value = ket_0.dag() * X * ket_0  # Expectation value

Table qutim commands:

Operation

QuTiP Command

Create basis state

basis(dim, index)

Create Pauli operators

sigmax(), sigmay(), sigmaz()

Tensor product

tensor(obj1, obj2)

Inner product

state1.dag() * state2

Expectation value

state1.dag() * operator * state1

Overlap

state1.overlap(state2)

Matrix representation

obj.data

2.2.4 Time evolution of quantum systems#

In some cases we may want to simulate how a quantum system evolves over time. How the state of a quantum system or an ensamble of quantum systems changes with time is described by the Schrödinger equation (for closed systems) or the master equation (for open systems).

QuTiP provides several methods for simulating the time evolution of quantum systems. There are two kinds of quantum systems: open systems that interact with a larger environment and closed systems that do not. The most commonly used methods are the Schrödinger equation for closed systems and the master equation for open systems.

Some of the solvers methods implemented in QuTiP for time evolution:

Equation

Solver

Description

Unitary evolution, Schrödinger equation

sesolve

Solves the time-dependent Schrödinger equation for closed quantum systems.

Periodic Schrödinger equation

fsolve

Solves the time-dependent Schrödinger equation for periodic systems.

Schrödinger equation using Krylov method

krylosolve

Efficiently solves the time-dependent Schrödinger equation using the Krylov subspace method.

Lindblad master equation or Von Neumann equation

mesolve

Solves the Lindblad master equation or the Von Neumann equation for open quantum systems.

Monte Carlo evolution

mcsolve

Simulates the stochastic evolution of quantum systems using the Monte Carlo wave-function method.

Non-Markovian master equation

nmme_solve

Solves non-Markovian master equations for open quantum systems.

Bloch-Redfield master equation

brmesolve

Solves the Bloch-Redfield master equation for open quantum systems.

Stochastic Schrödinger equation

ssesolve

Solves the stochastic Schrödinger equation for quantum systems under stochastic influences.

Stochastic master equation

smesolve

Solves the stochastic master equation for open quantum systems under stochastic influences.

The solver.Result Class#

The result of a time evolution simulation in QuTiP is encapsulated in the solver.Result class. This class contains information about the time points, states, and expectation values computed during the simulation. A solver.Result object typically includes the following attributes:

  • result.solver: The solver used for the simulation (e.g., mesolve, sesolve).

  • result.times: An array of time points at which the simulation was evaluated.

  • result.states: A list/array of quantum states as vectors or density matrices (Qobj instances) at each time point.

  • result.expect: A list of expectation values computed at each time point.

  • result.e_data: Dictionary of expectation values.

  • result.final_state: The final state of the system after the simulation.

    • result.stats: Various statistics about the simulation, such as the number of steps taken and the time taken for the computation.

Examples accessing Result Data:

# Access time points
time_points = result.times

# Access states
states = result.states

# Access expectation values
expectation_values = result.expect

# Access final state
final_state = result.final_state

# Access simulation statistics
simulation_stats = result.stats

Multilple Trajectories Solver Results#

When using solvers that involve stochastic processes, such as mcsolve for Monte Carlo wave-function simulations, the result may include multiple trajectories. Each trajectory represents a different realization of the stochastic process. In such cases, the solver.Result object will contain additional attributes to handle multiple trajectories:

  • result.num_trajectories: The number of trajectories simulated.

  • result.trajectories: A list of solver.Result objects, each corresponding to a single trajectory.

  • result.expect_trajectories: A list of expectation values for each trajectory.

  • result.states_trajectories: A list of states for each trajectory.

  • result.final_states_trajectories: A list of final states for each trajectory.

  • result.stats_trajectories: A list of statistics for each trajectory.

An interesting feature of QuTiP when working with multi-trajectories results is the addition operation that can be used to merge sets of trajectories.

# Merging two multi-trajectory results
merged_result = result1 + result2

2.2.5 Visualization and plotting#

QuTiP provides built-in functions for visualizing quantum states and operators. You can use these functions to create plots of state vectors, density matrices, and expectation values over time.

Some common visualization functions in QuTiP include:

Visualization Function

Description

plot_state

Plots the state vector or density matrix of a quantum state.

plot_expectation_values

Plots the expectation values of operators over time.

plot_wigner

Plots the Wigner function of a quantum state.

plot_bloch_vector

Plots the Bloch vector representation of a qubit state.

The Wigner function is a quasi-probability distribution used in quantum mechanics to represent quantum states in phase space. It provides a way to visualize the state of a quantum system, particularly in continuous-variable systems such as harmonic oscillators. The Wigner function can take on negative values, which is a signature of non-classical behavior. Read more here.

You can use these functions to create visualizations of your quantum systems. For example, to plot the expectation values of an operator over time, you can use the plot_expectation_values function:

from qutip import plot_expectation_values   
# Plot expectation values over time
plot_expectation_values(result)

2.2.6 Using Tensor Products and Partial traces#

QuTiP provides functions to work with composite quantum systems using tensor products and partial traces. The tensor function allows you to create composite states and operators, while the ptrace function enables you to compute the partial trace over specified subsystems.

Tensor Products#

To describe states of multipartite quantum systems, which means systems composed of multiple subsystems coupled within them, (e.g. two coupled qubits, a qubit coupled to a harmonic oscillator, etc), we use the tensor product. The tensor product allows us to combine the state spaces of individual subsystems into a larger state space that describes the entire system. It works by taking the outer product of the state vectors of the individual subsystems.

from qutip import tensor

# Create two qubit states
ket_0 = basis(2, 0)
ket_1 = basis(2, 1)

# Create a composite state |0⟩ ⊗ |1⟩
composite_state = tensor(ket_0, ket_1)
# Create two qubit operators
X1 = sigmax()
X2 = sigmax()
# Create a composite operator X1 ⊗ X2
composite_operator = tensor(X1, X2)

Partial Traces#

The partial trace is a mathematical operation used to obtain the reduced state of a subsystem by tracing out the degrees of freedom of the other subsystems. In QuTiP, you can use the ptrace function to compute the partial trace over specified subsystems.

from qutip import ptrace
# Create a composite state |0⟩ ⊗ |1⟩
composite_state = tensor(ket_0, ket_1)
# Compute the partial trace over the second subsystem
reduced_state = ptrace(composite_state, 0)  # Trace out the second subsystem
# Create a composite operator X1 ⊗ X2
composite_operator = tensor(X1, X2)
# Compute the partial trace over the second subsystem
reduced_operator = ptrace(composite_operator, 0)  # Trace out the second subsystem
# Display the reduced state and operator
print(reduced_state)
print(reduced_operator)
# Output:
Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket
Quantum object: dims = [[2], [2]], shape = (2, 2), type = oper

From the manual: “The partial trace is an operation that reduces the dimension of a Hilbert space by eliminating some degrees of freedom by averaging (tracing). In this sense it is therefore the converse of the tensor product. It is useful when one is interested in only a part of a coupled quantum system.”

Superoperators and Tensor Manipulations#

QuTiP provides tools for working with superoperators, which are operators that act on other operators. Superoperators are particularly useful for describing the dynamics of open quantum systems. QuTiP includes functions for creating and manipulating superoperators, as well as for simulating the time evolution of quantum systems using master equations and quantum trajectories.

from qutip import spre, spost, to_super

# Create a quantum operator
A = sigmax()

# Create a superoperator that applies A from the left
super_left = spre(A)

# Create a superoperator that applies A from the right
super_right = spost(A)

# Convert an operator to a superoperator
super_A = to_super(A)

Example of superoperator application, Depolarizing channel.

A quantum depolarizing channel is a model for quantum noise in quantum systems, and it can represented as a superoperator acting on a qubit density matrix.

(Read more here)

import numpy as np
from qutip import spre, spost, to_super, Qobj

p = 0.1  # Depolarizing probability
I = Qobj(np.eye(2))  # Identity operator
X = Qobj([[0, 1], [1, 0]])  # Pauli-X operator
Y = Qobj([[0, -1j], [1j, 0]])  # Pauli-Y operator
Z = Qobj([[1, 0], [0, -1]])  # Pauli-Z operator

# Construct the depolarizing channel superoperator
depolarizing_channel = (1 - p) * to_super(I) + (p / 3) * (to_super(X) + to_super(Y) + to_super(Z))

print("\nDepolarizing channel superoperator:\n", depolarizing_channel)
Depolarizing channel superoperator:
 Quantum object: dims=[[[2], [2]], [[2], [2]]], shape=(4, 4), type='super', dtype=Dense, isherm=True
Qobj data =
[[0.93333333 0.         0.         0.06666667]
 [0.         0.86666667 0.         0.        ]
 [0.         0.         0.86666667 0.        ]
 [0.06666667 0.         0.         0.93333333]]

2.3 Exercises#

In pairs, choose a tutorial from the QuTiP Quantum Mechanics lectures and work through it.

Be prepared to discuss your findings and any challenges you encountered during the exercise.