Quantum Characterisation, Verification and Validation
To demonstrate how to implement new benchmarking experiments within the Superstaq QCVV framework, consider implementing a naive benchmarking routine where we try to estimate the fidelity of a single qubit Z gate by repeatedly applying the gate to a qubit in the ground state (such that the Z-gate should have no effect) and observing if any observations of the excited state occur. If the excited state is observed this indicates an error has occurred. Assuming that each time the Z-gate is applied the probability of a bit flip error is \(e\) then after \(d\) gates the probability of observing the ground state is
We can create an experiment to measure this as follows
[1]:
from __future__ import annotations
[2]:
try:
import supermarq
except ImportError:
print("Installing supermarq...")
%pip install --quiet supermarq
print("Installed supermarq.")
print("You may need to restart the kernel to import newly installed packages.")
[3]:
from supermarq.qcvv import QCVVExperiment, Sample, QCVVResults
from dataclasses import dataclass
from collections.abc import Sequence
from typing import Iterable
from tqdm.contrib.itertools import product
import cirq
from scipy.stats import linregress
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
@dataclass
class NaiveExperimentResult(QCVVResults):
gate_fidelity: float = 1
gate_error: float = 0
def _analyze(self) -> None:
"""To analyse the results to fit a simple exponential decay. This can be done easily
by fitting a linear model to the logarithm of the equation above.
"""
model = linregress(x=self.data["depth"], y=np.log(2 * self.data["0"] - 1))
self.gate_fidelity = np.exp(model.slope)
self.gate_error = 1 - np.exp(model.slope)
def plot_results(self) -> None:
"""Plot the data with the fit superimposed on top."""
fig, axs = plt.subplots(
1,
)
sns.scatterplot(self.data, x="depth", y="0", ax=axs)
x = np.linspace(0, max(self.data.depth))
y = 0.5 * self.gate_fidelity**x + 0.5
axs.plot(x, y)
axs.set_xlabel("Circuit depth")
axs.set_ylabel("Probability of ground state")
def print_results(self) -> None:
print(f"Estimated gate error: {self.gate_error}")
class NaiveExperiment(QCVVExperiment[NaiveExperimentResult]):
def __init__(
self,
num_circuits: int,
cycle_depths: Iterable[int],
*,
random_seed: int | np.random.Generator | None = None,
):
super().__init__(
num_qubits=1,
num_circuits=num_circuits,
cycle_depths=cycle_depths,
results_cls=NaiveExperimentResult,
random_seed=random_seed,
)
def _build_circuits(self, num_circuits: int, layers: Iterable[int]) -> Sequence[Sample]:
"""Build the circuits by composing multiple Z gates together into circuits. The
number of gates to compose is given by the `layers` parameter.
"""
samples = []
for index, depth in product(range(num_circuits), layers, desc="Building circuits."):
circuit = cirq.Circuit([cirq.Z(*self.qubits) for _ in range(depth)])
circuit += cirq.measure(*self.qubits)
samples.append(Sample(circuit_index=index, circuit=circuit, data={"depth": depth}))
return samples
To test this basic experiment, we use a depolarising noise model and a density matrix simulator. Note that if we use a single qubit depolarising channel with pauli error rate \(p\) this will result in an error with probability of \(4p/3\).
[4]:
noise = cirq.DepolarizingChannel(p=0.01 * 3 / 4)
simulator = cirq.DensityMatrixSimulator(noise=noise, seed=0)
experiment = NaiveExperiment(10, [10, 50, 100], random_seed=0)
results = experiment.run_with_simulator(simulator=simulator)
[5]:
results.analyze()
Estimated gate error: 0.0100782074092588
Checking this result we have
[6]:
pauli_error_rate = results.gate_error
print(pauli_error_rate)
0.0100782074092588
Which agrees very closely with our channel which we set up with \(p=0.01\)
[7]:
ss_results = experiment.run_on_device(target="ss_unconstrained_simulator", repetitions=1000)
[8]:
ss_results.analyze()
Estimated gate error: 0.0
As expected, since the Superstaq simulator is exact, we obtain a gate fidelity of 1.0