Direct Fidelity Estimation with Qiskit Superstaq
This notebook demonstrates how to run a estimate the fidelity between two quantum states prepared in different devices using Superstaq. The direct fidelity estimation protocol is integrated into Superstaq following Cross-Platform Verification of Intermediate Scale Quantum Devices and can be accessed using qiskit-superstaq.
Imports and API Token
This example tutorial notebook uses qiskit-superstaq, our Superstaq client for Qiskit,
[1]:
try:
import qiskit
import qiskit_superstaq as qss
except ImportError:
print("Installing qiskit-superstaq...")
%pip install --quiet 'qiskit-superstaq[examples]'
print("Installed qiskit-superstaq.")
print("You may need to restart the kernel to import newly installed packages.")
import qiskit
import qiskit_superstaq as qss
[2]:
# Required imports
import numpy as np
To interface Superstaq via Qiskit, we must first instantiate a provider in qiskit-superstaq with SuperstaqProvider(). We then supply a Superstaq API token (or key) by either providing the API token as an argument of qss.SuperstaqProvider() or by setting it as an environment variable (see more details here).
[3]:
# Get the qiskit superstaq provider for Superstaq backend
provider = qss.SuperstaqProvider()
Randomized measurements background
The core idea behind this protocol is the use of random measurements to measure the overlap between two states \(\rho_1\) and \(\rho_2\), defined as \(\mathrm{Tr}(\rho_1 \rho_2)\). To do this, we measure both states in the same randomized Clifford product bases of the form \(C_1 \otimes \cdots \otimes C_N\) where each \(C_i\) is a one qubit Clifford and we are comparing \(N\)-qubit (sub)systems. We then compare the distributions over all random measurements to get estimate the overlap between the two states.
Succintly, the protocol goes as follows:
Apply the same random unitary \(U_j = C_1 \otimes \cdots \otimes C_N\) to both states.
Measure both states in the standard computational basis \(\left(|0\rangle^{\otimes N}, |1\rangle^{\otimes N}\right)\).
Repeat these measurementes for a fixed random basis to get an estimate of \(P_{U_j}^i(x) = \mathrm{Tr}\left( U_j \rho_i U_j^\dagger |x\rangle\langle x| \right)\) for all \(x \in \{0, 1\}^{\otimes N}\).
Repeat steps 1-3 for a collection of random unitaries \(U = \{U_1, \cdots U_M\}\).
With this data, we can calculate the overlap between the two states as:
Where \(M = |U|\) is the number of random unitaries, and \(\mathcal{D}(x, x')\) is the hamming distance between two bitstrings (i.e., the number of positions at which the bits are different). Fidelity is then computed as:
Where we compute the purities in the denominator with the same formula as the overlap but setting both \(\rho_i\) to be the same.
Running DFE
Identical states
To run a DFE protocol, we have to define circuits that prepare the states we want to compare.
[4]:
equal_superposition = qiskit.QuantumCircuit(1)
equal_superposition.h(0)
equal_superposition.draw("mpl")
[4]:
We also have to specify the target in which we want to prepare the states. These two pieces of information are what make up a state to be passed to submit_dfe, which is a tuple with the circuit that prepares the state as its first element and the target as its second element.
[5]:
target = "ss_unconstrained_simulator"
rho = (equal_superposition, target)
With this, we can run the protocol comparing the state we defined to itself.
[6]:
ids = provider.submit_dfe(
rho_1=rho,
rho_2=rho,
num_random_bases=50,
shots=1000,
)
result = provider.process_dfe(ids)
[7]:
print(result)
0.9994410259335388
As we can see above, we get a fidelity very close to 1, as expected for identical states.
Orthogonal states
To test our protocol is giving sensible results, we can run it on orthogonal states.
[9]:
state_1 = qiskit.QuantumCircuit(1)
state_1.id(0)
state_1.draw("mpl") # |0>
[9]:
[10]:
rho_1 = (state_1, target)
[11]:
state_2 = qiskit.QuantumCircuit(1)
state_2.x(0)
state_2.draw("mpl") # |1>
[11]:
[12]:
rho_2 = (state_2, target)
To get an idea of how many measurements and shots should be used depending on the number of qubits and any given information about the states, refer to Figure 2 and related text in the paper linked at the beginning of this tutorial.
[13]:
ids = provider.submit_dfe(
rho_1=rho_1,
rho_2=rho_2,
num_random_bases=50,
shots=1000,
)
result = provider.process_dfe(ids)
[14]:
print(result)
0.16255441673134413
We get a fidelity close to 0, expected for orthogonal states.
A more interesting example
Let’s say we want to compare how two different devices prepare two different states. To do this, we can simply set the target for each state to be whatever device we want (as long as you have access to it). We will set method="dry-run" for now to simulate the results, but if this argument is removed the circuits will be submitted to the real backend.
[15]:
state_1 = qiskit.QuantumCircuit(2)
state_1.h(0)
state_1.cx(0, 1)
state_1.draw("mpl")
[15]:
[16]:
state_2 = qiskit.QuantumCircuit(2)
state_2.h(0)
state_2.id(1)
state_2.draw("mpl")
[16]:
[19]:
rho_1 = (state_1, "ibmq_brisbane_qpu")
rho_2 = (state_2, "ibmq_kyiv_qpu")
[20]:
ids = provider.submit_dfe(
rho_1=rho_1,
rho_2=rho_2,
num_random_bases=50,
shots=5000,
method="dry-run", # Remove this argument to run on real devices
)
result = provider.process_dfe(ids)
[ ]:
print(result)
0.25785476714708405
We can see how our estimation compares to the ideal value by using the formula for fidelity between pure states.
[ ]:
np.trace(
qiskit.quantum_info.DensityMatrix(state_1).data
@ qiskit.quantum_info.DensityMatrix(state_2).data
)
(0.2499999999999999+0j)