11
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
- from typing import Sequence , TYPE_CHECKING , Optional , Tuple , Dict
14
+ from typing import Sequence , TYPE_CHECKING , Optional , Tuple , Dict , cast , Mapping
15
15
16
16
from dataclasses import dataclass
17
+ from types import MappingProxyType
17
18
import itertools
18
19
import functools
19
20
22
23
import numpy as np
23
24
import pandas as pd
24
25
25
- from cirq import ops , devices , value , vis
26
+ from cirq import ops , value , vis
26
27
from cirq .experiments .xeb_sampling import sample_2q_xeb_circuits
27
28
from cirq .experiments .xeb_fitting import benchmark_2q_xeb_fidelities
28
29
from cirq .experiments .xeb_fitting import fit_exponential_decays , exponential_decay
29
30
from cirq .experiments import random_quantum_circuit_generation as rqcg
31
+ from cirq .experiments .qubit_characterizations import ParallelRandomizedBenchmarkingResult
32
+ from cirq .qis import noise_utils
33
+ from cirq ._compat import cached_method
30
34
31
35
if TYPE_CHECKING :
32
36
import cirq
33
37
34
38
35
- def _grid_qubits_for_sampler (sampler : 'cirq.Sampler' ):
39
+ def _grid_qubits_for_sampler (sampler : 'cirq.Sampler' ) -> Optional [ Sequence [ 'cirq.GridQubit' ]] :
36
40
if hasattr (sampler , 'processor' ):
37
41
device = sampler .processor .get_device ()
38
42
return sorted (device .metadata .qubit_set )
39
- else :
40
- qubits = devices .GridQubit .rect (3 , 2 , 4 , 3 )
41
- # Delete one qubit from the rectangular arangement to
42
- # 1) make it irregular 2) simplify simulation.
43
- return qubits [:- 1 ]
43
+ return None
44
44
45
45
46
46
def _manhattan_distance (qubit1 : 'cirq.GridQubit' , qubit2 : 'cirq.GridQubit' ) -> int :
@@ -65,7 +65,7 @@ def all_qubit_pairs(self) -> Tuple[Tuple['cirq.GridQubit', 'cirq.GridQubit'], ..
65
65
return tuple (sorted (self ._qubit_pair_map .keys ()))
66
66
67
67
def plot_heatmap (self , ax : Optional [plt .Axes ] = None , ** plot_kwargs ) -> plt .Axes :
68
- """plot the heatmap for xeb error .
68
+ """plot the heatmap of XEB errors .
69
69
70
70
Args:
71
71
ax: the plt.Axes to plot on. If not given, a new figure is created,
@@ -75,7 +75,6 @@ def plot_heatmap(self, ax: Optional[plt.Axes] = None, **plot_kwargs) -> plt.Axes
75
75
show_plot = not ax
76
76
if not isinstance (ax , plt .Axes ):
77
77
fig , ax = plt .subplots (1 , 1 , figsize = (8 , 8 ))
78
-
79
78
heatmap_data : Dict [Tuple ['cirq.GridQubit' , ...], float ] = {
80
79
pair : self .xeb_error (* pair ) for pair in self .all_qubit_pairs
81
80
}
@@ -131,10 +130,13 @@ def _record(self, q0, q1) -> pd.Series:
131
130
q0 , q1 = q1 , q0
132
131
return self .fidelities .iloc [self ._qubit_pair_map [(q0 , q1 )]]
133
132
133
+ def xeb_fidelity (self , q0 : 'cirq.GridQubit' , q1 : 'cirq.GridQubit' ) -> float :
134
+ """Return the XEB fidelity of a qubit pair."""
135
+ return self ._record (q0 , q1 ).layer_fid
136
+
134
137
def xeb_error (self , q0 : 'cirq.GridQubit' , q1 : 'cirq.GridQubit' ) -> float :
135
138
"""Return the XEB error of a qubit pair."""
136
- p = self ._record (q0 , q1 ).layer_fid
137
- return 1 - p
139
+ return 1 - self .xeb_fidelity (q0 , q1 )
138
140
139
141
def all_errors (self ) -> Dict [Tuple ['cirq.GridQubit' , 'cirq.GridQubit' ], float ]:
140
142
"""Return the XEB error of all qubit pairs."""
@@ -156,9 +158,163 @@ def plot_histogram(self, ax: Optional[plt.Axes] = None, **plot_kwargs) -> plt.Ax
156
158
fig .show (** plot_kwargs )
157
159
return ax
158
160
161
+ @cached_method
162
+ def pauli_error (self ) -> Dict [Tuple ['cirq.GridQubit' , 'cirq.GridQubit' ], float ]:
163
+ """Return the Pauli error of all qubit pairs."""
164
+ return {
165
+ pair : noise_utils .decay_constant_to_pauli_error (
166
+ noise_utils .xeb_fidelity_to_decay_constant (self .xeb_fidelity (* pair ), num_qubits = 2 ),
167
+ num_qubits = 2 ,
168
+ )
169
+ for pair in self .all_qubit_pairs
170
+ }
171
+
172
+
173
+ @dataclass (frozen = True )
174
+ class InferredXEBResult :
175
+ """Uses the results from XEB and RB to compute inferred two-qubit Pauli errors."""
176
+
177
+ rb_result : ParallelRandomizedBenchmarkingResult
178
+ xeb_result : TwoQubitXEBResult
179
+
180
+ @property
181
+ def all_qubit_pairs (self ) -> Sequence [Tuple ['cirq.GridQubit' , 'cirq.GridQubit' ]]:
182
+ return self .xeb_result .all_qubit_pairs
183
+
184
+ @cached_method
185
+ def single_qubit_pauli_error (self ) -> Mapping ['cirq.Qid' , float ]:
186
+ """Return the single-qubit Pauli error for all qubits (RB results)."""
187
+ return self .rb_result .pauli_error ()
188
+
189
+ @cached_method
190
+ def two_qubit_pauli_error (self ) -> Mapping [Tuple ['cirq.GridQubit' , 'cirq.GridQubit' ], float ]:
191
+ """Return the two-qubit Pauli error for all pairs."""
192
+ return MappingProxyType (self .xeb_result .pauli_error ())
193
+
194
+ @cached_method
195
+ def inferred_pauli_error (self ) -> Mapping [Tuple ['cirq.GridQubit' , 'cirq.GridQubit' ], float ]:
196
+ """Return the inferred Pauli error for all pairs."""
197
+ single_q_paulis = self .rb_result .pauli_error ()
198
+ xeb = self .xeb_result .pauli_error ()
199
+
200
+ def _pauli_error (q0 : 'cirq.GridQubit' , q1 : 'cirq.GridQubit' ) -> float :
201
+ q0 , q1 = sorted ([q0 , q1 ])
202
+ return xeb [(q0 , q1 )] - single_q_paulis [q0 ] - single_q_paulis [q1 ]
203
+
204
+ return MappingProxyType ({pair : _pauli_error (* pair ) for pair in self .all_qubit_pairs })
205
+
206
+ @cached_method
207
+ def inferred_decay_constant (self ) -> Mapping [Tuple ['cirq.GridQubit' , 'cirq.GridQubit' ], float ]:
208
+ """Return the inferred decay constant for all pairs."""
209
+ return MappingProxyType (
210
+ {
211
+ pair : noise_utils .pauli_error_to_decay_constant (pauli , 2 )
212
+ for pair , pauli in self .inferred_pauli_error ().items ()
213
+ }
214
+ )
215
+
216
+ @cached_method
217
+ def inferred_xeb_error (self ) -> Mapping [Tuple ['cirq.GridQubit' , 'cirq.GridQubit' ], float ]:
218
+ """Return the inferred XEB error for all pairs."""
219
+ return MappingProxyType (
220
+ {
221
+ pair : 1 - noise_utils .decay_constant_to_xeb_fidelity (decay , 2 )
222
+ for pair , decay in self .inferred_decay_constant ().items ()
223
+ }
224
+ )
225
+
226
+ def _target_errors (
227
+ self , target_error : str
228
+ ) -> Mapping [Tuple ['cirq.GridQubit' , 'cirq.GridQubit' ], float ]:
229
+ error_funcs = {
230
+ 'pauli' : self .inferred_pauli_error ,
231
+ 'decay_constant' : self .inferred_decay_constant ,
232
+ 'xeb' : self .inferred_xeb_error ,
233
+ }
234
+ return error_funcs [target_error ]()
235
+
236
+ def plot_heatmap (
237
+ self , target_error : str = 'pauli' , ax : Optional [plt .Axes ] = None , ** plot_kwargs
238
+ ) -> plt .Axes :
239
+ """plot the heatmap of the target errors.
240
+
241
+ Args:
242
+ target_error: The error to draw. Must be one of 'xeb', 'pauli', or 'decay_constant'
243
+ ax: the plt.Axes to plot on. If not given, a new figure is created,
244
+ plotted on, and shown.
245
+ **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
246
+ """
247
+ show_plot = not ax
248
+ if not isinstance (ax , plt .Axes ):
249
+ fig , ax = plt .subplots (1 , 1 , figsize = (8 , 8 ))
250
+ heatmap_data = cast (
251
+ Mapping [Tuple ['cirq.GridQubit' , ...], float ], self ._target_errors (target_error )
252
+ )
253
+
254
+ name = f'{ target_error } error' if target_error != 'decay_constant' else 'decay constant'
255
+ ax .set_title (f'device { name } heatmap' )
256
+
257
+ vis .TwoQubitInteractionHeatmap (heatmap_data ).plot (ax = ax , ** plot_kwargs )
258
+ if show_plot :
259
+ fig .show ()
260
+ return ax
261
+
262
+ def plot_histogram (
263
+ self ,
264
+ target_error : str = 'pauli' ,
265
+ ax : Optional [plt .Axes ] = None ,
266
+ kind : str = 'two_qubit' ,
267
+ ** plot_kwargs ,
268
+ ) -> plt .Axes :
269
+ """plot a histogram of target error.
270
+
271
+ Args:
272
+ target_error: The error to draw. Must be one of 'xeb', 'pauli', or 'decay_constant'
273
+ kind: Whether to plot the single-qubit RB errors ('single_qubit') or the
274
+ two-qubit inferred errors ('two_qubit') or both ('both').
275
+ ax: the plt.Axes to plot on. If not given, a new figure is created,
276
+ plotted on, and shown.
277
+ **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
278
+
279
+ Raises:
280
+ ValueError: If
281
+ - `kind` is not one of 'single_qubit', 'two_qubit', or 'both'.
282
+ - `target_error` is not one of 'pauli', 'xeb', or 'decay_constant'
283
+ - single qubit error is requested and `target_error` is not 'pauli'.
284
+ """
285
+ if kind not in ('single_qubit' , 'two_qubit' , 'both' ):
286
+ raise ValueError (
287
+ f"kind must be one of 'single_qubit', 'two_qubit', or 'both', not { kind } "
288
+ )
289
+ if kind != 'two_qubit' and target_error != 'pauli' :
290
+ raise ValueError (f'{ target_error } is not supported for single qubits' )
291
+ fig = None
292
+ if ax is None :
293
+ fig , ax = plt .subplots (1 , 1 , figsize = (8 , 8 ))
294
+
295
+ alpha = 0.5 if kind == 'both' else 1.0
296
+ if kind == 'single_qubit' or kind == 'both' :
297
+ self .rb_result .plot_integrated_histogram (
298
+ ax = ax , alpha = alpha , label = 'single qubit' , color = 'green' , ** plot_kwargs
299
+ )
300
+ if kind == 'two_qubit' or kind == 'both' :
301
+ vis .integrated_histogram (
302
+ data = self ._target_errors (target_error ),
303
+ ax = ax ,
304
+ alpha = alpha ,
305
+ label = 'two qubit' ,
306
+ color = 'blue' ,
307
+ ** plot_kwargs ,
308
+ )
309
+
310
+ if fig is not None :
311
+ fig .show (** plot_kwargs )
312
+ return ax
313
+
159
314
160
315
def parallel_two_qubit_xeb (
161
316
sampler : 'cirq.Sampler' ,
317
+ qubits : Optional [Sequence ['cirq.GridQubit' ]] = None ,
162
318
entangling_gate : 'cirq.Gate' = ops .CZ ,
163
319
n_repetitions : int = 10 ** 4 ,
164
320
n_combinations : int = 10 ,
@@ -172,6 +328,7 @@ def parallel_two_qubit_xeb(
172
328
173
329
Args:
174
330
sampler: The quantum engine or simulator to run the circuits.
331
+ qubits: Qubits under test. If none, uses all qubits on the sampler's device.
175
332
entangling_gate: The entangling gate to use.
176
333
n_repetitions: The number of repetitions to use.
177
334
n_combinations: The number of combinations to generate.
@@ -184,10 +341,17 @@ def parallel_two_qubit_xeb(
184
341
185
342
Returns:
186
343
A TwoQubitXEBResult object representing the results of the experiment.
344
+
345
+ Raises:
346
+ ValueError: If qubits are not specified and the sampler has no device.
187
347
"""
188
348
rs = value .parse_random_state (random_state )
189
349
190
- qubits = _grid_qubits_for_sampler (sampler )
350
+ if qubits is None :
351
+ qubits = _grid_qubits_for_sampler (sampler )
352
+ if qubits is None :
353
+ raise ValueError ("Couldn't determine qubits from sampler. Please specify them." )
354
+
191
355
graph = nx .Graph (
192
356
pair for pair in itertools .combinations (qubits , 2 ) if _manhattan_distance (* pair ) == 1
193
357
)
0 commit comments