Skip to content

Commit

Permalink
tie up lose ends
Browse files Browse the repository at this point in the history
  • Loading branch information
cdbf1 committed Aug 5, 2024
1 parent 5da72d6 commit 08c7af7
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 84 deletions.
45 changes: 28 additions & 17 deletions supermarq-benchmarks/examples/qcvv/qcvv_css.ipynb

Large diffs are not rendered by default.

39 changes: 19 additions & 20 deletions supermarq-benchmarks/examples/qcvv/qcvv_irb_css.ipynb

Large diffs are not rendered by default.

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

from .base_experiment import BenchmarkingExperiment, BenchmarkingResults, Sample
from .irb import IRB, IRBResults

__all__ = ["BenchmarkingExperiment", "BenchmarkingResults", "Sample", "IRB", "IRBResults"]
28 changes: 15 additions & 13 deletions supermarq-benchmarks/supermarq/qcvv/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,18 @@ def target(self) -> str:


@dataclass(frozen=True)
class QCVVResults:
class BenchmarkingResults(ABC):
"""A dataclass for storing the results of the experiment. Requires subclassing for
each new experiment type"""

experiment_name: str
"""The name of the experiment."""
target: str
"""The target device that was used."""
total_circuits: int
"""The total number of circuits used in the experiment."""

experiment_name: str = field(init=False)
"""The name of the experiment"""


class BenchmarkingExperiment(ABC):
"""Base class for gate benchmarking experiments.
Expand Down Expand Up @@ -106,7 +107,7 @@ class BenchmarkingExperiment(ABC):
results = experiment.analyse_results(<<args>>)
#. The final results of the experiment will be stored in the :code:`results` attribute as a
:class:`QCVVResults` of values, while all the data from the experiment will be
:class:`BenchmarkingResults` of values, while all the data from the experiment will be
stored in the :code:`raw_data` attribute as a :class:`~pandas.DataFrame`. Some experiments
may include additional data attributes for data generated during the analysis.
Expand All @@ -130,14 +131,13 @@ class BenchmarkingExperiment(ABC):
into a :class:`pandas.DataFrame`.
#. :meth:`analyse_results`: Analyse the data in the :attr:`raw_data` dataframe and return a
:class:`QCVVResults` object containing the results of the experiment.
:class:`BenchmarkingResults` object containing the results of the experiment.
#. :meth:`plot_results`: Produce any relevant plots that are useful for understanding the
results of the experiment.
Additionally the :class:`QCVVResults` dataclass needs subclassing to hold the specific results
of the new experiment.
Additionally the :class:`BenchmarkingResults` dataclass needs subclassing to hold the specific
results of the new experiment.
"""

def __init__(
Expand All @@ -156,7 +156,7 @@ def __init__(
self._raw_data: pd.DataFrame | None = None
"The data generated during the experiment"

self._results: QCVVResults | None = None
self._results: BenchmarkingResults | None = None
"""The attribute to store the results in."""

self._samples: Sequence[Sample] | None = None
Expand All @@ -166,7 +166,7 @@ def __init__(
"""The superstaq service for submitting jobs."""

@property
def results(self) -> QCVVResults:
def results(self) -> BenchmarkingResults:
"""The results from the most recently run experiment.
Raises:
Expand Down Expand Up @@ -218,8 +218,10 @@ def sample_targets(self) -> list[str]:
def _validate_circuits(self) -> None:
"""Checks that all circuits contain a terminal measurement of all qubits."""
for sample in self.samples:
if not sample.circuit.has_measurements():
raise ValueError("QCVV experiment circuits must contain measurements.")
if not sample.circuit.are_all_measurements_terminal():
raise ValueError("QCVV experiment circuits can only contain terminal measurements")
raise ValueError("QCVV experiment circuits can only contain terminal measurements.")
if not sorted(sample.circuit[-1].qubits) == sorted(self.qubits):
raise ValueError(
"The terminal measurement in QCVV experiment circuits must measure all qubits."
Expand Down Expand Up @@ -432,7 +434,7 @@ def build_circuits(
cycle_depths: An iterable of the different cycle depths to use during the experiment.
Returns:
The list of circuit objects
The list of experiment samples.
"""

@abstractmethod
Expand All @@ -452,7 +454,7 @@ def plot_results(self) -> None:
"""Plot the results of the experiment"""

@abstractmethod
def analyse_results(self, plot_results: bool = True) -> QCVVResults:
def analyse_results(self, plot_results: bool = True) -> BenchmarkingResults:
"""Perform the experiment analysis and store the results in the `results` attribute
Args:
Expand Down
22 changes: 15 additions & 7 deletions supermarq-benchmarks/supermarq/qcvv/base_experiment_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import pandas as pd
import pytest

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


@pytest.fixture(scope="session", autouse=True)
Expand All @@ -50,16 +50,18 @@ def sample_circuits() -> list[Sample]:
circuit=cirq.Circuit(cirq.CZ(*qubits), cirq.CZ(*qubits), cirq.measure(*qubits)),
data={"circuit": 1},
),
Sample(circuit=cirq.Circuit(cirq.CX(*qubits)), data={"circuit": 2}),
Sample(circuit=cirq.Circuit(cirq.CX(*qubits), cirq.measure(*qubits)), data={"circuit": 2}),
]


@dataclass(frozen=True)
class ExampleResults(QCVVResults):
class ExampleResults(BenchmarkingResults):
"""NamedTuple instance to use for testing"""

example: float

experiment_name = "Example results"


def test_sample_target_property() -> None:
sample = Sample(circuit=MagicMock(), data={})
Expand All @@ -80,9 +82,7 @@ def test_benchmarking_experiment_init(abc_experiment: BenchmarkingExperiment) ->
assert abc_experiment._samples is None

abc_experiment._raw_data = pd.DataFrame([{"Example": 0.1}])
abc_experiment._results = ExampleResults(
experiment_name="Example", target="Some target", total_circuits=2, example=5.0
)
abc_experiment._results = ExampleResults(target="Some target", total_circuits=2, example=5.0)

pd.testing.assert_frame_equal(abc_experiment.raw_data, abc_experiment._raw_data)
assert abc_experiment.results == abc_experiment._results
Expand Down Expand Up @@ -325,7 +325,7 @@ def test_validate_circuits(
# Add a gate so not all measurements are terminal
abc_experiment._samples[0].circuit += cirq.X(abc_experiment.qubits[0])
with pytest.raises(
ValueError, match="QCVV experiment circuits can only contain terminal measurements"
ValueError, match="QCVV experiment circuits can only contain terminal measurements."
):
abc_experiment._validate_circuits()

Expand All @@ -339,6 +339,14 @@ def test_validate_circuits(
):
abc_experiment._validate_circuits()

# Remove all measurements
abc_experiment._samples[0].circuit = abc_experiment._samples[0].circuit[:-2]
with pytest.raises(
ValueError,
match="QCVV experiment circuits must contain measurements.",
):
abc_experiment._validate_circuits()


def test_process_device_counts(abc_experiment: BenchmarkingExperiment) -> None:
counts = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import random
from collections.abc import Iterable, Sequence
from typing import NamedTuple, cast
from dataclasses import dataclass

import cirq
import numpy as np
Expand All @@ -26,10 +26,11 @@
from scipy.stats import linregress
from tqdm.contrib.itertools import product

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


class IRBResults(NamedTuple):
@dataclass(frozen=True)
class IRBResults(BenchmarkingResults):
"""Data structure for the IRB experiment results."""

rb_layer_fidelity: float
Expand All @@ -45,6 +46,8 @@ class IRBResults(NamedTuple):
average_interleaved_gate_error_std: float
"""Standard deviation of the estimate for the interleaving gate error."""

experiment_name = "IRB"


class IRB(BenchmarkingExperiment):
r"""Interleaved random benchmarking (IRB) experiment.
Expand Down Expand Up @@ -72,7 +75,7 @@ class IRB(BenchmarkingExperiment):
.. math::
e_{\mathcal{C}^*} = 1 - \frac{\tilde{\alpha}}{\alpha}
e_{\mathcal{C}^*} = \frac{1}{2} \left(1 - \frac{\tilde{\alpha}}{\alpha}\right)
"""

Expand All @@ -94,11 +97,6 @@ def __init__(
self.interleaved_gate = interleaved_gate
"""The gate being interleaved"""

@property
def results(self) -> IRBResults:
"""The results from the most recently run experiment"""
return cast("IRBResults", super().results)

@staticmethod
def _reduce_clifford_seq(
gate_seq: list[cirq.ops.SingleQubitCliffordGate],
Expand Down Expand Up @@ -159,20 +157,18 @@ def _invert_clifford_circuit(self, circuit: cirq.Circuit) -> cirq.Circuit:
clifford_gates.append(inv_element)
return cirq.Circuit(*[gate(*self.qubits) for gate in clifford_gates]) # type: ignore[misc]

def build_circuits(self, num_circuits: int, layers: Iterable[int]) -> Sequence[Sample]:
def build_circuits(self, num_circuits: int, cycle_depths: Iterable[int]) -> Sequence[Sample]:
"""Build a list of randomised circuits required for the IRB experiment.
These circuits do not include the interleaving gate or the final inverse
gate, instead these are added in the :meth:`sample_circuit` function.
Args:
num_circuits: Number of circuits to generate.
layers: TODO
cycle_depths: An iterable of the different cycle depths to use during the experiment.
Returns:
TODO
The list of experiment samples.
"""
samples = []
for _, depth in product(range(num_circuits), layers, desc="Building circuits"):
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)]
)
Expand All @@ -182,15 +178,15 @@ def build_circuits(self, num_circuits: int, layers: Iterable[int]) -> Sequence[S
)
samples += [
Sample(
circuit=rb_circuit,
circuit=rb_circuit + cirq.measure(sorted(rb_circuit.all_qubits())),
data={
"num_cycles": depth,
"circuit_depth": len(rb_circuit),
"experiment": "RB",
},
),
Sample(
circuit=irb_circuit,
circuit=irb_circuit + cirq.measure(sorted(irb_circuit.all_qubits())),
data={
"num_cycles": depth,
"circuit_depth": len(irb_circuit),
Expand All @@ -201,14 +197,19 @@ def build_circuits(self, num_circuits: int, layers: Iterable[int]) -> Sequence[S

return samples

def process_probabilities(self) -> None:
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.
"""
super().process_probabilities()

records = []
for sample in self.samples:
for sample in samples:
records.append(
{
"clifford_depth": sample.data["num_cycles"],
Expand All @@ -218,7 +219,7 @@ def process_probabilities(self) -> None:
}
)

self._raw_data = pd.DataFrame(records)
return pd.DataFrame(records)

def plot_results(self) -> None:
"""Plot the exponential decay of the circuit fidelity with
Expand All @@ -237,7 +238,14 @@ def plot_results(self) -> None:
ax.set_title(r"Exponential decay of circuit fidelity", fontsize=15)

def analyse_results(self, plot_results: bool = True) -> IRBResults:
"""Analyse the experiment results and estimate the interleaved gate error."""
"""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"])
Expand All @@ -264,6 +272,8 @@ def analyse_results(self, plot_results: bool = True) -> IRBResults:
)

self._results = IRBResults(
target=self.sample_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,
Expand Down
Loading

0 comments on commit 08c7af7

Please sign in to comment.