Average Gate Fidelity¶
Instead of performing process tomography on a system, it is possible to use a series of state tomographies and calculate an average gate fidelity from the measured density matices. To achieve this, the following equation can be used [Nie02]:
\(\begin{equation}\overline{F}(\mathcal{E}, U) = \frac{\sum_{jk}\alpha_{jk}tr(UU_j^{\dagger}U^{\dagger}\mathcal{E}(\rho_k))+d^2}{d^2(d+1)}\end{equation},\)
where \(\mathcal{E}(\rho_k) = \rho_k^\prime\) is the result from a single state tomography experiment with input \(k\). This sum can then be evaluated using the input basis \(\ket{0}\), \(\ket{1}\), \(\frac{\ket{0} + \ket{1}}{\sqrt{2}}\) & \(\frac{\ket{0} + i\ket{1}}{\sqrt{2}}\) to determine an estimate for gate fidelity.
This notebook demonstates how the included GateFidelity object in Lightworks can be used to automate the process of gathering and analysing data to find the average gate fidelities.
[1]:
import matplotlib.pyplot as plt
import numpy as np
import lightworks as lw
from lightworks import emulator, qubit
from lightworks.tomography import GateFidelity
We’ll define a run_experiments function which accepts the list of experiments and generates the results. In this example, an additional source_indistinguishability argument is included to allow for variation of this value, it defaults to a value of 1.
[2]:
def run_experiments(
experiments, source_indistinguishability: float = 1.0
) -> list:
"""
Experiment function which is required for performing state tomography on a
system. It takes a list of circuits and generates a corresponding list of
results for each of them. Only supports one qubit systems currently.
"""
# Post-select on 1 photon across each pair of qubit modes
post_select = lw.PostSelection()
post_select.add((0, 1), 1)
post_select.add((2, 3), 1)
backend = emulator.Backend("slos")
# Use a batch to run all tasks at once
batch = lw.Batch(
lw.Sampler,
task_args=[experiments.all_circuits, experiments.all_inputs, [20000]],
task_kwargs={
"source": [
emulator.Source(
indistinguishability=source_indistinguishability
)
],
"post_selection": [post_select],
"random_seed": [10],
},
)
return backend.run(batch)
For this tutorial, the 2 qubit CNOT gate is used, which is created and shown below. A 90% loss is also included on each of the output channels.
[3]:
n_qubits = 2
cnot = qubit.CNOT()
for i in range(4):
cnot.loss(i, 0.9)
cnot.display(display_loss=True)
Ideal System¶
To measure the average gate fidelity, a new GateFidelity object is created with the required configuration and the experiments are then generated.
[4]:
gate_f = GateFidelity(n_qubits, cnot)
experiments = gate_f.get_experiments()
We then run the experiments. Initially, we want to test an ideal system, so the photon indistinguishability is set to 1.
[5]:
results = run_experiments(experiments, 1.0)
The fidelity is then calculated with the process method. Unlike the other tomography approaches, the average gate fidelity requires knowledge of the expected unitary matrix for the gate. In the case of the CNOT gate, this is
\begin{equation} \text{U}_\text{cnot} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{bmatrix},\end{equation}
which is encoded within the array below.
As expected, the calculated fidelity with an ideal system is nearly 100%.
[6]:
u_cnot = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
avg_fidelity = gate_f.process(results, u_cnot)
print(f"Average gate fidelity = {avg_fidelity * 100} %")
Average gate fidelity = 99.844 %
Photon Distinguishability¶
It is then possible to use the built-in noise modelling in Lightworks to simulate the likely effect that this has on the gate fidelity. A key limitation for gate fidelity is the extent to which photons are indistinguishable from each other, with distinguishability reducing overall fidelity.
To examine this effect, we sweep the photon indistinguishability from 80 to 100% and calculate the fidelity at each point. This is then plotted to view the relationship between the two quantities.
Note
This cell may take a while to execute (~1 min).
[7]:
indistinguishability = range(80, 101, 2)
fidelities = []
for ind in indistinguishability:
# Re-run experiments with new value
results = run_experiments(experiments, ind / 100)
# And then calculate fidelity
fidelities.append(gate_f.process(results, u_cnot) * 100)
plt.plot(indistinguishability, fidelities)
plt.xlabel("Photon Indistinguishability (%)")
plt.ylabel("CNOT Fidelity (%)")
plt.show()

State Fidelities¶
With any of the process tomography algorithms, it is also possible to take the collected data and find the output state fidelity matrices for the input basis used by the algorithms. While the gate fidelity is not strictly an average of these output state fidelities, it can be a useful way to understand how a system behaves in a range of different configurations.
To demonstrate this we will re-run the experiments created above with a source indistinguishability of 80%.
[8]:
results = run_experiments(experiments, source_indistinguishability=0.8)
This data is then processed by the target process tomography algorithm. The return from this is a dictionary containing the input basis and the calculated state tomography fidelities.
[9]:
state_fidelities = gate_f.process_state_fidelities(results, u_cnot)
state_fidelities
[9]:
{'Z+,Z+': np.float64(1.00000000023185),
'Z+,Z-': np.float64(1.0000000002009133),
'Z+,X+': np.float64(1.000000010924441),
'Z+,Y+': np.float64(1.0000000034365901),
'Z-,Z+': np.float64(0.8433860329394077),
'Z-,Z-': np.float64(0.843386034015182),
'Z-,X+': np.float64(1.0000000006441527),
'Z-,Y+': np.float64(0.8433860393166331),
'X+,Z+': np.float64(0.8646531101531837),
'X+,Z-': np.float64(0.8646531096608464),
'X+,X+': np.float64(0.8433860370210113),
'X+,Y+': np.float64(0.8646531177873183),
'Y+,Z+': np.float64(0.864653115978776),
'Y+,Z-': np.float64(0.8646531098657455),
'Y+,X+': np.float64(0.8433860458498089),
'Y+,Y+': np.float64(0.8646531259003788)}
This data can then be plotted to view the individual fidelities.
As can be seen, despite the non-ideal photon indistinguishability some states are still generated with 100% fidelity. For the first 4 inputs, this occurs because there is no interference between photons when the first qubit is in the zero state (Z+), and so indistinguishability does not matter. The other case is Z-,X+ in which there is also no interference because the second qubit being input as X+ converts the gate from a CNOT to a CZ, we’re then doing a CZ gate on the state |10>, which has no effect and so again no interference occurs.
[10]:
x = range(len(state_fidelities))
plt.bar(x, state_fidelities.values())
plt.xlabel("Input")
plt.ylabel("Fidelity")
plt.xticks(x, labels=state_fidelities, rotation=90)
plt.show()
