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

Add json magic methods to the QCVV library to facilitate saving and reloading using cirq #1138

Merged
merged 9 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
32 changes: 27 additions & 5 deletions docs/source/apps/supermarq/qcvv/qcvv_css.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,14 @@
"outputs": [],
"source": [
"from supermarq.qcvv import QCVVExperiment, Sample, QCVVResults\n",
"\n",
"from dataclasses import dataclass\n",
"from collections.abc import Sequence\n",
"from typing import Iterable\n",
"from typing import Any, Iterable\n",
"from tqdm.contrib.itertools import product\n",
"from typing_extensions import Self\n",
"\n",
"import cirq\n",
"\n",
"from scipy.stats import linregress\n",
"import numpy as np\n",
"import seaborn as sns\n",
Expand Down Expand Up @@ -123,13 +124,15 @@
" cycle_depths: Iterable[int],\n",
" *,\n",
" random_seed: int | np.random.Generator | None = None,\n",
" _samples: list[Sample] | None = None,\n",
" ):\n",
" super().__init__(\n",
" num_qubits=1,\n",
" num_circuits=num_circuits,\n",
" cycle_depths=cycle_depths,\n",
" results_cls=NaiveExperimentResult,\n",
" random_seed=random_seed,\n",
" _samples=_samples,\n",
" )\n",
"\n",
" def _build_circuits(self, num_circuits: int, layers: Iterable[int]) -> Sequence[Sample]:\n",
Expand All @@ -144,7 +147,26 @@
" Sample(circuit_realization=index, circuit=circuit, data={\"depth\": depth})\n",
" )\n",
"\n",
" return samples"
" return samples\n",
"\n",
" def _json_dict_(self) -> dict[str, Any]:\n",
" return super()._json_dict_()\n",
"\n",
" @classmethod\n",
" def _from_json_dict_(\n",
" cls,\n",
" samples: list[Sample],\n",
" num_qubits: int,\n",
" num_circuits: int,\n",
" cycle_depths: list[int],\n",
" **kwargs: Any,\n",
" ) -> Self:\n",
" return cls(\n",
" num_circuits=num_circuits,\n",
" num_qubits=num_qubits,\n",
" cycle_depths=cycle_depths,\n",
" _samples=samples**kwargs,\n",
" )"
]
},
{
Expand All @@ -164,7 +186,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a19c41741da343ec9bd6cef212a79049",
"model_id": "a6c23a1f3f634c4dafbcce0f33720ce5",
"version_major": 2,
"version_minor": 0
},
Expand All @@ -178,7 +200,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "83cf5524bafc4e8180ed52ff52f68131",
"model_id": "fdcc9e7e6f954957a558c89022eb601e",
"version_major": 2,
"version_minor": 0
},
Expand Down
3 changes: 2 additions & 1 deletion supermarq-benchmarks/supermarq/qcvv/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""A toolkit of QCVV routines."""

from .base_experiment import QCVVExperiment, QCVVResults, Sample
from .irb import IRB, IRBResults
from .irb import IRB, IRBResults, RBResults
from .xeb import XEB, XEBResults

__all__ = [
Expand All @@ -12,4 +12,5 @@
"IRBResults",
"XEB",
"XEBResults",
"RBResults",
]
134 changes: 132 additions & 2 deletions supermarq-benchmarks/supermarq/qcvv/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,47 @@

import functools
import math
import pathlib
import warnings
from abc import ABC, abstractmethod
from collections.abc import Callable, Iterable, Sequence
from dataclasses import dataclass
from typing import Any, Generic, TypeVar
from typing import TYPE_CHECKING, Any, Generic, TypeVar

import cirq
import cirq_superstaq as css
import numpy as np
import pandas as pd
from tqdm.auto import tqdm

import supermarq

if TYPE_CHECKING:
from typing_extensions import Self


def qcvv_resolver(cirq_type: str) -> type[Any] | None:
"""Resolves string's referencing classes in the QCVV library. Used by `cirq.read_json()`
to deserialize.

Args:
cirq_type: The type being resolved

Returns:
The corresponding type object (if found) else None

Raises:
ValueError: If the provided type is not resolvable
"""
prefix = "supermarq.qcvv."
if cirq_type.startswith(prefix):
name = cirq_type[len(prefix) :]
if name not in supermarq.qcvv.__all__:
raise ValueError(f"{name} is not resolvable from the QCVV library")
if hasattr(supermarq.qcvv, name):
return getattr(supermarq.qcvv, name)
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved
return None


@dataclass
class Sample:
Expand All @@ -46,6 +75,45 @@ class Sample:
"""The corresponding data about the circuit that is needed when analyzing results
(e.g. cycle depth)."""

def _json_dict_(self) -> dict[str, Any]:
"""Converts the sample to a json-able dictionary that can be used to recreate the
sample object.

Returns:
Json-able dictionary of the sample data.
"""
return {
"circuit": css.serialize_circuits(self.circuit),
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved
"data": self.data,
"circuit_realization": self.circuit_realization,
}

@classmethod
def _from_json_dict_(
cls,
circuit: str,
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved
circuit_realization: int,
data: dict[str, Any],
**_: Any,
) -> Self:
"""Creates a sample from a dictionary of the data.

Args:
dictionary: Dict containing the sample data.

Returns:
The deserialized Sample object.
"""
return cls(
circuit=css.deserialize_circuits(circuit)[0],
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved
circuit_realization=circuit_realization,
data=data,
)

@classmethod
def _json_namespace_(cls) -> str:
return "supermarq.qcvv"


@dataclass
class QCVVResults(ABC):
Expand Down Expand Up @@ -226,6 +294,7 @@ def __init__(
*,
random_seed: int | np.random.Generator | None = None,
results_cls: type[ResultsT],
_samples: Sequence[Sample] | None = None,
**kwargs: Any,
) -> None:
"""Initializes a benchmarking experiment.
Expand All @@ -237,6 +306,7 @@ def __init__(
cycle_depths: A sequence of depths to sample.
random_seed: An optional seed to use for randomization.
results_cls: The results class to use for the experiment.
_samples: Optional list of samples to construct the experiment from
kwargs: Additional kwargs passed to the Superstaq service object.
"""
self.qubits = cirq.LineQubit.range(num_qubits)
Expand All @@ -255,7 +325,10 @@ def __init__(

self._results_cls: type[ResultsT] = results_cls

self.samples = self._prepare_experiment()
if not _samples:
self.samples = self._prepare_experiment()
else:
self.samples = _samples
"""Create all the samples needed for the experiment."""

##############
Expand Down Expand Up @@ -315,6 +388,63 @@ def _interleave_op(
)
return interleaved_circuit

@abstractmethod
def _json_dict_(self) -> dict[str, Any]:
"""Converts the experiment to a json-able dictionary that can be used to recreate the
experiment object. Note that the state of the random number generator is not stored.

.. note:: Must be re-implemented in any subclasses to ensure all important data is stored.

Returns:
Json-able dictionary of the experiment data.
"""
return {
cdbf1 marked this conversation as resolved.
Show resolved Hide resolved
"cycle_depths": self.cycle_depths,
"num_circuits": self.num_circuits,
"num_qubits": self.num_qubits,
"samples": self.samples,
**self._service_kwargs,
}

@classmethod
@abstractmethod
def _from_json_dict_(cls, *args: Any, **kwargs: Any) -> Self:
"""Creates a experiment from an expanded dictionary of the data.

Returns:
The deserialized experiment object.
"""

@classmethod
def _json_namespace_(cls) -> str:
return "supermarq.qcvv"

def to_file(self, filename: str | pathlib.Path) -> None:
"""Save the experiment to a json file.

Args:
filename: Filename to save to.
"""
with open(filename, "w") as file_stream:
cirq.to_json(self, file_stream)

@classmethod
def from_file(cls, filename: str | pathlib.Path) -> Self:
"""Load the experiment from a json file.

Args:
filename: Filename to load from.

Returns:
The loaded experiment.
"""
with open(filename, "r") as file_stream:
experiment = cirq.read_json(
file_stream,
resolvers=[*css.SUPERSTAQ_RESOLVERS, *cirq.DEFAULT_RESOLVERS, qcvv_resolver],
)
return experiment

def _prepare_experiment(
self,
) -> Sequence[Sample]:
Expand Down
Loading
Loading