The latest version of this notebook is available on https://github.com/Qiskit/qiskit-tutorial.
Christopher J. Wood$^{1}$ and Jay M. Gambetta$^{1}$
In this notebook we demonstrate how to design and run experiments to perform quantum state tomography using Qiskit, and demonstrate this using both simulators and the IBM Q Experience hardware.
The functions used in this notebook to implement state tomography may be imported from the qiskit.tools.qcvv.tomography
module.
The tomography experiments demonstrated in this notebook append a state preparation circuit with measurements of all qubits in each of the X, Y, and Z Pauli bases. For $n$-qubits this gives a total of $3^n$ measurement circuits which must be run, and the resulting counts for the $6^n$ measurement outcomes across all circuits give a tomographically overcomplete basis for reconstruction of the quantum state.
State reconstruction may be done using a variety of methods. In this notebook we implement two simple cases:
In [2]:
import numpy as np
from time import sleep # used for polling jobs
# importing the QISKit
from qiskit import Aer, IBMQ
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit import register, execute
# import tomography library
import qiskit.tools.qcvv.tomography as tomo
# useful additional packages
from qiskit.tools.visualization import plot_state, plot_histogram
from qiskit.tools.qi.qi import state_fidelity, concurrence, purity, outer
from qiskit.wrapper.jupyter import *
from qiskit.backends.ibmq import least_busy
In [3]:
# Load saved IBMQ accounts
IBMQ.load_accounts()
We will demonstrate preparation, measurement, and tomographic reconstruction of a 2-qubit Bell state $\frac{1}{\sqrt2}\left(|00\rangle+|11\rangle\right)$.
We begin with constructing a circuit to prepare the target state. This circuit requires a 2-qubit quantum register, and a 2-bit classical register to store measurement outcomes.
In [3]:
# Create a 2-qubit quantum register
qr = QuantumRegister(2)
cr = ClassicalRegister(2)
# quantum circuit to make an entangled Bell state
bell_circ = QuantumCircuit(qr, cr, name='bell')
bell_circ.h(qr[1])
bell_circ.cx(qr[1], qr[0])
Out[3]:
In [4]:
backend = Aer.get_backend('statevector_simulator')
job = execute(bell_circ, backend=backend)
bell_psi = job.result().get_statevector(bell_circ)
bell_rho = outer(bell_psi) # construct the density matrix from the state vector
We may visualize the final state using the plot_state
function. By default this creates a city plot, which is a 2D-bar plot of real and imaginary matrix elements of a density matrix $\rho$. Here, we instead ask it to plot the Pauli state vector.
In [5]:
# plot the state
plot_state(bell_rho,'paulivec')
We can compare the ideal entangled state to a non-entangled mixed state $\frac{1}{2}(|00\rangle\langle00| +|11\rangle\langle11|)$:
In [6]:
rho_mixed = np.array([[1,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,1]])/2
plot_state(rho_mixed, 'paulivec')
Next we must construct a family of circuits that implements a tomographically complete set of measurements of the qubit. The qiskit.tools.qcvv.tomography
module contains functions to generate these measurement circuits for general n-qubit systems.
A state tomography experiment is specified by a state_tomography_set
object:
tomo_set = tomo.state_tomography_set(qubits, meas_basis)
where
qubits
is a list of the qubit indexes within a register (eg. [0, 1] for the first 2 qubits)meas_basis
is the basis to measure each qubit in. The default value is meas_basis='Pauli'
.This contains information about which qubits are to be measured, and in what basis. The default option is to measure each single qubit in the X, Y, and Z bases. This results in $3^n$ measurement circuits that must be executed to gather count statistics for the tomographic reconstruction.
Once a state tomography set has been defined, the function to append the tomography circuits to a QuantumProgram
is:
tomo_circuits = tomo.create_tomography_circuits(circuit, qr, cr, tomo_set)
where
circuit
is the preparation circuit 'name'
, to be appended with measurements.qr
is the quantum register contained in circuit
to be measured.cr
is the classical register in circuit
to store the measurement results. tomo_set
is a state_tomography_set
, the dict of tomography configurations.The function output tomo_circuits
is a list of quantum tomography circuits used to execute the tomography experiments. The measured qubits will be qr[j]
, with corresponding measurement outcomes stored in classical bits cr[j]
, where j
corresponds to each qubit in the state tomography set.
In [7]:
# Construct state tomography set for measurement of qubits [0, 1] in the Pauli basis
bell_tomo_set = tomo.state_tomography_set([0, 1])
# Add the state tomography measurement circuits to the Quantum Program
bell_tomo_circuits = tomo.create_tomography_circuits(bell_circ, qr, cr, bell_tomo_set)
print('Created State tomography circuits:')
for circ in bell_tomo_circuits:
print(circ.name)
Now that we have prepared the required circuits for state preparation and measurement, we should test them on a simulator before trying to run them on the real device.
We specify the device, and a number of experiment shots to perform to gather measurement statistics. The larger the number of shots, the more accurate our measurement probabilities will be compared to the true value.
In [8]:
# Use the local simulator
backend = Aer.get_backend('qasm_simulator')
# Take 5000 shots for each measurement basis
shots = 5000
# Run the simulation
bell_tomo_job = execute(bell_tomo_circuits, backend=backend, shots=shots)
bell_tomo_result = bell_tomo_job.result()
print(bell_tomo_result)
Next we extract the tomography data from the output results using:
data = tomo.tomography_data(results, 'name', state_tomo_set)
where
results
is the Result object returned from execution of state_tomo_set
circuits.name
is the name of the original circuit used to generate tomography data.state_tomo_set
is the tomography set used for generation of tomography data.This returns a dictionary that stores the measurement basis, and measured counts along with measurement basis configuration corresponding to each outcome.
In [9]:
bell_tomo_data = tomo.tomography_data(bell_tomo_result, bell_circ.name, bell_tomo_set)
To reconstruct the maximum likelihood estimate of the measured quantum state, we use the following function:
tomo.fit_tomography_data(tomo_data, method, options)
where
QP
is the quantum program containing the measurement resultscircuits
is the array of tomographic measurement circuits measuredshots
is the total number of shots for each measurement circuittotal_qubits
is the total number of qubits in the system (the length of shot outcome bitstrings)meas_qubits
is an array of the measurement qubit indices
In [10]:
rho_fit = tomo.fit_tomography_data(bell_tomo_data)
We can compare the reconstructed state to the target state vector. We use the Fidelity function, which for comparing a density matrix $\rho$ to a pure state $|\psi\rangle$ is given by $F = \sqrt{\langle \psi| \rho |\psi\rangle}$. This may be done by the function state_fidelity
from the qiskit.tools.qi
module.
In [11]:
# calculate fidelity, concurrence and purity of fitted state
F_fit = state_fidelity(rho_fit, bell_psi)
con = concurrence(rho_fit)
pur = purity(rho_fit)
# plot
plot_state(rho_fit, 'paulivec')
print('Fidelity =', F_fit)
print('concurrence = ', str(con))
print('purity = ', str(pur))
Note that since our simulator is perfect, the output state should be exactly the Bell state, so we should obtain F = 1. Why is it not, in our case? Since we can never directly see the final state, we must obtain information about it via measurements. We would only obtain the true probabilities for the state in the limit of infinite measurement shots. Hence, we have statistical error in our reconstruction from imperfect information about the state itself. Try running with a different number of shots on the simulator and see how it affects the fidelity of the reconstruction.
Now that we've checked that our simple tomography experiment worked, let's try it out on the IBM Quantum Experience! To do this, we must attach our API key, and it is good practice to set a limit on the number of credits to use:
In [22]:
%%qiskit_job_status
# Register API token for online backends
# Use the IBM Quantum Experience (choose least busy device)
backend = least_busy(IBMQ.backends())
# Take 1000 shots for each measurement basis
# Note: reduce this number for larger number of qubits
shots = 1000
# set max credits to spend
max_credits = 8
# Run the experiment
job_on_device = execute(bell_tomo_circuits, backend=backend, shots=shots, max_credits=max_credits)
You can monitor the status of your job, and ask for the result.
Note: a call to job.result()
blocks until the job execution is done and result is available.
In [14]:
# Wait for the result and get it when ready
bell_tomo_result_device = job_on_device.result()
As before, we can extract the tomography data and reconstruct the measured density matrix for the 2-qubit Bell state prepared by our test circuit
In [15]:
bell_tomo_data = tomo.tomography_data(bell_tomo_result_device, 'bell', bell_tomo_set)
# Reconstruct experimentally measured density matrix
rho_fit_real = tomo.fit_tomography_data(bell_tomo_data)
Finally, we compare the fidelity of the experimentally measured state to the ideal state. Notice that the fidelity is lower than for the simulation. This is because in a real experiment, there is not only the statistical error in the reconstruction, but also experimental error in the device when implementing the gates in the preparation and measurement circuits, as well as error in the measurements themselves.
In [16]:
F_fit_real = state_fidelity(rho_fit_real, bell_psi)
plot_state(rho_fit_real, 'paulivec')
print('Fidelity with ideal state')
print('F =', F_fit_real)
# calculate concurrence and purity
con = concurrence(rho_fit_real)
pur = purity(rho_fit_real)
print('concurrence = ', str(con))
print('purity = ', str(pur))
In [ ]: