diff --git a/docs/source/apps/supermarq/qcvv/qcvv.rst b/docs/source/apps/supermarq/qcvv/qcvv.rst index be220eb31..363104316 100644 --- a/docs/source/apps/supermarq/qcvv/qcvv.rst +++ b/docs/source/apps/supermarq/qcvv/qcvv.rst @@ -11,14 +11,14 @@ For a demonstration of how to implement a new experiment take a look at the foll qcvv_css - Alternatively for pre-build experiments that can be used out of the box see .. toctree:: :maxdepth: 1 - + + qcvv_irb_css qcvv_xeb_css .. note:: - At present the QCVV library is only available in :code:`cirq-superstaq`. \ No newline at end of file + At present the QCVV library is only available in :code:`cirq-superstaq`. diff --git a/docs/source/apps/supermarq/qcvv/qcvv_irb_css.ipynb b/docs/source/apps/supermarq/qcvv/qcvv_irb_css.ipynb new file mode 100644 index 000000000..793bf499c --- /dev/null +++ b/docs/source/apps/supermarq/qcvv/qcvv_irb_css.ipynb @@ -0,0 +1,188 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Interleaved Randomized Benchmarking (IRB)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The interleaved randomized benchmarking routine allows us to estimate the gate fidelity of single\n", + "qubit Clifford gates. To demonstrate this routine, consider device noise modelled by an amplitude \n", + "damping channel with decay probability $\\gamma=0.01$" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import cirq\n", + "import numpy as np\n", + "import supermarq" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "decay_prob = 0.025\n", + "noise = cirq.AmplitudeDampingChannel(gamma=decay_prob)\n", + "simulator = cirq.DensityMatrixSimulator(noise=noise)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can calculate the average fidelity of this channel [as](https://quantumcomputing.stackexchange.com/questions/16074/how-to-calculate-the-average-fidelity-of-an-amplitude-damping-channel):\n", + "$$\\begin{align}\n", + "\\overline{F} &= \\int\\langle\\psi|\\mathcal{N_\\gamma}(|\\psi\\rangle\\langle\\psi|)|\\psi\\rangle d\\psi\\\\\n", + "&=\\int\\langle\\psi|K_0|\\psi\\rangle\\langle\\psi|K_0^\\dagger|\\psi\\rangle + \\langle\\psi|K_1|\\psi\\rangle\\langle\\psi|K_1^\\dagger|\\psi\\rangle d\\psi\\\\\n", + "& =\\frac{1}{4\\pi}\\int_0^\\pi\\int_0^{2\\pi}\\left|\\begin{pmatrix}\\cos\\frac{\\theta}{2}&e^{-i\\phi}\\sin\\frac{\\theta}{2}\\end{pmatrix}\\begin{pmatrix}1 & 0 \\\\0 & \\sqrt{1 - \\gamma}\\end{pmatrix}\\begin{pmatrix}\\cos\\frac{\\theta}{2}\\\\e^{i\\phi}\\sin\\frac{\\theta}{2}\\end{pmatrix}\\right|^2\\sin\\theta \\\\\n", + "& + \\left|\\begin{pmatrix}\\cos\\frac{\\theta}{2}&e^{-i\\phi}\\sin\\frac{\\theta}{2}\\end{pmatrix}\\begin{pmatrix}0 & \\sqrt{\\gamma} \\\\0 & 0\\end{pmatrix}\\begin{pmatrix}\\cos\\frac{\\theta}{2}\\\\e^{i\\phi}\\sin\\frac{\\theta}{2}\\end{pmatrix}\\right|^2\\sin\\theta d\\phi d\\theta \\\\\n", + "&=\\frac{1}{4\\pi}\\int_0^\\pi\\int_0^{2\\pi}\\left|\\cos^2\\frac{\\theta}{2}+\\sqrt{1-\\gamma}\\sin^2\\frac{\\theta}{2}\\right|^2\\sin\\theta + \\left|\\sqrt{\\gamma}e^{i\\phi}\\sin\\frac{\\theta}{2}\\cos\\frac{\\theta}{2}\\right|^2\\sin\\theta d\\phi d\\theta \\\\\n", + "&=\\frac{1}{2}\\int_0^\\pi\\left(\\cos^4\\frac{\\theta}{2}+(1-\\gamma)\\sin^4\\frac{\\theta}{2}+\\frac{\\sqrt{1-\\gamma}}{2}\\sin^2\\theta + \\frac{\\gamma}{4}\\sin^2\\theta\\right)\\sin\\theta d\\theta \\\\\n", + "&=\\frac{1}{2}\\int_0^\\pi\\sin\\theta\\cos^4\\frac{\\theta}{2}+(1-\\gamma)\\sin\\theta\\sin^4\\frac{\\theta}{2}+\\frac{\\gamma+2\\sqrt{1-\\gamma}}{4}\\sin^3\\theta d\\theta \\\\\n", + "&=\\frac{1}{2}\\left(\\frac{2}{3} + (1-\\gamma)\\frac{2}{3} + \\frac{\\gamma+2\\sqrt{1-\\gamma}}{4}\\frac{4}{3}\\right) \\\\\n", + "&=\\frac{1}{2}\\left(\\frac{4}{3} - \\frac{\\gamma}{3} + \\frac{2\\sqrt{1-\\gamma}}{3}\\right) \\\\\n", + "&=\\frac{2}{3}-\\frac{\\gamma}{6} + \\frac{\\sqrt{1-\\gamma}}{3}.\n", + "\\end{align}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Thus we have a gate error $$\\frac{1}{3}+\\frac{\\gamma}{6} - \\frac{\\sqrt{1-\\gamma}}{3}$$" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "expected_gate_error = 1 / 3 + decay_prob / 6 - np.sqrt(1 - decay_prob) / 3" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3707426ff75d42b18b4f7329a12bd870", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Building circuits: 0%| | 0/400 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "if experiment.collect_data():\n", + " results = experiment.analyze_results(plot_results=True)\n", + " print(results)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Expected gate error: 0.008360\n", + "Measured gate error: 0.008280 +/- 0.000297\n" + ] + } + ], + "source": [ + "print(f\"Expected gate error: {expected_gate_error:.6f}\")\n", + "print(\n", + " f\"Measured gate error: {results.average_interleaved_gate_error:.6f} +/- {results.average_interleaved_gate_error_std:.6f}\"\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "client_superstaq", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/supermarq-benchmarks/supermarq/qcvv/__init__.py b/supermarq-benchmarks/supermarq/qcvv/__init__.py index 14297c050..15a1c194a 100644 --- a/supermarq-benchmarks/supermarq/qcvv/__init__.py +++ b/supermarq-benchmarks/supermarq/qcvv/__init__.py @@ -1,12 +1,15 @@ """A toolkit of QCVV routines.""" from .base_experiment import BenchmarkingExperiment, BenchmarkingResults, Sample +from .irb import IRB, IRBResults from .xeb import XEB, XEBResults, XEBSample __all__ = [ "BenchmarkingExperiment", "BenchmarkingResults", "Sample", + "IRB", + "IRBResults", "XEB", "XEBResults", "XEBSample", diff --git a/supermarq-benchmarks/supermarq/qcvv/base_experiment.py b/supermarq-benchmarks/supermarq/qcvv/base_experiment.py index 3d7edd2fd..a812ab25a 100644 --- a/supermarq-benchmarks/supermarq/qcvv/base_experiment.py +++ b/supermarq-benchmarks/supermarq/qcvv/base_experiment.py @@ -345,7 +345,7 @@ def _retrieve_jobs(self) -> dict[str, str]: return statuses - def _run_check(self) -> None: + def _has_raw_data(self) -> None: """Checks if any of the samples already have probabilities stored. If so raises a runtime error to prevent them from being overwritten. @@ -523,7 +523,7 @@ def run_on_device( The superstaq job containing all the circuits submitted as part of the experiment. """ if not overwrite: - self._run_check() + self._has_raw_data() experiment_job = self._service.create_job( [sample.circuit for sample in self.samples], @@ -556,7 +556,7 @@ def run_with_simulator( be over written in the process. Defaults to False. """ if not overwrite: - self._run_check() + self._has_raw_data() if simulator is None: simulator = cirq.Simulator() @@ -593,7 +593,7 @@ def run_with_callable( RuntimeError: If the returned probabilities dictionary values do not sum to 1.0. """ if not overwrite: - self._run_check() + self._has_raw_data() for sample in tqdm(self.samples, desc="Running circuits"): probability = circuit_eval_func(sample.circuit, **kwargs) if not all(len(key) == self.num_qubits for key in probability.keys()): diff --git a/supermarq-benchmarks/supermarq/qcvv/irb.py b/supermarq-benchmarks/supermarq/qcvv/irb.py new file mode 100644 index 000000000..dbd8824c3 --- /dev/null +++ b/supermarq-benchmarks/supermarq/qcvv/irb.py @@ -0,0 +1,292 @@ +# Copyright 2021 The Cirq Developers +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tooling for interleaved randomised benchmarking +""" + +from __future__ import annotations + +import random +from collections.abc import Iterable, Sequence +from dataclasses import dataclass + +import cirq +import numpy as np +import pandas as pd +import seaborn as sns +from scipy.stats import linregress +from tqdm.contrib.itertools import product + +from supermarq.qcvv.base_experiment import BenchmarkingExperiment, BenchmarkingResults, Sample + + +@dataclass(frozen=True) +class IRBResults(BenchmarkingResults): + """Data structure for the IRB experiment results.""" + + rb_layer_fidelity: float + """Layer fidelity estimate without the interleaving gate.""" + rb_layer_fidelity_std: float + """Standard deviation of the layer fidelity estimate without the interleaving gate.""" + irb_layer_fidelity: float + """Layer fidelity estimate with the interleaving gate.""" + irb_layer_fidelity_std: float + """Standard deviation of the layer fidelity estimate with the interleaving gate.""" + average_interleaved_gate_error: float + """Estimate of the interleaving gate error.""" + average_interleaved_gate_error_std: float + """Standard deviation of the estimate for the interleaving gate error.""" + + experiment_name = "IRB" + + +class IRB(BenchmarkingExperiment[IRBResults]): + r"""Interleaved random benchmarking (IRB) experiment. + + IRB estimates the gate error of specified Clifford gate, :math:`\mathcal{C}^*`. + This is achieved by first choosing a random sequence, :math:`\{\mathcal{C_i}\}_m` + of :math:`m` Clifford gates and then using this to generate two circuits. The first + is generated by appending to this sequence the single gate that corresponds to the + inverse of the original sequence. The second circuit it obtained by inserting the + interleaving gate, :math:`\mathcal{C}^*` after each gate in the sequence and then + again appending the corresponding inverse element of the new circuit. Thus both + circuits correspond to the identity operation. + + We run both circuits on the specified target and calculate the probability of measuring + the resulting state in the ground state, :math:`p(0...0)`. This gives the circuit fidelity + + .. math:: + + f(m) = 2p(0...0) - 1 + + We can then fit an exponential decay :math:`\log(f) \sim m` to this circuit fidelity + for each circuit, with decay rates :math:`\alpha` and :math:`\tilde{\alpha}` for the circuit + without and with interleaving respectively. Finally the gate error of the + specified gate, :math:`\mathcal{C}^*` is estimated as + + .. math:: + + e_{\mathcal{C}^*} = \frac{1}{2} \left(1 - \frac{\tilde{\alpha}}{\alpha}\right) + + For more details see: https://arxiv.org/abs/1203.4550 + """ + + def __init__( + self, + interleaved_gate: cirq.ops.SingleQubitCliffordGate = cirq.ops.SingleQubitCliffordGate.Z, + num_qubits: int = 1, + ) -> None: + """Constructs an IRB experiment. + + Args: + interleaved_gate: The single qubit Clifford gate to measure the gate error of. + num_qubits: The number of qubits to experiment on + """ + if num_qubits != 1: + raise NotImplementedError( + "IRB experiment is currently only implemented for single qubit use" + ) + super().__init__(num_qubits=1) + + self.interleaved_gate = interleaved_gate + """The gate being interleaved""" + + @staticmethod + def _reduce_clifford_seq( + gate_seq: list[cirq.ops.SingleQubitCliffordGate], + ) -> cirq.ops.SingleQubitCliffordGate: + """Reduces a list of single qubit clifford gates to a single gate. + + Args: + gate_seq: The list of gates. + The single reduced gate. + Returns: + The single reduced gate + """ + cur = gate_seq[0] + for gate in gate_seq[1:]: + cur = cur.merged_with(gate) + return cur + + @classmethod + def _random_single_qubit_clifford(cls) -> cirq.ops.SingleQubitCliffordGate: + """Choose a random single qubit clifford gate. + + Returns: + The random clifford gate. + """ + Id = cirq.ops.SingleQubitCliffordGate.I + H = cirq.ops.SingleQubitCliffordGate.H + S = cirq.ops.SingleQubitCliffordGate.Z_sqrt + X = cirq.ops.SingleQubitCliffordGate.X + Y = cirq.ops.SingleQubitCliffordGate.Y + Z = cirq.ops.SingleQubitCliffordGate.Z + + set_A = [ + Id, + S, + H, + cls._reduce_clifford_seq([H, S]), + cls._reduce_clifford_seq([S, H]), + cls._reduce_clifford_seq([H, S, H]), + ] + + set_B = [Id, X, Y, Z] + + return cls._reduce_clifford_seq([random.choice(set_A), random.choice(set_B)]) + + def _invert_clifford_circuit(self, circuit: cirq.Circuit) -> cirq.Circuit: + """Given a Clifford circuit find and append the corresponding inverse Clifford gate. + + Args: + circuit: The Clifford circuit to invert. + + Returns: + A copy of the original Clifford circuit with the inverse element appended. + """ + clifford_gates = [op.gate for op in circuit.all_operations()] + inv_element = self._reduce_clifford_seq( + cirq.inverse(clifford_gates) # type: ignore[arg-type] + ) + return circuit + inv_element(*self.qubits) + + def _build_circuits(self, num_circuits: int, cycle_depths: Iterable[int]) -> Sequence[Sample]: + """Build a list of randomised circuits required for the IRB experiment. + + Args: + num_circuits: Number of circuits to generate. + cycle_depths: An iterable of the different cycle depths to use during the experiment. + + Returns: + The list of experiment samples. + """ + samples = [] + for _, depth in product(range(num_circuits), cycle_depths, desc="Building circuits"): + base_circuit = cirq.Circuit( + *[self._random_single_qubit_clifford()(*self.qubits) for _ in range(depth)] + ) + rb_circuit = self._invert_clifford_circuit(base_circuit) + irb_circuit = self._invert_clifford_circuit( + self._interleave_op( + base_circuit, self.interleaved_gate(*self.qubits), include_final=True + ) + ) + samples += [ + Sample( + raw_circuit=rb_circuit + cirq.measure(sorted(rb_circuit.all_qubits())), + data={ + "num_cycles": depth, + "circuit_depth": len(rb_circuit), + "experiment": "RB", + }, + ), + Sample( + raw_circuit=irb_circuit + cirq.measure(sorted(irb_circuit.all_qubits())), + data={ + "num_cycles": depth, + "circuit_depth": len(irb_circuit), + "experiment": "IRB", + }, + ), + ] + + return samples + + def _process_probabilities(self, samples: Sequence[Sample]) -> pd.DataFrame: + """Processes the probabilities generated by sampling the circuits into the data structures + needed for analyzing the results. + + Args: + samples: The list of samples to process the results from. + + Returns: + A data frame of the full results needed to analyse the experiment. + """ + + records = [] + for sample in samples: + records.append( + { + "clifford_depth": sample.data["num_cycles"], + "circuit_depth": sample.data["circuit_depth"], + "experiment": sample.data["experiment"], + **sample.probabilities, + } + ) + + return pd.DataFrame(records) + + def plot_results(self) -> None: + """Plot the exponential decay of the circuit fidelity with cycle depth.""" + plot = sns.lmplot( + data=self.raw_data, + x="clifford_depth", + y="log_fidelity", + hue="experiment", + ) + ax = plot.axes.item() + plot.tight_layout() + ax.set_xlabel(r"Cycle depth", fontsize=15) + ax.set_ylabel(r"Log Circuit fidelity", fontsize=15) + ax.set_title(r"Exponential decay of circuit fidelity", fontsize=15) + + def analyze_results(self, plot_results: bool = True) -> IRBResults: + """Analyse the experiment results and estimate the interleaved gate error. + + Args: + plot_results: Whether to generate plots of the results. Defaults to False. + + Returns: + A named tuple of the final results from the experiment. + """ + + self.raw_data["fidelity"] = 2 * self.raw_data["0"] - 1 + self.raw_data["log_fidelity"] = np.log(self.raw_data["fidelity"]) + self.raw_data.dropna(axis=0, inplace=True) # Remove any NaNs coming from the P(0) < 0.5 + + rb_model = linregress( + self.raw_data.query("experiment == 'RB'")["clifford_depth"], + np.log(self.raw_data.query("experiment == 'RB'")["fidelity"]), + ) + irb_model = linregress( + self.raw_data.query("experiment == 'IRB'")["clifford_depth"], + np.log(self.raw_data.query("experiment == 'IRB'")["fidelity"]), + ) + + # Extract fit values. + rb_layer_fidelity = np.exp(rb_model.slope) + rb_layer_fidelity_std = rb_model.stderr * rb_layer_fidelity + irb_layer_fidelity = np.exp(irb_model.slope) + irb_layer_fidelity_std = irb_model.stderr * irb_layer_fidelity + + interleaved_gate_error = (1 - irb_layer_fidelity / rb_layer_fidelity) / 2 + + interleaved_gate_error_std = np.sqrt( + (irb_layer_fidelity_std / (2 * rb_layer_fidelity)) ** 2 + + ((irb_layer_fidelity * rb_layer_fidelity_std) / (2 * rb_layer_fidelity**2)) ** 2 + ) + + self._results = IRBResults( + target="& ".join(self.targets), + total_circuits=len(self.samples), + rb_layer_fidelity=rb_layer_fidelity, + rb_layer_fidelity_std=rb_layer_fidelity_std, + irb_layer_fidelity=irb_layer_fidelity, + irb_layer_fidelity_std=irb_layer_fidelity_std, + average_interleaved_gate_error=interleaved_gate_error, + average_interleaved_gate_error_std=interleaved_gate_error_std, + ) + + if plot_results: + self.plot_results() + + return self.results diff --git a/supermarq-benchmarks/supermarq/qcvv/irb_test.py b/supermarq-benchmarks/supermarq/qcvv/irb_test.py new file mode 100644 index 000000000..0106dc11c --- /dev/null +++ b/supermarq-benchmarks/supermarq/qcvv/irb_test.py @@ -0,0 +1,281 @@ +# Copyright 2021 The Cirq Developers +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=missing-function-docstring +# pylint: disable=missing-return-doc +# mypy: disable-error-code=method-assign +from __future__ import annotations + +import os +from unittest.mock import MagicMock, patch + +import cirq +import pandas as pd +import pytest + +from supermarq.qcvv.base_experiment import Sample +from supermarq.qcvv.irb import IRB + + +@pytest.fixture(scope="session", autouse=True) +def patch_tqdm() -> None: + os.environ["TQDM_DISABLE"] = "1" + + +def test_irb_init() -> None: + with patch("cirq_superstaq.service.Service"): + experiment = IRB() + assert experiment.num_qubits == 1 + assert experiment.interleaved_gate == cirq.ops.SingleQubitCliffordGate.Z + + experiment = IRB(interleaved_gate=cirq.ops.SingleQubitCliffordGate.X) + assert experiment.num_qubits == 1 + assert experiment.interleaved_gate == cirq.ops.SingleQubitCliffordGate.X + + with pytest.raises(NotImplementedError): + IRB(num_qubits=2) + + +@pytest.fixture +def irb_experiment() -> IRB: + with patch("cirq_superstaq.service.Service"): + return IRB() + + +def test_reduce_clifford_sequence() -> None: + sequence = [ + cirq.ops.SingleQubitCliffordGate.X, + cirq.ops.SingleQubitCliffordGate.X, + cirq.ops.SingleQubitCliffordGate.Z, + ] + + combined_gate = IRB._reduce_clifford_seq(sequence) + assert combined_gate == cirq.ops.SingleQubitCliffordGate.Z + + +def test_random_single_qubit_clifford() -> None: + gate = IRB._random_single_qubit_clifford() + assert isinstance(gate, cirq.ops.SingleQubitCliffordGate) + + +def test_invert_clifford_circuit(irb_experiment: IRB) -> None: + circuit = cirq.Circuit( + [ + cirq.ops.SingleQubitCliffordGate.X(irb_experiment.qubits[0]), + cirq.ops.SingleQubitCliffordGate.Y(irb_experiment.qubits[0]), + ] + ) + inverse = irb_experiment._invert_clifford_circuit(circuit) + expected_inverse = circuit + cirq.ops.SingleQubitCliffordGate.Z(irb_experiment.qubits[0]) + + cirq.testing.assert_same_circuits(inverse, expected_inverse) + + +def test_irb_process_probabilities(irb_experiment: IRB) -> None: + samples = [ + Sample( + raw_circuit=cirq.Circuit(), + data={ + "num_cycles": 20, + "circuit_depth": 23, + "experiment": "example", + }, + ) + ] + samples[0].probabilities = {"00": 0.1, "01": 0.2, "10": 0.3, "11": 0.4} + + data = irb_experiment._process_probabilities(samples) + + expected_data = pd.DataFrame( + [ + { + "clifford_depth": 20, + "circuit_depth": 23, + "experiment": "example", + "00": 0.1, + "01": 0.2, + "10": 0.3, + "11": 0.4, + } + ] + ) + + pd.testing.assert_frame_equal(expected_data, data) + + +def test_irb_build_circuit(irb_experiment: IRB) -> None: + irb_experiment._random_single_qubit_clifford = (mock_random_clifford := MagicMock()) + mock_random_clifford.side_effect = [ + cirq.ops.SingleQubitCliffordGate.Z, + cirq.ops.SingleQubitCliffordGate.Z, + cirq.ops.SingleQubitCliffordGate.Z, + cirq.ops.SingleQubitCliffordGate.X, + cirq.ops.SingleQubitCliffordGate.X, + cirq.ops.SingleQubitCliffordGate.X, + ] + + circuits = irb_experiment._build_circuits(2, [3]) + expected_circuits = [ + Sample( + raw_circuit=cirq.Circuit( + [ + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), + cirq.measure(irb_experiment.qubits), + ] + ), + data={ + "num_cycles": 3, + "circuit_depth": 4, + "experiment": "RB", + }, + ), + Sample( + raw_circuit=cirq.Circuit( + [ + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), + cirq.TaggedOperation( + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), "no_compile" + ), + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), + cirq.TaggedOperation( + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), "no_compile" + ), + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), + cirq.TaggedOperation( + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), "no_compile" + ), + cirq.ops.SingleQubitCliffordGate.I(*irb_experiment.qubits), + cirq.measure(irb_experiment.qubits), + ] + ), + data={ + "num_cycles": 3, + "circuit_depth": 7, + "experiment": "IRB", + }, + ), + Sample( + raw_circuit=cirq.Circuit( + [ + cirq.ops.SingleQubitCliffordGate.X(*irb_experiment.qubits), + cirq.ops.SingleQubitCliffordGate.X(*irb_experiment.qubits), + cirq.ops.SingleQubitCliffordGate.X(*irb_experiment.qubits), + cirq.ops.SingleQubitCliffordGate.X(*irb_experiment.qubits), + cirq.measure(irb_experiment.qubits), + ] + ), + data={ + "num_cycles": 3, + "circuit_depth": 4, + "experiment": "RB", + }, + ), + Sample( + raw_circuit=cirq.Circuit( + [ + cirq.ops.SingleQubitCliffordGate.X(*irb_experiment.qubits), + cirq.TaggedOperation( + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), "no_compile" + ), + cirq.ops.SingleQubitCliffordGate.X(*irb_experiment.qubits), + cirq.TaggedOperation( + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), "no_compile" + ), + cirq.ops.SingleQubitCliffordGate.X(*irb_experiment.qubits), + cirq.TaggedOperation( + cirq.ops.SingleQubitCliffordGate.Z(*irb_experiment.qubits), "no_compile" + ), + cirq.ops.SingleQubitCliffordGate.Y(*irb_experiment.qubits), + cirq.measure(irb_experiment.qubits), + ] + ), + data={ + "num_cycles": 3, + "circuit_depth": 7, + "experiment": "IRB", + }, + ), + ] + + assert len(circuits) == 4 + + cirq.testing.assert_same_circuits(circuits[0].circuit, expected_circuits[0].circuit) + assert circuits[0].data == expected_circuits[0].data + cirq.testing.assert_same_circuits(circuits[1].circuit, expected_circuits[1].circuit) + assert circuits[1].data == expected_circuits[1].data + cirq.testing.assert_same_circuits(circuits[2].circuit, expected_circuits[2].circuit) + assert circuits[2].data == expected_circuits[2].data + cirq.testing.assert_same_circuits(circuits[3].circuit, expected_circuits[3].circuit) + assert circuits[3].data == expected_circuits[3].data + + +def test_analyse_results(irb_experiment: IRB) -> None: + irb_experiment._samples = MagicMock() + irb_experiment._raw_data = pd.DataFrame( + [ + { + "clifford_depth": 1, + "circuit_depth": 2, + "experiment": "RB", + "0": 0.5 * 0.95**1 + 0.5, + "1": 0.5 - 0.5 * 0.95**1, + }, + { + "clifford_depth": 1, + "circuit_depth": 3, + "experiment": "IRB", + "0": 0.5 * 0.8**1 + 0.5, + "1": 0.5 - 0.5 * 0.8**1, + }, + { + "clifford_depth": 5, + "circuit_depth": 6, + "experiment": "RB", + "0": 0.5 * 0.95**5 + 0.5, + "1": 0.5 - 0.5 * 0.95**5, + }, + { + "clifford_depth": 5, + "circuit_depth": 11, + "experiment": "IRB", + "0": 0.5 * 0.8**5 + 0.5, + "1": 0.5 - 0.5 * 0.8**5, + }, + { + "clifford_depth": 10, + "circuit_depth": 11, + "experiment": "RB", + "0": 0.5 * 0.95**10 + 0.5, + "1": 0.5 - 0.5 * 0.95**10, + }, + { + "clifford_depth": 10, + "circuit_depth": 21, + "experiment": "IRB", + "0": 0.5 * 0.8**10 + 0.5, + "1": 0.5 - 0.5 * 0.8**10, + }, + ] + ) + irb_experiment.analyze_results() + + assert irb_experiment.results.rb_layer_fidelity == pytest.approx(0.95) + assert irb_experiment.results.irb_layer_fidelity == pytest.approx(0.8) + assert irb_experiment.results.average_interleaved_gate_error == pytest.approx( + 0.5 * (1 - 0.8 / 0.95) + ) + + # Test that plotting results doesn't introduce any errors. + irb_experiment.plot_results() diff --git a/supermarq-benchmarks/supermarq/qcvv/xeb.py b/supermarq-benchmarks/supermarq/qcvv/xeb.py index f3c2a5ddb..dfc23d29e 100644 --- a/supermarq-benchmarks/supermarq/qcvv/xeb.py +++ b/supermarq-benchmarks/supermarq/qcvv/xeb.py @@ -117,6 +117,8 @@ class XEB(BenchmarkingExperiment[XEBResults]): Thus fitting another linear model to :math:`\log(f_d) \sim d` provides us with an estimate of the cycle fidelity. + + For more details see: https://www.nature.com/articles/s41586-019-1666-5 """ def __init__(