Quantum State Tomography

Quantum tomography is an experimental procedure to reconstruct a description of part of a quantum system from the measurement outcomes of a specific set of experiments. In particular, quantum state tomography reconstructs the density matrix of a quantum state by preparing the state many times and measuring them in a tomographically complete basis of measurement operators.

Note

This tutorial requires the qiskit-aer and qiskit-ibm-runtime packages to run simulations. You can install them with python -m pip install qiskit-aer qiskit-ibm-runtime.

We first initialize a simulator to run the experiments on.

from qiskit_aer import AerSimulator
from qiskit_ibm_runtime.fake_provider import FakePerth

backend = AerSimulator.from_backend(FakePerth())

To run a state tomography experiment, we initialize the experiment with a circuit to prepare the state to be measured. We can also pass in an Operator or a Statevector to describe the preparation circuit.

import qiskit
from qiskit_experiments.framework import ParallelExperiment
from qiskit_experiments.library import StateTomography

# GHZ State preparation circuit
nq = 2
qc_ghz = qiskit.QuantumCircuit(nq)
qc_ghz.h(0)
qc_ghz.s(0)
for i in range(1, nq):
    qc_ghz.cx(0, i)

# QST Experiment
qstexp1 = StateTomography(qc_ghz)
qstdata1 = qstexp1.run(backend, seed_simulation=100).block_for_results()

# Print results
display(qstdata1.analysis_results(dataframe=True))
name experiment components value quality backend run_time trace eigvals raw_eigvals rescaled_psd fitter_metadata conditional_probability positive
f5e31068 state StateTomography [Q0, Q1] DensityMatrix([[ 0.45296224+0.j , 0.00... None aer_simulator_from(fake_perth) None 1.0 [0.9026801763165022, 0.04617323637875837, 0.03... [0.9026801763165022, 0.04617323637875837, 0.03... False {'fitter': 'linear_inversion', 'fitter_time': ... 1.0 True
104fdc05 state_fidelity StateTomography [Q0, Q1] 0.902344 None aer_simulator_from(fake_perth) None None None None None None None None
5581b5a3 positive StateTomography [Q0, Q1] True None aer_simulator_from(fake_perth) None None None None None None None None

Tomography Results

The main result for tomography is the fitted state, which is stored as a DensityMatrix object:

state_result = qstdata1.analysis_results("state", dataframe=True).iloc[0]
print(state_result.value)
DensityMatrix([[ 0.45296224+0.j        ,  0.0061849 -0.00390625j,
                 0.00423177+0.00244141j, -0.00341797-0.43896484j],
               [ 0.0061849 +0.00390625j,  0.03499349+0.j        ,
                -0.00146484-0.00927734j, -0.00651042-0.00927734j],
               [ 0.00423177-0.00244141j, -0.00146484+0.00927734j,
                 0.0382487 +0.j        , -0.00748698+0.00390625j],
               [-0.00341797+0.43896484j, -0.00651042+0.00927734j,
                -0.00748698-0.00390625j,  0.47379557+0.j        ]],
              dims=(2, 2))

We can also visualize the density matrix:

from qiskit.visualization import plot_state_city
state = qstdata1.analysis_results("state", dataframe=True).iloc[0].value
plot_state_city(state, title='Density Matrix')
../../_images/state_tomography_3_0.png

The state fidelity of the fitted state with the ideal state prepared by the input circuit is stored in the "state_fidelity" result field. Note that if the input circuit contained any measurements the ideal state cannot be automatically generated and this field will be set to None.

fid_result = qstdata1.analysis_results("state_fidelity", dataframe=True).iloc[0]
print("State Fidelity = {:.5f}".format(fid_result.value))
State Fidelity = 0.90234

Additional state metadata

Additional data is stored in the tomography under additional fields. This includes

  • eigvals: the eigenvalues of the fitted state

  • trace: the trace of the fitted state

  • positive: Whether the eigenvalues are all non-negative

If trace rescaling was performed this dictionary will also contain a raw_trace field containing the trace before rescaling. Futhermore, if the state was rescaled to be positive or trace 1 an additional field raw_eigvals will contain the state eigenvalues before rescaling was performed.

for col in ["eigvals", "trace", "positive"]:
    print(f"{col}: {state_result[col]}")
eigvals: [0.90268018 0.04617324 0.03575777 0.01538882]
trace: 1.0000000000000016
positive: True

To see the effect of rescaling, we can perform a “bad” fit with very low counts:

# QST Experiment
bad_data = qstexp1.run(backend, shots=10, seed_simulation=100).block_for_results()
bad_state_result = bad_data.analysis_results("state", dataframe=True).iloc[0]

# Print result
for key, val in bad_state_result.items():
    print(f"{key}: {val}")
name: state
experiment: StateTomography
components: [<Qubit(Q0)>, <Qubit(Q1)>]
value: DensityMatrix([[ 0.52608502+0.00000000e+00j,  0.01894386-2.42367606e-02j,
                 0.050487  -7.50249597e-03j, -0.17076234-4.04234257e-01j],
               [ 0.01894386+2.42367606e-02j,  0.00587836+0.00000000e+00j,
                -0.01059066+5.39396966e-03j,  0.01513751-1.50829029e-02j],
               [ 0.050487  +7.50249597e-03j, -0.01059066-5.39396966e-03j,
                 0.07127845+0.00000000e+00j, -0.00194624-8.22724210e-02j],
               [-0.17076234+4.04234257e-01j,  0.01513751+1.50829029e-02j,
                -0.00194624+8.22724210e-02j,  0.39675817+2.77555756e-17j]],
              dims=(2, 2))
quality: None
backend: aer_simulator_from(fake_perth)
run_time: None
trace: 1.0000000000000078
eigvals: [0.91643275 0.08006585 0.0035014  0.        ]
raw_eigvals: [ 1.01243858  0.17607168  0.09950723 -0.2880175 ]
rescaled_psd: True
fitter_metadata: {'fitter': 'linear_inversion', 'fitter_time': 0.002674102783203125}
conditional_probability: 1.0
positive: True

Tomography Fitters

The default fitters is linear_inversion, which reconstructs the state using dual basis of the tomography basis. This will typically result in a non-positive reconstructed state. This state is rescaled to be positive-semidefinite (PSD) by computing its eigen-decomposition and rescaling its eigenvalues using the approach from Ref. [1].

There are several other fitters are included (See API documentation for details). For example, if cvxpy is installed we can use the cvxpy_gaussian_lstsq() fitter, which allows constraining the fit to be PSD without requiring rescaling.

try:
    import cvxpy

    # Set analysis option for cvxpy fitter
    qstexp1.analysis.set_options(fitter='cvxpy_gaussian_lstsq')

    # Re-run experiment
    qstdata2 = qstexp1.run(backend, seed_simulation=100).block_for_results()

    state_result2 = qstdata2.analysis_results("state", dataframe=True).iloc[0]
    for key, val in state_result2.items():
        print(f"{key}: {val}")

except ModuleNotFoundError:
    print("CVXPY is not installed")
name: state
experiment: StateTomography
components: [<Qubit(Q0)>, <Qubit(Q1)>]
value: DensityMatrix([[ 4.78684854e-01+0.00000000e+00j,
                 9.07479966e-03+2.89547071e-03j,
                 6.87761024e-03-3.32902566e-04j,
                -1.36951123e-03-4.43468037e-01j],
               [ 9.07479966e-03-2.89547071e-03j,
                 1.90969523e-02+0.00000000e+00j,
                 2.32438995e-05+9.86720269e-03j,
                -3.83833603e-03-9.63987006e-04j],
               [ 6.87761024e-03+3.32902566e-04j,
                 2.32438995e-05-9.86720269e-03j,
                 2.79374312e-02+0.00000000e+00j,
                -8.42416422e-03-9.69110606e-03j],
               [-1.36951123e-03+4.43468037e-01j,
                -3.83833603e-03+9.63987006e-04j,
                -8.42416422e-03+9.69110606e-03j,
                 4.74280762e-01+0.00000000e+00j]],
              dims=(2, 2))
quality: None
backend: aer_simulator_from(fake_perth)
run_time: None
trace: 0.9999999975423799
eigvals: [0.92022989 0.03758286 0.0326018  0.00958544]
raw_eigvals: [0.9202299  0.03758286 0.0326018  0.00958544]
rescaled_psd: False
fitter_metadata: {'fitter': 'cvxpy_gaussian_lstsq', 'cvxpy_solver': 'SCS', 'cvxpy_status': ['optimal'], 'psd_constraint': True, 'trace_preserving': True, 'fitter_time': 0.030614376068115234}
conditional_probability: 1.0
positive: True

Parallel Tomography Experiment

We can also use the ParallelExperiment class to run subsystem tomography on multiple qubits in parallel.

For example if we want to perform 1-qubit QST on several qubits at once:

from math import pi
num_qubits = 5
gates = [qiskit.circuit.library.RXGate(i * pi / (num_qubits - 1))
         for i in range(num_qubits)]

subexps = [
    StateTomography(gate, physical_qubits=(i,))
    for i, gate in enumerate(gates)
]
parexp = ParallelExperiment(subexps)
pardata = parexp.run(backend, seed_simulation=100).block_for_results()

display(pardata.analysis_results(dataframe=True))
name experiment components value quality backend run_time trace eigvals raw_eigvals rescaled_psd fitter_metadata conditional_probability positive
2eed1bd9 state StateTomography [Q0] DensityMatrix([[0.97167969+0.j , 0.0351... None aer_simulator_from(fake_perth) None 1.0 [0.9731331933142322, 0.02686680668576869] [0.9731331933142322, 0.02686680668576869] False {'fitter': 'linear_inversion', 'fitter_time': ... 1.0 True
235dde1f state_fidelity StateTomography [Q0] 0.97168 None aer_simulator_from(fake_perth) None None None None None None None None
e96ccdf2 positive StateTomography [Q0] True None aer_simulator_from(fake_perth) None None None None None None None None
993e54e0 state StateTomography [Q1] DensityMatrix([[ 0.82128906+0.j , -0.00... None aer_simulator_from(fake_perth) None 1.0 [0.9571460025542904, 0.04285399744571043] [0.9571460025542904, 0.04285399744571043] False {'fitter': 'linear_inversion', 'fitter_time': ... 1.0 True
74b2432c state_fidelity StateTomography [Q1] 0.957133 None aer_simulator_from(fake_perth) None None None None None None None None
0a8dec01 positive StateTomography [Q1] True None aer_simulator_from(fake_perth) None None None None None None None None
a97439e5 state StateTomography [Q2] DensityMatrix([[ 0.46875 +0.j , -0.00... None aer_simulator_from(fake_perth) None 1.0 [0.9650211797013135, 0.034978820298687624] [0.9650211797013135, 0.034978820298687624] False {'fitter': 'linear_inversion', 'fitter_time': ... 1.0 True
fc80a030 state_fidelity StateTomography [Q2] 0.963867 None aer_simulator_from(fake_perth) None None None None None None None None
53b99718 positive StateTomography [Q2] True None aer_simulator_from(fake_perth) None None None None None None None None
76239067 state StateTomography [Q3] DensityMatrix([[ 0.1796875 +0.j , -0.00... None aer_simulator_from(fake_perth) None 1.0 [0.9578505776880251, 0.0421494223119762] [0.9578505776880251, 0.0421494223119762] False {'fitter': 'linear_inversion', 'fitter_time': ... 1.0 True
9c53f160 state_fidelity StateTomography [Q3] 0.957824 None aer_simulator_from(fake_perth) None None None None None None None None
15d131be positive StateTomography [Q3] True None aer_simulator_from(fake_perth) None None None None None None None None
fc977395 state StateTomography [Q4] DensityMatrix([[ 0.03027344+0.j , -0.02... None aer_simulator_from(fake_perth) None 1.0 [0.9705835720840978, 0.029416427915902703] [0.9705835720840978, 0.029416427915902703] False {'fitter': 'linear_inversion', 'fitter_time': ... 1.0 True
f50978e5 state_fidelity StateTomography [Q4] 0.969727 None aer_simulator_from(fake_perth) None None None None None None None None
c54db909 positive StateTomography [Q4] True None aer_simulator_from(fake_perth) None None None None None None None None

View experiment analysis results for one component:

results = pardata.analysis_results(dataframe=True)
display(results[results.components.apply(lambda x: x == ["Q0"])])
name experiment components value quality backend run_time trace eigvals raw_eigvals rescaled_psd fitter_metadata conditional_probability positive
2eed1bd9 state StateTomography [Q0] DensityMatrix([[0.97167969+0.j , 0.0351... None aer_simulator_from(fake_perth) None 1.0 [0.9731331933142322, 0.02686680668576869] [0.9731331933142322, 0.02686680668576869] False {'fitter': 'linear_inversion', 'fitter_time': ... 1.0 True
235dde1f state_fidelity StateTomography [Q0] 0.97168 None aer_simulator_from(fake_perth) None None None None None None None None
e96ccdf2 positive StateTomography [Q0] True None aer_simulator_from(fake_perth) None None None None None None None None

References

See also