15
15
16
16
import math
17
17
import warnings
18
- from typing import Optional
18
+ from typing import List
19
19
20
20
import numpy as np
21
21
import uncertainties
22
22
from qiskit_experiments .exceptions import AnalysisError
23
- from qiskit_experiments .curve_analysis .visualization import plot_scatter , plot_errorbar
24
23
from qiskit_experiments .framework import (
25
24
BaseAnalysis ,
26
25
AnalysisResultData ,
27
26
Options ,
28
27
)
28
+ from qiskit_experiments .visualization import BasePlotter , MplDrawer
29
+
30
+
31
+ class QuantumVolumePlotter (BasePlotter ):
32
+ """Plotter for QuantumVolumeAnalysis"""
33
+
34
+ @classmethod
35
+ def expected_series_data_keys (cls ) -> List [str ]:
36
+ """Returns the expected series data keys supported by this plotter.
37
+
38
+ Data Keys:
39
+ hops: Heavy-output probability fraction for each circuit
40
+ """
41
+ return ["hops" ]
42
+
43
+ @classmethod
44
+ def expected_supplementary_data_keys (cls ) -> List [str ]:
45
+ """Returns the expected figures data keys supported by this plotter.
46
+
47
+ Data Keys:
48
+ depth: The depth of the quantun volume circuits used in the experiment
49
+ """
50
+ return ["depth" ]
51
+
52
+ def set_supplementary_data (self , ** data_kwargs ):
53
+ """Sets supplementary data for the plotter.
54
+
55
+ Args:
56
+ data_kwargs: See :meth:`expected_supplementary_data_keys` for the
57
+ expected supplementary data keys.
58
+ """
59
+ # Hook method to capture the depth for inclusion in the plot title
60
+ if "depth" in data_kwargs :
61
+ self .set_figure_options (
62
+ figure_title = (
63
+ f"Quantum Volume experiment for depth { data_kwargs ['depth' ]} "
64
+ " - accumulative hop"
65
+ ),
66
+ )
67
+ super ().set_supplementary_data (** data_kwargs )
68
+
69
+ @classmethod
70
+ def _default_figure_options (cls ) -> Options :
71
+ options = super ()._default_figure_options ()
72
+ options .xlabel = "Number of Trials"
73
+ options .ylabel = "Heavy Output Probability"
74
+ options .figure_title = "Quantum Volume experiment - accumulative hop"
75
+ options .series_params = {
76
+ "hop" : {"color" : "gray" , "symbol" : "." },
77
+ "threshold" : {"color" : "black" , "linestyle" : "dashed" , "linewidth" : 1 },
78
+ "hop_cumulative" : {"color" : "r" },
79
+ "hop_twosigma" : {"color" : "lightgray" },
80
+ }
81
+ return options
82
+
83
+ @classmethod
84
+ def _default_options (cls ) -> Options :
85
+ options = super ()._default_options ()
86
+ options .style ["figsize" ] = (6.4 , 4.8 )
87
+ options .style ["axis_label_size" ] = 14
88
+ options .style ["symbol_size" ] = 2
89
+ return options
90
+
91
+ def _plot_figure (self ):
92
+ series = self .series [0 ]
93
+ (hops ,) = self .data_for (series , ["hops" ])
94
+ trials = np .arange (1 , 1 + len (hops ))
95
+ hop_accumulative = np .cumsum (hops ) / trials
96
+ hop_twosigma = 2 * (hop_accumulative * (1 - hop_accumulative ) / trials ) ** 0.5
97
+
98
+ self .drawer .line (
99
+ trials ,
100
+ hop_accumulative ,
101
+ name = "hop_cumulative" ,
102
+ label = "Cumulative HOP" ,
103
+ legend = True ,
104
+ )
105
+ self .drawer .hline (
106
+ 2 / 3 ,
107
+ name = "threshold" ,
108
+ label = "Threshold" ,
109
+ legend = True ,
110
+ )
111
+ self .drawer .scatter (
112
+ trials ,
113
+ hops ,
114
+ name = "hop" ,
115
+ label = "Individual HOP" ,
116
+ legend = True ,
117
+ linewidth = 1.5 ,
118
+ )
119
+ self .drawer .filled_y_area (
120
+ trials ,
121
+ hop_accumulative - hop_twosigma ,
122
+ hop_accumulative + hop_twosigma ,
123
+ alpha = 0.5 ,
124
+ legend = True ,
125
+ name = "hop_twosigma" ,
126
+ label = "2σ" ,
127
+ )
128
+
129
+ self .drawer .set_figure_options (
130
+ ylim = (
131
+ max (hop_accumulative [- 1 ] - 4 * hop_twosigma [- 1 ], 0 ),
132
+ min (hop_accumulative [- 1 ] + 4 * hop_twosigma [- 1 ], 1 ),
133
+ ),
134
+ )
29
135
30
136
31
137
class QuantumVolumeAnalysis (BaseAnalysis ):
@@ -49,10 +155,12 @@ def _default_options(cls) -> Options:
49
155
Analysis Options:
50
156
plot (bool): Set ``True`` to create figure for fit result.
51
157
ax (AxesSubplot): Optional. A matplotlib axis object to draw.
158
+ plotter (BasePlotter): Plotter object to use for figure generation.
52
159
"""
53
160
options = super ()._default_options ()
54
161
options .plot = True
55
162
options .ax = None
163
+ options .plotter = QuantumVolumePlotter (MplDrawer ())
56
164
return options
57
165
58
166
def _run_analysis (self , experiment_data ):
@@ -77,8 +185,9 @@ def _run_analysis(self, experiment_data):
77
185
hop_result , qv_result = self ._calc_quantum_volume (heavy_output_prob_exp , depth , num_trials )
78
186
79
187
if self .options .plot :
80
- ax = self ._format_plot (hop_result , ax = self .options .ax )
81
- figures = [ax .get_figure ()]
188
+ self .options .plotter .set_series_data ("hops" , hops = hop_result .extra ["HOPs" ])
189
+ self .options .plotter .set_supplementary_data (depth = hop_result .extra ["depth" ])
190
+ figures = [self .options .plotter .figure ()]
82
191
else :
83
192
figures = None
84
193
return [hop_result , qv_result ], figures
@@ -238,73 +347,3 @@ def _calc_quantum_volume(self, heavy_output_prob_exp, depth, trials):
238
347
},
239
348
)
240
349
return hop_result , qv_result
241
-
242
- @staticmethod
243
- def _format_plot (
244
- hop_result : AnalysisResultData , ax : Optional ["matplotlib.pyplot.AxesSubplot" ] = None
245
- ):
246
- """Format the QV plot
247
-
248
- Args:
249
- hop_result: the heavy output probability analysis result.
250
- ax: matplotlib axis to add plot to.
251
-
252
- Returns:
253
- AxesSubPlot: the matplotlib axes containing the plot.
254
- """
255
- trials = hop_result .extra ["trials" ]
256
- heavy_probs = hop_result .extra ["HOPs" ]
257
- trial_list = np .arange (1 , trials + 1 ) # x data
258
-
259
- hop_accumulative = np .cumsum (heavy_probs ) / trial_list
260
- two_sigma = 2 * (hop_accumulative * (1 - hop_accumulative ) / trial_list ) ** 0.5
261
-
262
- # Plot individual HOP as scatter
263
- ax = plot_scatter (
264
- trial_list ,
265
- heavy_probs ,
266
- ax = ax ,
267
- s = 3 ,
268
- zorder = 3 ,
269
- label = "Individual HOP" ,
270
- )
271
- # Plot accumulative HOP
272
- ax .plot (trial_list , hop_accumulative , color = "r" , label = "Cumulative HOP" )
273
-
274
- # Plot two-sigma shaded area
275
- ax = plot_errorbar (
276
- trial_list ,
277
- hop_accumulative ,
278
- two_sigma ,
279
- ax = ax ,
280
- fmt = "none" ,
281
- ecolor = "lightgray" ,
282
- elinewidth = 20 ,
283
- capsize = 0 ,
284
- alpha = 0.5 ,
285
- label = "2$\\ sigma$" ,
286
- )
287
- # Plot 2/3 success threshold
288
- ax .axhline (2 / 3 , color = "k" , linestyle = "dashed" , linewidth = 1 , label = "Threshold" )
289
-
290
- ax .set_ylim (
291
- max (hop_accumulative [- 1 ] - 4 * two_sigma [- 1 ], 0 ),
292
- min (hop_accumulative [- 1 ] + 4 * two_sigma [- 1 ], 1 ),
293
- )
294
-
295
- ax .set_xlabel ("Number of Trials" , fontsize = 14 )
296
- ax .set_ylabel ("Heavy Output Probability" , fontsize = 14 )
297
-
298
- ax .set_title (
299
- "Quantum Volume experiment for depth "
300
- + str (hop_result .extra ["depth" ])
301
- + " - accumulative hop" ,
302
- fontsize = 14 ,
303
- )
304
-
305
- # Re-arrange legend order
306
- handles , labels = ax .get_legend_handles_labels ()
307
- handles = [handles [1 ], handles [2 ], handles [0 ], handles [3 ]]
308
- labels = [labels [1 ], labels [2 ], labels [0 ], labels [3 ]]
309
- ax .legend (handles , labels )
310
- return ax
0 commit comments