From 846ec2bdb6f14d768012b70fb4aec1ac951f5c00 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 15 Jan 2024 19:58:59 -0800 Subject: [PATCH 1/7] Add ParallelRandomizedBenchmarkingResult class --- .../experiments/qubit_characterizations.py | 133 +++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/experiments/qubit_characterizations.py b/cirq-core/cirq/experiments/qubit_characterizations.py index 65967154395..87d451bc6c5 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations.py +++ b/cirq-core/cirq/experiments/qubit_characterizations.py @@ -32,10 +32,14 @@ from scipy.optimize import curve_fit from matplotlib import pyplot as plt +import cirq.vis.heatmap as cirq_heatmap +import cirq.vis.histogram as cirq_histogram # this is for older systems with matplotlib <3.2 otherwise 3d projections fail from mpl_toolkits import mplot3d from cirq import circuits, ops, protocols +from cirq.devices import grid_qubit + if TYPE_CHECKING: import cirq @@ -144,6 +148,131 @@ def _fit_exponential(self) -> Tuple[np.ndarray, np.ndarray]: ) +class ParallelRandomizedBenchmarkingResult: + """Results from a parallel randomized benchmarking experiment.""" + + def __init__(self, results_dictionary: Mapping['cirq.Qid', 'RandomizedBenchMarkResult']): + """Inits ParallelRandomizedBenchmarkingResult. + + Args: + results_dictionary: A dictionary containing the results for each qubit. + """ + self._results_dictionary = results_dictionary + + def plot_single_qubit( + self, qubit: 'cirq.Qid', ax: Optional[plt.Axes] = None, **plot_kwargs: Any + ) -> plt.Axes: + """Plot the raw data for the specified qubit. + + Args: + qubit: Plot data for this qubit. + ax: the plt.Axes to plot on. If not given, a new figure is created, + plotted on, and shown. + **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'. + Returns: + The plt.Axes containing the plot. + """ + + return self._results_dictionary[qubit].plot(ax, **plot_kwargs) + + def pauli_error(self) -> Mapping['cirq.Qid', float]: + """ + Returns: + A dictionary containing the Pauli errors for all qubits. + """ + + return { + qubit: self._results_dictionary[qubit].pauli_error() + for qubit in self._results_dictionary + } + + def plot_heatmap( + self, + ax: Optional[plt.Axes] = None, + annotation_format='0.1%', + title='Single-qubit Pauli error', + **plot_kwargs: Any, + ) -> plt.Axes: + """Plot a heatmap of the Pauli errors. If qubits are not cirq.GridQubits, throws an error. + + Args: + ax: the plt.Axes to plot on. If not given, a new figure is created, + plotted on, and shown. + ***plot_kwargs: Arguments to be passed to 'cirq.Heatmap.plot()'. + Returns: + The plt.Axes containing the plot. + """ + + pauli_errors = self.pauli_error() + dictionary_with_grid_qubit_keys = {} + for qubit in pauli_errors: + assert type(qubit) == grid_qubit.GridQubit, "qubits must be cirq.GridQubits" + dictionary_with_grid_qubit_keys[cast(grid_qubit.GridQubit, qubit)] = pauli_errors[qubit] + + if ax is None: + _, ax = plt.subplots(dpi=200, facecolor='white') + + return cirq_heatmap.Heatmap(dictionary_with_grid_qubit_keys).plot( + ax, annotation_format=annotation_format, title=title, **plot_kwargs + ) + + def plot_integrated_histogram( + self, + ax: Optional[plt.Axes] = None, + *, + cdf_on_x: bool = False, + axis_label: str = 'Pauli error', + semilog: bool = True, + median_line: bool = True, + median_label: Optional[str] = 'median', + mean_line: bool = False, + mean_label: Optional[str] = 'mean', + show_zero: bool = False, + title: Optional[str] = None, + **kwargs, + ) -> plt.Axes: + """Plot the Pauli errors using cirq.integrated_histogram(). + + Args: + ax: The axis to plot on. If None, we generate one. + cdf_on_x: If True, flip the axes compared the above example. + axis_label: Label for x axis (y-axis if cdf_on_x is True). + semilog: If True, force the x-axis to be logarithmic. + median_line: If True, draw a vertical line on the median value. + median_label: If drawing median line, optional label for it. + mean_line: If True, draw a vertical line on the mean value. + mean_label: If drawing mean line, optional label for it. + title: Title of the plot. If None, we assign "N={len(data)}". + show_zero: If True, moves the step plot up by one unit by prepending 0 + to the data. + **kwargs: Kwargs to forward to `ax.step()`. Some examples are + color: Color of the line. + linestyle: Linestyle to use for the plot. + lw: linewidth for integrated histogram. + ms: marker size for a histogram trace. + label: An optional label which can be used in a legend. + Returns: + The axis that was plotted on. + """ + + ax = cirq_histogram.integrated_histogram( + data=self.pauli_error(), + ax=ax, + cdf_on_x=cdf_on_x, + axis_label=axis_label, + semilog=semilog, + median_line=median_line, + median_label=median_label, + mean_line=mean_line, + mean_label=mean_label, + show_zero=show_zero, + title=title, + **kwargs, + ) + ax.set_ylabel('Percentile') + return ax + + class TomographyResult: """Results from a state tomography experiment.""" @@ -321,7 +450,9 @@ def parallel_single_qubit_randomized_benchmarking( idx += 1 for qubit in qubits: gnd_probs[qubit].append(1.0 - np.mean(excited_probs[qubit])) - return {q: RandomizedBenchMarkResult(num_clifford_range, gnd_probs[q]) for q in qubits} + return ParallelRandomizedBenchmarkingResult( + {q: RandomizedBenchMarkResult(num_clifford_range, gnd_probs[q]) for q in qubits} + ) def two_qubit_randomized_benchmarking( From 926bc3d78380f6c562c6f4ca1365db352495d5c8 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 15 Jan 2024 20:23:45 -0800 Subject: [PATCH 2/7] typecheck, etc --- cirq-core/cirq/experiments/qubit_characterizations_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/experiments/qubit_characterizations_test.py b/cirq-core/cirq/experiments/qubit_characterizations_test.py index 36fc6a838d6..29b2d69f20a 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations_test.py +++ b/cirq-core/cirq/experiments/qubit_characterizations_test.py @@ -126,7 +126,7 @@ def test_parallel_single_qubit_randomized_benchmarking(): simulator, num_clifford_range=num_cfds, repetitions=100, qubits=qubits ) for qubit in qubits: - g_pops = np.asarray(results[qubit].data)[:, 1] + g_pops = np.asarray(results._results_dictionary[qubit].data)[:, 1] assert np.isclose(np.mean(g_pops), 1.0) From 40e209ae15cf442f18b75ec89707fce10a2d71ee Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 15 Jan 2024 20:27:04 -0800 Subject: [PATCH 3/7] typecheck, etc --- .../cirq/experiments/qubit_characterizations.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cirq-core/cirq/experiments/qubit_characterizations.py b/cirq-core/cirq/experiments/qubit_characterizations.py index 87d451bc6c5..749649fb2b2 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations.py +++ b/cirq-core/cirq/experiments/qubit_characterizations.py @@ -176,7 +176,7 @@ def plot_single_qubit( return self._results_dictionary[qubit].plot(ax, **plot_kwargs) def pauli_error(self) -> Mapping['cirq.Qid', float]: - """ + """Return a dictionary of Pauli errors. Returns: A dictionary containing the Pauli errors for all qubits. """ @@ -189,8 +189,8 @@ def pauli_error(self) -> Mapping['cirq.Qid', float]: def plot_heatmap( self, ax: Optional[plt.Axes] = None, - annotation_format='0.1%', - title='Single-qubit Pauli error', + annotation_format: str = '0.1%', + title: str = 'Single-qubit Pauli error', **plot_kwargs: Any, ) -> plt.Axes: """Plot a heatmap of the Pauli errors. If qubits are not cirq.GridQubits, throws an error. @@ -198,23 +198,24 @@ def plot_heatmap( Args: ax: the plt.Axes to plot on. If not given, a new figure is created, plotted on, and shown. + annotation_format: The format string for the numbers in the heatmap. + title: The title printed above the heatmap. ***plot_kwargs: Arguments to be passed to 'cirq.Heatmap.plot()'. Returns: The plt.Axes containing the plot. """ pauli_errors = self.pauli_error() - dictionary_with_grid_qubit_keys = {} for qubit in pauli_errors: assert type(qubit) == grid_qubit.GridQubit, "qubits must be cirq.GridQubits" - dictionary_with_grid_qubit_keys[cast(grid_qubit.GridQubit, qubit)] = pauli_errors[qubit] if ax is None: _, ax = plt.subplots(dpi=200, facecolor='white') - return cirq_heatmap.Heatmap(dictionary_with_grid_qubit_keys).plot( + ax, _ = cirq_heatmap.Heatmap(pauli_errors).plot( ax, annotation_format=annotation_format, title=title, **plot_kwargs ) + return ax def plot_integrated_histogram( self, @@ -407,7 +408,7 @@ def parallel_single_qubit_randomized_benchmarking( ), num_circuits: int = 10, repetitions: int = 1000, -) -> Mapping['cirq.Qid', 'RandomizedBenchMarkResult']: +) -> 'ParallelRandomizedBenchmarkingResult': """Clifford-based randomized benchmarking (RB) single qubits in parallel. This is the same as `single_qubit_randomized_benchmarking` except on all From e73aea37f8e0c675e6c2a2d61d05a83241bd6b0d Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 15 Jan 2024 20:45:32 -0800 Subject: [PATCH 4/7] typecheck, etc --- cirq-core/cirq/experiments/qubit_characterizations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cirq-core/cirq/experiments/qubit_characterizations.py b/cirq-core/cirq/experiments/qubit_characterizations.py index 749649fb2b2..b4a6ccb4e60 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations.py +++ b/cirq-core/cirq/experiments/qubit_characterizations.py @@ -200,7 +200,7 @@ def plot_heatmap( plotted on, and shown. annotation_format: The format string for the numbers in the heatmap. title: The title printed above the heatmap. - ***plot_kwargs: Arguments to be passed to 'cirq.Heatmap.plot()'. + **plot_kwargs: Arguments to be passed to 'cirq.Heatmap.plot()'. Returns: The plt.Axes containing the plot. """ @@ -212,7 +212,7 @@ def plot_heatmap( if ax is None: _, ax = plt.subplots(dpi=200, facecolor='white') - ax, _ = cirq_heatmap.Heatmap(pauli_errors).plot( + ax, _ = cirq_heatmap.Heatmap(pauli_errors).plot( # type: ignore ax, annotation_format=annotation_format, title=title, **plot_kwargs ) return ax @@ -395,7 +395,7 @@ def single_qubit_randomized_benchmarking( num_circuits=num_circuits, repetitions=repetitions, ) - return result[qubit] + return result._results_dictionary[qubit] def parallel_single_qubit_randomized_benchmarking( From 325a70fbc3292010944bd7bdcab06b3738fc8e88 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Mon, 15 Jan 2024 21:03:34 -0800 Subject: [PATCH 5/7] add tests --- cirq-core/cirq/experiments/qubit_characterizations_test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cirq-core/cirq/experiments/qubit_characterizations_test.py b/cirq-core/cirq/experiments/qubit_characterizations_test.py index 29b2d69f20a..e2affe211a0 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations_test.py +++ b/cirq-core/cirq/experiments/qubit_characterizations_test.py @@ -128,6 +128,11 @@ def test_parallel_single_qubit_randomized_benchmarking(): for qubit in qubits: g_pops = np.asarray(results._results_dictionary[qubit].data)[:, 1] assert np.isclose(np.mean(g_pops), 1.0) + _ = results.plot_single_qubit(qubit) + pauli_errors = results.pauli_error() + assert len(pauli_errors) == len(qubits) + _ = results.plot_heatmap() + _ = results.plot_integrated_histogram() def test_two_qubit_randomized_benchmarking(): From 82fa43be072bad63d76b0f746a6b1e296d6ea2a3 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Tue, 16 Jan 2024 20:39:47 -0800 Subject: [PATCH 6/7] nits, etc --- .../experiments/qubit_characterizations.py | 22 ++++++++----------- .../qubit_characterizations_test.py | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/cirq-core/cirq/experiments/qubit_characterizations.py b/cirq-core/cirq/experiments/qubit_characterizations.py index b4a6ccb4e60..e93eb770710 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations.py +++ b/cirq-core/cirq/experiments/qubit_characterizations.py @@ -148,16 +148,11 @@ def _fit_exponential(self) -> Tuple[np.ndarray, np.ndarray]: ) +@dataclasses.dataclass(frozen=True) class ParallelRandomizedBenchmarkingResult: """Results from a parallel randomized benchmarking experiment.""" - def __init__(self, results_dictionary: Mapping['cirq.Qid', 'RandomizedBenchMarkResult']): - """Inits ParallelRandomizedBenchmarkingResult. - - Args: - results_dictionary: A dictionary containing the results for each qubit. - """ - self._results_dictionary = results_dictionary + results_dictionary: Mapping['cirq.Qid', 'RandomizedBenchMarkResult'] def plot_single_qubit( self, qubit: 'cirq.Qid', ax: Optional[plt.Axes] = None, **plot_kwargs: Any @@ -173,7 +168,7 @@ def plot_single_qubit( The plt.Axes containing the plot. """ - return self._results_dictionary[qubit].plot(ax, **plot_kwargs) + return self.results_dictionary[qubit].plot(ax, **plot_kwargs) def pauli_error(self) -> Mapping['cirq.Qid', float]: """Return a dictionary of Pauli errors. @@ -182,8 +177,8 @@ def pauli_error(self) -> Mapping['cirq.Qid', float]: """ return { - qubit: self._results_dictionary[qubit].pauli_error() - for qubit in self._results_dictionary + qubit: self.results_dictionary[qubit].pauli_error() + for qubit in self.results_dictionary } def plot_heatmap( @@ -206,13 +201,15 @@ def plot_heatmap( """ pauli_errors = self.pauli_error() + pauli_errors_with_grid_qubit_keys = {} for qubit in pauli_errors: assert type(qubit) == grid_qubit.GridQubit, "qubits must be cirq.GridQubits" + pauli_errors_with_grid_qubit_keys[qubit] = pauli_errors[qubit] # just for typecheck if ax is None: _, ax = plt.subplots(dpi=200, facecolor='white') - ax, _ = cirq_heatmap.Heatmap(pauli_errors).plot( # type: ignore + ax, _ = cirq_heatmap.Heatmap(pauli_errors_with_grid_qubit_keys).plot( ax, annotation_format=annotation_format, title=title, **plot_kwargs ) return ax @@ -220,7 +217,6 @@ def plot_heatmap( def plot_integrated_histogram( self, ax: Optional[plt.Axes] = None, - *, cdf_on_x: bool = False, axis_label: str = 'Pauli error', semilog: bool = True, @@ -395,7 +391,7 @@ def single_qubit_randomized_benchmarking( num_circuits=num_circuits, repetitions=repetitions, ) - return result._results_dictionary[qubit] + return result.results_dictionary[qubit] def parallel_single_qubit_randomized_benchmarking( diff --git a/cirq-core/cirq/experiments/qubit_characterizations_test.py b/cirq-core/cirq/experiments/qubit_characterizations_test.py index e2affe211a0..36d76606ade 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations_test.py +++ b/cirq-core/cirq/experiments/qubit_characterizations_test.py @@ -126,7 +126,7 @@ def test_parallel_single_qubit_randomized_benchmarking(): simulator, num_clifford_range=num_cfds, repetitions=100, qubits=qubits ) for qubit in qubits: - g_pops = np.asarray(results._results_dictionary[qubit].data)[:, 1] + g_pops = np.asarray(results.results_dictionary[qubit].data)[:, 1] assert np.isclose(np.mean(g_pops), 1.0) _ = results.plot_single_qubit(qubit) pauli_errors = results.pauli_error() From 9f1d9fc981946f9c2b74d1106fe35ce878089260 Mon Sep 17 00:00:00 2001 From: Eliott Rosenberg Date: Tue, 16 Jan 2024 20:41:18 -0800 Subject: [PATCH 7/7] format --- cirq-core/cirq/experiments/qubit_characterizations.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cirq-core/cirq/experiments/qubit_characterizations.py b/cirq-core/cirq/experiments/qubit_characterizations.py index e93eb770710..5d5a669ae8d 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations.py +++ b/cirq-core/cirq/experiments/qubit_characterizations.py @@ -177,8 +177,7 @@ def pauli_error(self) -> Mapping['cirq.Qid', float]: """ return { - qubit: self.results_dictionary[qubit].pauli_error() - for qubit in self.results_dictionary + qubit: self.results_dictionary[qubit].pauli_error() for qubit in self.results_dictionary } def plot_heatmap( @@ -204,7 +203,7 @@ def plot_heatmap( pauli_errors_with_grid_qubit_keys = {} for qubit in pauli_errors: assert type(qubit) == grid_qubit.GridQubit, "qubits must be cirq.GridQubits" - pauli_errors_with_grid_qubit_keys[qubit] = pauli_errors[qubit] # just for typecheck + pauli_errors_with_grid_qubit_keys[qubit] = pauli_errors[qubit] # just for typecheck if ax is None: _, ax = plt.subplots(dpi=200, facecolor='white')