-
Notifications
You must be signed in to change notification settings - Fork 19
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
cdbf1
wants to merge
44
commits into
main
Choose a base branch
from
feature/SU2_benchmarking
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
SU2 benchmarking #1017
Changes from 39 commits
Commits
Show all changes
44 commits
Select commit
Hold shift + click to select a range
0de866f
Feature: add base qcvv framework
cdbf1 0aa0d41
nit: add blank line
cdbf1 41bcc9b
Move qcvv to Supermarq
cdbf1 17b585b
Fix imports and tests
cdbf1 6dbdfa2
Revised results processing
cdbf1 a8ba683
Fix tests
cdbf1 246c58d
Remove qcvv from cirq docs
cdbf1 3eac549
Patch css Service in tests
cdbf1 9d8e844
Fix import css
cdbf1 26af253
fix: fix tests and notebook
cdbf1 d107276
fix: add seaborn to requirements
cdbf1 5859d69
fix: add future annotations to notebook
cdbf1 215b806
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 48aa81d
Fixes following review
cdbf1 cc881ad
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 f58dcd0
minor fix to tests and docs
cdbf1 3cde95e
Reduce circuit count in example
cdbf1 f361b14
Further fixes from code review
cdbf1 16cf125
Remove kw_only data classes as it doesn't work with python 3.8
cdbf1 2a0092c
Fix: add functionality for multiple subjobs
cdbf1 f3ea316
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 43c7879
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 39c415b
Minor updates to naming
cdbf1 aa30d6b
Fix: add generic results type for subclassing
cdbf1 88327b0
Fix: fixes from RR review
cdbf1 41f41f6
Merge branch 'main' into feature/base_qcvv_framework
cdbf1 3bd50cf
Initial implementation
cdbf1 63f3805
A bit of tidying up
cdbf1 c4da36d
Add testing
cdbf1 a5e5d87
Add additional documentation
cdbf1 5c0e898
Add future import to demo notebook
cdbf1 0560732
Merge branch 'main' into feature/SU2_benchmarking
cdbf1 edc97ec
Align with updated qcvv framework
cdbf1 89337e8
Add test skipping for different version of python
cdbf1 e2469d9
Fix version
cdbf1 531a773
Allow missing coverage for skipped tests
cdbf1 c7b33d2
Merge branch 'main' into feature/SU2_benchmarking
cdbf1 3273f65
Merge remote-tracking branch 'origin/main' into feature/SU2_benchmarking
cdbf1 2d6a3fb
Fixed some nts from code review
cdbf1 7f52957
Fix: should be global rotation
cdbf1 36c5127
Merge branch 'main' into feature/SU2_benchmarking
cdbf1 3458880
Fix: updated probability attribute from `main`
cdbf1 d633ee9
Merge branch 'main' into feature/SU2_benchmarking
cdbf1 6b51310
Update to new QCVV structure
cdbf1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
# 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 = "SU2" | ||
|
||
@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 fit decay is then fitted to the observed 00 state probability as it decays with | ||
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. | ||
|
||
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: | ||
"""Core component of the experimental circuits. | ||
|
||
These circuits that are repeated to create the full circuit. Can optionally include the | ||
two qubit gate being measured, as is required for the | ||
first half of the full circuit, but not for the second half. | ||
|
||
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]), | ||
( | ||
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)) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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")
There was a problem hiding this comment.
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 😃