Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SU2 benchmarking #1017

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
0de866f
Feature: add base qcvv framework
cdbf1 Jul 16, 2024
0aa0d41
nit: add blank line
cdbf1 Jul 16, 2024
41bcc9b
Move qcvv to Supermarq
cdbf1 Jul 19, 2024
17b585b
Fix imports and tests
cdbf1 Jul 19, 2024
6dbdfa2
Revised results processing
cdbf1 Jul 19, 2024
a8ba683
Fix tests
cdbf1 Jul 19, 2024
246c58d
Remove qcvv from cirq docs
cdbf1 Jul 19, 2024
3eac549
Patch css Service in tests
cdbf1 Jul 19, 2024
9d8e844
Fix import css
cdbf1 Jul 19, 2024
26af253
fix: fix tests and notebook
cdbf1 Jul 22, 2024
d107276
fix: add seaborn to requirements
cdbf1 Jul 22, 2024
5859d69
fix: add future annotations to notebook
cdbf1 Jul 22, 2024
215b806
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 Jul 23, 2024
48aa81d
Fixes following review
cdbf1 Jul 31, 2024
cc881ad
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 Jul 31, 2024
f58dcd0
minor fix to tests and docs
cdbf1 Jul 31, 2024
3cde95e
Reduce circuit count in example
cdbf1 Jul 31, 2024
f361b14
Further fixes from code review
cdbf1 Aug 1, 2024
16cf125
Remove kw_only data classes as it doesn't work with python 3.8
cdbf1 Aug 1, 2024
2a0092c
Fix: add functionality for multiple subjobs
cdbf1 Aug 2, 2024
f3ea316
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 Aug 2, 2024
43c7879
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 Aug 5, 2024
39c415b
Minor updates to naming
cdbf1 Aug 5, 2024
aa30d6b
Fix: add generic results type for subclassing
cdbf1 Aug 5, 2024
88327b0
Fix: fixes from RR review
cdbf1 Aug 6, 2024
41f41f6
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 Aug 6, 2024
3bd50cf
Initial implementation
cdbf1 Aug 7, 2024
63f3805
A bit of tidying up
cdbf1 Aug 8, 2024
c4da36d
Add testing
cdbf1 Aug 8, 2024
a5e5d87
Add additional documentation
cdbf1 Aug 8, 2024
5c0e898
Add future import to demo notebook
cdbf1 Aug 8, 2024
0560732
Merge branch 'main' into feature/SU2_benchmarking
cdbf1 Aug 14, 2024
edc97ec
Align with updated qcvv framework
cdbf1 Aug 14, 2024
89337e8
Add test skipping for different version of python
cdbf1 Aug 14, 2024
e2469d9
Fix version
cdbf1 Aug 14, 2024
531a773
Allow missing coverage for skipped tests
cdbf1 Aug 14, 2024
c7b33d2
Merge branch 'main' into feature/SU2_benchmarking
cdbf1 Aug 20, 2024
3273f65
Merge remote-tracking branch 'origin/main' into feature/SU2_benchmarking
cdbf1 Sep 20, 2024
2d6a3fb
Fixed some nts from code review
cdbf1 Sep 20, 2024
7f52957
Fix: should be global rotation
cdbf1 Oct 2, 2024
36c5127
Merge branch 'main' into feature/SU2_benchmarking
cdbf1 Oct 2, 2024
3458880
Fix: updated probability attribute from `main`
cdbf1 Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions supermarq-benchmarks/examples/qcvv/qcvv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Alternatively for pre-built experiments that can be used out of the box see

.. toctree::
:maxdepth: 1

qcvv_su2_css
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved
qcvv_xeb_css

.. note::

At present the QCVV library is only available in :code:`cirq-superstaq`.
At present the QCVV library is only available in :code:`cirq-superstaq`.
170 changes: 170 additions & 0 deletions supermarq-benchmarks/examples/qcvv/qcvv_su2_css.ipynb

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions supermarq-benchmarks/supermarq/qcvv/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""A toolkit of QCVV routines."""

from .base_experiment import BenchmarkingExperiment, BenchmarkingResults, Sample
from .su2 import SU2, SU2Results
from .xeb import XEB, XEBResults, XEBSample

__all__ = [
Expand All @@ -10,4 +11,6 @@
"XEB",
"XEBResults",
"XEBSample",
"SU2",
"SU2Results",
]
265 changes: 265 additions & 0 deletions supermarq-benchmarks/supermarq/qcvv/su2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# 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 SU(2) benchmarking
"""

from __future__ import annotations

from collections.abc import Iterable, Sequence
from dataclasses import dataclass

import cirq
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib.ticker import MaxNLocator
from scipy.stats import linregress
from tqdm.contrib.itertools import product

from supermarq.qcvv.base_experiment import BenchmarkingExperiment, BenchmarkingResults, Sample


@dataclass(frozen=True)
class SU2Results(BenchmarkingResults):
"""Data structure for the SU2 experiment results."""

two_qubit_gate_fidelity: float
"""Estimated two qubit gate fidelity"""
two_qubit_gate_fidelity_std: float
"""Standard deviation of the two qubit gate fidelity estimate"""
single_qubit_noise: float
single_qubit_noise_std: float

experiment_name = "IRB"
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved

@property
def two_qubit_gate_error(self) -> float:
"""Returns:
The two qubit gate error. Equal to one minus the fidelity.
"""
return 1 - self.two_qubit_gate_fidelity

@property
def two_qubit_gate_error_std(self) -> float:
"""Returns:
The two qubit gate error standard deviation. Equal to standard deviation of the
fidelity.
"""
return self.two_qubit_gate_fidelity_std


class SU2(BenchmarkingExperiment[SU2Results]):
r"""SU2 benchmarking experiment.

SU2 benchmarking extracts the fidelity of a given two qubit gate, even in the presence of
additional single qubit errors. The method works by sampling circuits of the form

.. code::

0: ──|─Rr───Q───X───Q──|─ ^{n} ... ─|─Rr───X─|─ ^{N-n} ... ──Rf───M───
| │ │ | | | │
1: ──|─Rr───Q───X───Q──|─ ... ─|─Rr───X─|─ ... ──Rf───M───

Where each :code:`Rr` gate is a randomly chosen :math:`SU(2)` rotation and the :code:`Rf` gates
are single qubit :math:`SU(2)` rotations that in the absence of noise invert the preceding
circuit so that the final qubit state should be :code:`00`.

An exponential fi decay is then fitted to the observed 00 state probability as it decays with
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved
the number of two qubit gates included. Note that all circuits contain a fixed number of single
qubit gates, so that the contribution for single qubit noise is constant.

See Fig. 3 of :ref:`https://www.nature.com/articles/s41586-023-06481-y#Fig3` for further
details.
"""

def __init__(
self,
two_qubit_gate: cirq.Gate = cirq.CZ,
) -> None:
"""Args:
two_qubit_gate: The Clifford gate to measure the gate error of.
num_qubits: The number of qubits to experiment on. Must equal 2.
"""
super().__init__(num_qubits=2)

if two_qubit_gate.num_qubits() != 2:
raise ValueError(
"The `two_qubit_gate` parameter must be a gate that acts on exactly two qubits."
)
self.two_qubit_gate = two_qubit_gate
"""The two qubit gate to be benchmarked"""

def _build_circuits(
self,
num_circuits: int,
cycle_depths: Iterable[int],
) -> Sequence[Sample]:
"""Build a list of circuits required for the experiment. These circuits are stored in
:class:`Sample` objects along with any additional data that is needed during the analysis.
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved

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 = []
max_depth = max(cycle_depths)
for depth, _ in product(cycle_depths, range(num_circuits), desc="Building circuits"):
circuit = cirq.Circuit(
*[self._component(include_two_qubit_gate=True) for _ in range(depth)],
*[self._component(include_two_qubit_gate=False) for _ in range(max_depth - depth)],
)
circuit_inv = cirq.inverse(circuit)
# Decompose circuit inverse into a pair of single qubit rotation gates
_, rot_1, rot_2 = cirq.kron_factor_4x4_to_2x2s(cirq.unitary(circuit_inv))

if (op_1 := cirq.single_qubit_matrix_to_phxz(rot_1)) is not None:
circuit += op_1(self.qubits[0])

if (op_2 := cirq.single_qubit_matrix_to_phxz(rot_2)) is not None:
circuit += op_2(self.qubits[1])

circuit += cirq.measure(sorted(circuit.all_qubits()))

samples.append(Sample(raw_circuit=circuit, data={"num_two_qubit_gates": 2 * depth}))
return samples

def _process_probabilities(self, samples: Sequence[Sample]) -> pd.DataFrame:
"""Processes the probabilities generated by sampling the circuits into a data frame
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(
{
"num_two_qubit_gates": sample.data["num_two_qubit_gates"],
**sample.probabilities,
}
)

return pd.DataFrame(records)

def analyze_results(self, plot_results: bool = True) -> SU2Results:
"""Perform the experiment analysis and store the results in the `results` attribute.

Args:
plot_results: Whether to generate plots of the results. Defaults to False.

Returns:
A named tuple of the final results from the experiment.
"""
fit = linregress(
x=self.raw_data["num_two_qubit_gates"],
y=np.log(self.raw_data["00"] - 1 / 4),
# Scale the y coordinate to account for limit of the decay being 1/4
)
gate_fid = np.exp(fit.slope)
gate_fid_std = fit.stderr * gate_fid

single_qubit_noise = 1 - 4 / 3 * np.exp(fit.intercept)
single_qubit_noise_std = fit.intercept_stderr * (1 - single_qubit_noise)

self._results = SU2Results(
target="& ".join(self.targets),
total_circuits=len(self.samples),
two_qubit_gate_fidelity=gate_fid,
two_qubit_gate_fidelity_std=gate_fid_std,
single_qubit_noise=single_qubit_noise,
single_qubit_noise_std=single_qubit_noise_std,
)

if plot_results:
self.plot_results()

return self.results

@staticmethod
def _haar_random_rotation() -> cirq.Gate:
"""Returns:
Haar randomly sampled SU(2) rotation.
"""
gate: cirq.Gate | None = None
while gate is None:
gate = cirq.single_qubit_matrix_to_phxz(cirq.testing.random_special_unitary(dim=2))
return gate

def _component(self, include_two_qubit_gate: bool) -> cirq.Circuit:
"""The core component of the experimental circuits that are repeated to create the full
circuit. Can optionally include the two qubit gate being measaured, as is required for the
first half of the full circuit, but not for the second half.
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved

The component looks like:
.. code::

0: ───R1───Q───X───Q───
│ │
1: ───R2───Q───X───Q───

where :code:`R1` and :code:`R2` are Haar randomly chosen SU(2) rotation
and :code:`Q-Q` represents the two qubit gate being measured.

Args:
include_two_qubit_gate: Whether to include the two qubit gate being measured

Returns:
The sub circuit to be repeated when building the full circuit
"""
return cirq.Circuit(
self._haar_random_rotation().on(self.qubits[0]),
self._haar_random_rotation().on(self.qubits[1]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe the same rotation is supposed to be applied to both gates, so that they can be done with a single global pulse on systems that support it (the language in the paper is "random global single-qubit rotations between sequences of CZ entangling gates")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry only just seen this comment.
Yes I agree, thanks. I must have misinterpreted the paper. Now fixed 😃

(
self.two_qubit_gate(*self.qubits).with_tags("no_compile")
if include_two_qubit_gate
else []
),
cirq.X.on_each(*self.qubits),
(
self.two_qubit_gate(*self.qubits).with_tags("no_compile")
if include_two_qubit_gate
else []
),
)

def plot_results(self) -> None:
"""Plot the results of the experiment"""
_, ax = plt.subplots()
sns.scatterplot(
data=self.raw_data.melt(
id_vars="num_two_qubit_gates", var_name="state", value_name="prob"
),
x="num_two_qubit_gates",
y="prob",
hue="state",
hue_order=["00", "01", "10", "11"],
style="state",
ax=ax,
)
ax.plot(
xx := self.raw_data["num_two_qubit_gates"],
3 / 4 * (1 - self.results.single_qubit_noise) * self.results.two_qubit_gate_fidelity**xx
+ 0.25,
label="00 (fit)",
)
ax.set_xlabel("Number of two qubit gates")
ax.set_ylabel("State probability")
ax.legend(title="State")
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
Loading