1) Introduction

Let’s try to do the tomography of a given one-qubit quantum state just to understand the principle behind this procedure:

$$ \rho = \begin{pmatrix} \rho_{11} & \rho_{12} \\
\rho_{21} & \rho_{22} \end{pmatrix} = \frac{1}{2} \sum_{i=0}^3 S_i \sigma_i $$

Where $\rho_{11} + \rho_{22} = 1$, $\rho_{12} = \rho_{21}^*$, and $\sigma_i$ are the pauli matrices.

$$ \sigma_0 = I \ \ \ \sigma_1 = \sigma_x = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix} \ \ \ \sigma_2 = \sigma_y = \begin{pmatrix} 0 & i \\ -i & 0 \end{pmatrix} \ \ \ \sigma_3 = \sigma_z = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix} $$

Our goal is to find $S_i$ an then characterize the state.

A problem is that the IBM quantum computers only measures on the Z axis, therefore we only can know $S_3$ that is associated as the expected value of the measurement along the Z axis. We need to do a trick to be able to measure $\big< X \big>$ and $\big< Y \big>$.

Let’s first check what are the eigenvectors of those operators.

  • X has as eigenvectors $\left| + \right> = \frac{1}{\sqrt{2}} \bigg( \left| 0 \right> + \left| 1 \right> \bigg)$ and $\left| - \right> = \frac{1}{\sqrt{2}} \bigg( \left| 0 \right> - \left| 1 \right> \bigg)$ ;

  • Y has as eigenvectors $\left| i^+ \right> = \frac{1}{\sqrt{2}} \bigg( \left| 0 \right> + i\left| 1 \right> \bigg)$ and $\left| i^- \right> = \frac{1}{\sqrt{2}} \bigg( \left| 0 \right> - i\left| 1 \right> \bigg)$ ;

Thus to measure on those bases we need only to add a way to generate those eigenvectors on the circuit:

  • To generate X, we only need to apply the gate $H$.
  • To generate Y, we need to apply the gate $S^{\dagger}H$.

2) Doing Quantum State Tomography by hand on qiskit

import qiskit as qsk
import numpy as np
import matplotlib.pyplot as plt

In order to test this, consider we have the state $\left| \psi \right> = \frac{1}{\sqrt{2}} \big( \left| 0 \right> + \left| 1 \right> \big)$ and we want to do the tomography of this state.

def measure_X(circuit,n):    
    circuit.barrier(n)
    circuit.h(n)
    circuit.measure(n,n)
    return circuit

def measure_Y(circuit,n):    
    circuit.barrier(n)
    circuit.sdg(n)
    circuit.h(n)    
    circuit.measure(n,n)    
    return circuit

def tomography(circuit):
    """ Tomography of a one qubit Circuit.
    """
    qc_list = []
    base = ['X', 'Y', 'Z']
    for basis in base:
        Q = qsk.QuantumCircuit(1,1)
        Q.append(circuit, [0])
        if basis == 'X':
            measure_X(Q, 0)
            qc_list.append(Q)
        if basis == 'Y':
            measure_Y(Q,  0)
            qc_list.append(Q)
        if basis == 'Z':
            Q.measure(0,0)
            qc_list.append(Q)
    return qc_list, base
qc = qsk.QuantumCircuit(1)
qc.h(0)
qcs, bases = tomography(qc)

Running tomography

backend_sim = qsk.Aer.get_backend('qasm_simulator')
job = qsk.execute(qcs, backend_sim, shots=5000)
result = job.result()

for index, circuit in enumerate(qcs):
    print(result.get_counts(circuit))
    print(f'Base measured {bases[index]}\n')
{'0': 5000}
Base measured X

{'0': 2484, '1': 2516}
Base measured Y

{'0': 2503, '1': 2497}
Base measured Z

Thus we have: X = 1, Y = 0 and Z = 0, because 0 is equivalent to +1 and 1 equivalent to -1. It is not exactly 0 because of statistical fluctiations.

def get_density_matrix(measurements,circuits):
    """Get density matrix from tomography measurements.

    """
    density_matrix = np.eye(2, dtype=np.complex128)
    sigma_x = np.array([[0,1],[1,0]])
    sigma_y = np.array([[0,-1j],[1j,0]])
    sigma_z = np.array([[1,0],[0,-1]])
    basis = [sigma_x, sigma_y, sigma_z]

    for index in range(len(circuits)):
        R = measurements.get_counts(index)
        
        if '0' in R.keys() and '1' in R.keys():
            zero = R['0']
            one = R['1']
        
        elif '1' in R.keys():
            zero = 0
            one = R['1']
        
        elif '0' in R.keys():
            zero = R['0']
            one = 0

        total = sum(list(R.values()))
        expected = (zero - one)/total        
        density_matrix += expected * basis[index]

    return 0.5*density_matrix

density = get_density_matrix(result,qcs)
print(density)
[[0.5006+0.j     0.5   +0.0032j]
 [0.5   -0.0032j 0.4994+0.j    ]]

Writing the density matrix: $$ \rho = \frac{1}{2} \begin{pmatrix} 1 & 1 \\
1 & 1 \end{pmatrix} $$

3) Doing Quantum State Tomography using Ignis

Qiskit has a module inside ignis to do tomography that does exactly what we’ve done before, but is generalized for multiple qubits as input.

from qiskit.ignis.verification.tomography import state_tomography_circuits

tomography_circuits = state_tomography_circuits(qc, [0], meas_labels='Pauli', meas_basis='Pauli')

As a check, let’s see how they measure the Y basis, and we can verify that it is the same way as we did before.

tomography_circuits[1].draw('mpl')

tomography circuit

backend_sim = qsk.Aer.get_backend('qasm_simulator')
job = qsk.execute(tomography_circuits, backend_sim, shots=5000)
result = job.result()

They also has a function to transform the results to a density matrix which treats the state tomography as an optimization problem. The fit method has several methods for fitting the tomography results, such as cvx which is convex optimization and lstsq which makes uses the least square optimization.

from qiskit.ignis.verification.tomography import StateTomographyFitter

state_fitter = StateTomographyFitter(result, tomography_circuits, meas_basis='Pauli')
density_matrix = state_fitter.fit(method='lstsq')
print(density_matrix)
[[0.49040221+0.j         0.49988484+0.00479889j]
 [0.49988484-0.00479889j 0.50959779+0.j        ]]

Which is approximately what we expect, as shown before.

4) State tomography of 3 qubits

For this example, let’s use the GHZ state:

$$ \left| \psi \right> = \frac{1}{\sqrt{2}} \bigg( \left| 000 \right> + \left| 111 \right> \bigg) $$

This state is simply constructed using an H gate and two CNOT gates:

qc = qsk.QuantumCircuit(3)
qc.h(0)
qc.cx(0,1)
qc.cx(0,2)
qc.draw('mpl')

tomography circuit

In order to indicate which qubits we want to do tomography, we need to put an list after choosing the circuit that we want to do tomography. In this case, we want to do tomography of all qubits of qc, therefore we need to put [0,1,2].

from qiskit.ignis.verification.tomography import state_tomography_circuits

tomography_circuits = state_tomography_circuits(qc, [0,1,2], 
                                                meas_labels='Pauli', 
                                                meas_basis='Pauli')
backend_sim = qsk.Aer.get_backend('qasm_simulator')
job = qsk.execute(tomography_circuits, backend_sim, shots=5000)
result = job.result()
from qiskit.ignis.verification.tomography import StateTomographyFitter

state_fitter = StateTomographyFitter(result, tomography_circuits, meas_basis='Pauli')
density_matrix = state_fitter.fit(method='lstsq')

Let’s define a helper function that plots the real and imaginary part as a grey scale image:

def plot_density_matrix(DM):
    """Helper function to plot density matrices.

    Parameters
    ----------------------------------------------
    DM(np.array): Density Matrix.
    
    """
    from matplotlib.ticker import MaxNLocator
    
    fig = plt.figure(figsize=(16,10))
    gs = fig.add_gridspec(1, 2)
    ax1 = fig.add_subplot(gs[0, 0])
    ax2 = fig.add_subplot(gs[0, 1])

    im = ax1.imshow(np.real(DM), cmap='Greys')
    ax1.yaxis.set_major_locator(MaxNLocator(integer=True))
    ax1.xaxis.set_major_locator(MaxNLocator(integer=True))
    ax1.set_title('Real Part',size=16)
    plt.colorbar(im, ax=ax1)

    im = ax2.imshow(np.imag(DM), cmap='Greys')
    plt.colorbar(im, ax=ax2)
    ax2.yaxis.set_major_locator(MaxNLocator(integer=True))
    ax2.xaxis.set_major_locator(MaxNLocator(integer=True))
    ax2.set_title('Imaginary Part',size=16)
    plt.show()

Ploting the density matrix, we see that there are states only on the first and last column (row), each column (row) represents a quantum state on the computational basis, for this case the states represented are $\left| 000 \right>$ and $\left| 111 \right>$ respectively. We can ignore the imaginary part, since is is only random noise.

plot_density_matrix(density_matrix)

Density matrix

The code for this post is on github.

References

1 - Altepeter et al - Quantum State Tomography

2 - Qiskit Ignis Documentation


Version Information

Qiskit SoftwareVersion
Qiskit0.19.1
Terra0.14.1
Aer0.5.1
Ignis0.3.0
Aqua0.7.0
IBM Q Provider0.7.0
System information
Python3.7.3 | packaged by conda-forge | (default, Jul 1 2019, 21:52:21) [GCC 7.3.0]
OSLinux
CPUs2
Memory (Gb)7.664028167724609
Sat May 23 17:14:48 2020 -03