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
+ .. note::
35
+
36
+ This plotter only supports one series, named ``hops``, which it expects
37
+ to have an ``individual`` data key containing the individual heavy
38
+ output probabilities for each circuit in the experiment. Additional
39
+ series will be ignored.
40
+ """
41
+
42
+ @classmethod
43
+ def expected_series_data_keys (cls ) -> List [str ]:
44
+ """Returns the expected series data keys supported by this plotter.
45
+
46
+ Data Keys:
47
+ individual: Heavy-output probability fraction for each individual circuit
48
+ """
49
+ return ["individual" ]
50
+
51
+ @classmethod
52
+ def expected_supplementary_data_keys (cls ) -> List [str ]:
53
+ """Returns the expected figures data keys supported by this plotter.
54
+
55
+ Data Keys:
56
+ depth: The depth of the quantun volume circuits used in the experiment
57
+ """
58
+ return ["depth" ]
59
+
60
+ def set_supplementary_data (self , ** data_kwargs ):
61
+ """Sets supplementary data for the plotter.
62
+
63
+ Args:
64
+ data_kwargs: See :meth:`expected_supplementary_data_keys` for the
65
+ expected supplementary data keys.
66
+ """
67
+ # Hook method to capture the depth for inclusion in the plot title
68
+ if "depth" in data_kwargs :
69
+ self .set_figure_options (
70
+ figure_title = (
71
+ f"Quantum Volume experiment for depth { data_kwargs ['depth' ]} "
72
+ " - accumulative hop"
73
+ ),
74
+ )
75
+ super ().set_supplementary_data (** data_kwargs )
76
+
77
+ @classmethod
78
+ def _default_figure_options (cls ) -> Options :
79
+ options = super ()._default_figure_options ()
80
+ options .xlabel = "Number of Trials"
81
+ options .ylabel = "Heavy Output Probability"
82
+ options .figure_title = "Quantum Volume experiment - accumulative hop"
83
+ options .series_params = {
84
+ "hop" : {"color" : "gray" , "symbol" : "." },
85
+ "threshold" : {"color" : "black" , "linestyle" : "dashed" , "linewidth" : 1 },
86
+ "hop_cumulative" : {"color" : "r" },
87
+ "hop_twosigma" : {"color" : "lightgray" },
88
+ }
89
+ return options
90
+
91
+ @classmethod
92
+ def _default_options (cls ) -> Options :
93
+ options = super ()._default_options ()
94
+ options .style ["figsize" ] = (6.4 , 4.8 )
95
+ options .style ["axis_label_size" ] = 14
96
+ options .style ["symbol_size" ] = 2
97
+ return options
98
+
99
+ def _plot_figure (self ):
100
+ (hops ,) = self .data_for ("hops" , ["individual" ])
101
+ trials = np .arange (1 , 1 + len (hops ))
102
+ hop_accumulative = np .cumsum (hops ) / trials
103
+ hop_twosigma = 2 * (hop_accumulative * (1 - hop_accumulative ) / trials ) ** 0.5
104
+
105
+ self .drawer .line (
106
+ trials ,
107
+ hop_accumulative ,
108
+ name = "hop_cumulative" ,
109
+ label = "Cumulative HOP" ,
110
+ legend = True ,
111
+ )
112
+ self .drawer .hline (
113
+ 2 / 3 ,
114
+ name = "threshold" ,
115
+ label = "Threshold" ,
116
+ legend = True ,
117
+ )
118
+ self .drawer .scatter (
119
+ trials ,
120
+ hops ,
121
+ name = "hop" ,
122
+ label = "Individual HOP" ,
123
+ legend = True ,
124
+ linewidth = 1.5 ,
125
+ )
126
+ self .drawer .filled_y_area (
127
+ trials ,
128
+ hop_accumulative - hop_twosigma ,
129
+ hop_accumulative + hop_twosigma ,
130
+ alpha = 0.5 ,
131
+ legend = True ,
132
+ name = "hop_twosigma" ,
133
+ label = "2σ" ,
134
+ )
135
+
136
+ self .drawer .set_figure_options (
137
+ ylim = (
138
+ max (hop_accumulative [- 1 ] - 4 * hop_twosigma [- 1 ], 0 ),
139
+ min (hop_accumulative [- 1 ] + 4 * hop_twosigma [- 1 ], 1 ),
140
+ ),
141
+ )
29
142
30
143
31
144
class QuantumVolumeAnalysis (BaseAnalysis ):
@@ -49,10 +162,12 @@ def _default_options(cls) -> Options:
49
162
Analysis Options:
50
163
plot (bool): Set ``True`` to create figure for fit result.
51
164
ax (AxesSubplot): Optional. A matplotlib axis object to draw.
165
+ plotter (BasePlotter): Plotter object to use for figure generation.
52
166
"""
53
167
options = super ()._default_options ()
54
168
options .plot = True
55
169
options .ax = None
170
+ options .plotter = QuantumVolumePlotter (MplDrawer ())
56
171
return options
57
172
58
173
def _run_analysis (self , experiment_data ):
@@ -77,8 +192,9 @@ def _run_analysis(self, experiment_data):
77
192
hop_result , qv_result = self ._calc_quantum_volume (heavy_output_prob_exp , depth , num_trials )
78
193
79
194
if self .options .plot :
80
- ax = self ._format_plot (hop_result , ax = self .options .ax )
81
- figures = [ax .get_figure ()]
195
+ self .options .plotter .set_series_data ("hops" , individual = hop_result .extra ["HOPs" ])
196
+ self .options .plotter .set_supplementary_data (depth = hop_result .extra ["depth" ])
197
+ figures = [self .options .plotter .figure ()]
82
198
else :
83
199
figures = None
84
200
return [hop_result , qv_result ], figures
@@ -238,73 +354,3 @@ def _calc_quantum_volume(self, heavy_output_prob_exp, depth, trials):
238
354
},
239
355
)
240
356
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