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

Adding reprs to QCVV objects #1130

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
39 changes: 16 additions & 23 deletions docs/source/apps/supermarq/qcvv/qcvv_css.ipynb

Large diffs are not rendered by default.

72 changes: 27 additions & 45 deletions docs/source/apps/supermarq/qcvv/qcvv_xeb_css.ipynb

Large diffs are not rendered by default.

32 changes: 29 additions & 3 deletions supermarq-benchmarks/supermarq/qcvv/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def qcvv_resolver(cirq_type: str) -> type[Any] | None:
return None


@dataclass
@dataclass(repr=False)
class Sample:
"""A sample circuit to use along with any data about the circuit
that is needed for analysis
Expand Down Expand Up @@ -130,7 +130,7 @@ def _json_namespace_(cls) -> str:
return "supermarq.qcvv"


@dataclass
@dataclass(repr=False)
class QCVVResults(ABC):
"""A dataclass for storing the data and analyze results of the experiment. Requires
subclassing for each new experiment type."""
Expand All @@ -147,6 +147,14 @@ class QCVVResults(ABC):
data: pd.DataFrame | None = None
"""The raw data generated."""

def __repr__(self) -> str:
results_type = self.__class__.__name__
try:
results = self._results_msg()
except type(self._not_analyzed):
results = "Results not analyzed"
return f"{results_type}({results}, experiment={self.experiment}, target={self.target})"

@property
def data_ready(self) -> bool:
"""Whether the experimental data is ready to analyse.
Expand Down Expand Up @@ -231,9 +239,21 @@ def plot_results(self, filename: str | None = None) -> plt.Figure:
A single matplotlib figure containing the relevant plots of the results data.
"""

@abstractmethod
def print_results(self) -> None:
"""Prints the key results data."""
try:
msg = self._results_msg()
except type(self._not_analyzed):
msg = "Results not analyzed."
print(msg)

@abstractmethod
def _results_msg(self) -> str:
"""Returns:
The message from analyzing the results. Note that it is expected that this method will
implicitly result in a `self._not_analyzed` exception being raised if there are no
analyzed results available.
"""

def _collect_device_counts(self) -> pd.DataFrame:
"""Process the counts returned by the server and process into a results dataframe.
Expand Down Expand Up @@ -395,6 +415,12 @@ def __getitem__(self, key: str | int | uuid.UUID) -> Sample:
def __iter__(self) -> Iterator[Sample]:
return iter(self.samples)

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}(num_qubits={self.num_qubits}, "
f"num_samples={len(self.samples)})"
)

##############
# Properties #
##############
Expand Down
38 changes: 33 additions & 5 deletions supermarq-benchmarks/supermarq/qcvv/base_experiment_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def test_qcvv_resolver() -> None:
assert qcvv_resolver("supermarq.qcvv.base_experiment.qcvv_resolver") is None


@dataclass
@dataclass(repr=False)
class ExampleResults(QCVVResults):
"""Example results class for testing"""

Expand All @@ -62,8 +62,8 @@ def plot_results(self, filename: str | None = None) -> plt.Figure:
mock_plot(filename)
return plt.Figure()

def print_results(self) -> None:
mock_print("This is a test")
def _results_msg(self) -> str:
return f"This is a test: {self.example_final_result}"

@property
def example_final_result(self) -> float:
Expand Down Expand Up @@ -236,15 +236,26 @@ def test_results_job_no_data(abc_experiment: ExampleExperiment) -> None:
results.analyze()


def test_results_analyze(abc_experiment: ExampleExperiment) -> None:
@patch("builtins.print")
def test_results_analyze(mock_print: MagicMock, abc_experiment: ExampleExperiment) -> None:
results = ExampleResults(
target="target", experiment=abc_experiment, data=MagicMock(spec=pd.DataFrame)
)

# Check the `print_results()` method when there are no results to print.
results.print_results()
mock_print.assert_called_once_with("Results not analyzed.")
mock_print.reset_mock()

# Check the `print_results()` method when there are no results to print.
results.print_results()
mock_print.assert_called_once_with("Results not analyzed.")
mock_print.reset_mock()

results.analyze(plot_results=True, print_results=True, plot_filename="test_name")
assert results.example_final_result == 3.142
mock_plot.assert_called_once_with("test_name")
mock_print.assert_called_once_with("This is a test")
mock_print.assert_called_once_with("This is a test: 3.142")


def test_results_ready(abc_experiment: ExampleExperiment) -> None:
Expand Down Expand Up @@ -857,3 +868,20 @@ def test_dump_and_load(
assert exp.num_qubits == abc_experiment.num_qubits
assert exp.num_circuits == abc_experiment.num_circuits
assert exp.cycle_depths == abc_experiment.cycle_depths


def test_repr(abc_experiment: ExampleExperiment) -> None:
assert abc_experiment.__repr__() == "ExampleExperiment(num_qubits=2, num_samples=30)"
results = ExampleResults(
target="target", experiment=abc_experiment, job=MagicMock(spec=css.Job)
)
assert results.__repr__() == (
"ExampleResults(Results not analyzed, "
"experiment=ExampleExperiment(num_qubits=2, num_samples=30), target=target)"
)

results._example_final_result = 1.234
assert results.__repr__() == (
"ExampleResults(This is a test: 1.234, "
"experiment=ExampleExperiment(num_qubits=2, num_samples=30), target=target)"
)
39 changes: 27 additions & 12 deletions supermarq-benchmarks/supermarq/qcvv/irb.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def _reduce_clifford_seq(
]


@dataclass
@dataclass(repr=False)
class _RBResultsBase(QCVVResults):
_rb_decay_coefficient: float | None = None
"""Decay coefficient estimate without the interleaving gate."""
Expand Down Expand Up @@ -290,7 +290,7 @@ def _analyze(self) -> None:
self._rb_decay_coefficient, self._rb_decay_coefficient_std = rb_fit[0][1], rb_fit[1][1]


@dataclass
@dataclass(repr=False)
class IRBResults(_RBResultsBase):
"""Data structure for the IRB experiment results."""

Expand Down Expand Up @@ -370,6 +370,8 @@ def plot_results(
alpha=0.5,
color="tab:orange",
)
if filename is not None:
plt.savefig(filename)

root_figure = plot.figure.figure
if filename is not None:
Expand Down Expand Up @@ -397,14 +399,14 @@ def _analyze(self) -> None:
self._average_interleaved_gate_error = interleaved_gate_error
self._average_interleaved_gate_error_std = interleaved_gate_error_std

def print_results(self) -> None:
print(
f"Estimated gate error: {self.average_interleaved_gate_error:.6f} +/- "
f"{self.average_interleaved_gate_error_std:.6f}"
def _results_msg(self) -> str:
return (
f"Estimated gate error: {self.average_interleaved_gate_error:.5} +/- "
f"{self.average_interleaved_gate_error_std:.5}"
)


@dataclass
@dataclass(repr=False)
class RBResults(_RBResultsBase):
"""Data structure for the RB experiment results."""

Expand Down Expand Up @@ -463,11 +465,10 @@ def _analyze(self) -> None:
1 - 2**-self.num_qubits
) * self.rb_decay_coefficient_std

def print_results(self) -> None:

print(
f"Estimated error per Clifford: {self.average_error_per_clifford:.6f} +/- "
f"{self.average_error_per_clifford_std:.6f}"
def _results_msg(self) -> str:
return (
f"Estimated error per Clifford: {self.average_error_per_clifford:.5} +/- "
f"{self.average_error_per_clifford_std:.5}"
)


Expand Down Expand Up @@ -561,6 +562,20 @@ def __init__(
**kwargs,
)

def __repr__(self) -> str:
if self.interleaved_gate is None:
return f"RB(num_qubits={self.num_qubits}, " f"num_samples={len(self.samples)})"
else:
# Decompose the Clifford gate into "normal" gates for printing.
gates = [
str(g)
for g in cirq.single_qubit_matrix_to_gates(cirq.unitary(self.interleaved_gate))
]
return (
f"IRB(interleaved_gate={''.join(gates)}, num_qubits={self.num_qubits}, "
f"num_samples={len(self.samples)})"
)

def _clifford_gate_to_circuit(
self,
clifford: cirq.CliffordGate,
Expand Down
37 changes: 37 additions & 0 deletions supermarq-benchmarks/supermarq/qcvv/irb_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,3 +589,40 @@ def test_dump_and_load(
assert exp.cycle_depths == irb.cycle_depths
assert exp.interleaved_gate == irb.interleaved_gate
assert exp.clifford_op_gateset == irb.clifford_op_gateset


def test_repr_irb(irb: IRB) -> None:
assert irb.__repr__() == "IRB(interleaved_gate=Z, num_qubits=1, num_samples=60)"
results = IRBResults(target="example", experiment=irb, data=None)
assert results.__repr__() == (
"IRBResults(Results not analyzed, experiment=IRB(interleaved_gate"
"=Z, num_qubits=1, num_samples=60), target=example)"
)
# Add final results
results._average_interleaved_gate_error = 0.99
results._average_interleaved_gate_error_std = 0.01
assert results.__repr__() == (
"IRBResults(Estimated gate error: 0.99 +/- 0.01, experiment=IRB(interleaved_gate"
"=Z, num_qubits=1, num_samples=60), target=example)"
)


def test_repr_rb() -> None:
rb = IRB(interleaved_gate=None, num_circuits=1, cycle_depths=[1, 3, 5, 10])
results = RBResults(
target="example",
experiment=IRB(interleaved_gate=None, num_circuits=1, cycle_depths=[1, 3, 5, 10]),
)
assert rb.__repr__() == "RB(num_qubits=1, num_samples=4)"

assert results.__repr__() == (
"RBResults(Results not analyzed, experiment=RB("
"num_qubits=1, num_samples=4), target=example)"
)
# Add final results
results._average_error_per_clifford = 0.99
results._average_error_per_clifford_std = 0.01
assert results.__repr__() == (
"RBResults(Estimated error per Clifford: 0.99 +/- 0.01, experiment=RB("
"num_qubits=1, num_samples=4), target=example)"
)
Loading