1919import itertools
2020from typing import Any , cast , Iterator , Mapping , Sequence , TYPE_CHECKING
2121
22+ import attrs
2223import numpy as np
2324from matplotlib import pyplot as plt
2425
2829import cirq .vis .heatmap as cirq_heatmap
2930import cirq .vis .histogram as cirq_histogram
3031from cirq import circuits , ops , protocols
32+ from cirq ._compat import deprecated
3133from cirq .devices import grid_qubit
3234
3335if TYPE_CHECKING :
3638 import cirq
3739
3840
41+ def _canonize_clifford_sequences (
42+ sequences : list [list [ops .SingleQubitCliffordGate ]],
43+ ) -> list [list [ops .SingleQubitCliffordGate ]]:
44+ return [[_reduce_gate_seq (seq )] for seq in sequences ]
45+
46+
3947@dataclasses .dataclass
4048class Cliffords :
4149 """The single-qubit Clifford group, decomposed into elementary gates.
@@ -134,7 +142,7 @@ def _fit_exponential(self) -> tuple[np.ndarray, np.ndarray]:
134142 xdata = self ._num_cfds_seq ,
135143 ydata = self ._gnd_state_probs ,
136144 p0 = [0.5 , 0.5 , 1.0 - 1e-3 ],
137- bounds = ([0 , 0.25 , 0 ], [0.5 , 0.75 , 1 ]),
145+ bounds = ([0 , - 1 , 0 ], [1 , 1 , 1 ]),
138146 )
139147
140148
@@ -333,6 +341,44 @@ def plot(
333341 return axes
334342
335343
344+ @attrs .frozen
345+ class RBParameters :
346+ r"""Parameters for running randomized benchmarking.
347+
348+ Arguments:
349+ num_clifford_range: The different numbers of Cliffords in the RB study.
350+ num_circuits: The number of random circuits generated for each
351+ number of Cliffords.
352+ repetitions: The number of repetitions of each circuit.
353+ use_xy_basis: Determines if the Clifford gates are built with x and y
354+ rotations (True) or x and z rotations (False).
355+ strict_basis: whether to use only cliffords that can be represented by at
356+ most 2 gates of the choses basis. For example,
357+ if True and use_xy_basis is True, this excludes $I, Z, \sqrt(Z), \-sqrt(Z)^\dagger$.
358+ if True and use_xy_basis is False, this excludes $I, Y, \sqrt(Y), -\sqrt(Y)^\dagger$.
359+ """
360+
361+ num_clifford_range : Sequence [int ] = tuple (np .logspace (np .log10 (5 ), 3 , 5 , dtype = int ))
362+ num_circuits : int = 10
363+ repetitions : int = 600
364+ use_xy_basis : bool = False
365+ strict_basis : bool = True
366+
367+ def gateset (self ) -> list [list [ops .SingleQubitCliffordGate ]]:
368+ clifford_group = _single_qubit_cliffords ()
369+ sequences = clifford_group .c1_in_xy if self .use_xy_basis else clifford_group .c1_in_xz
370+ sequences = _canonize_clifford_sequences (sequences )
371+ if self .strict_basis :
372+ if self .use_xy_basis :
373+ excluded_gates = ops .Gateset (ops .I , ops .Z , ops .Z ** 0.5 , ops .Z ** - 0.5 )
374+ else :
375+ excluded_gates = ops .Gateset (ops .I , ops .Y , ops .Y ** 0.5 , ops .Y ** - 0.5 )
376+
377+ sequences = [[g ] for (g ,) in sequences if g not in excluded_gates ]
378+ return sequences
379+
380+
381+ @deprecated (deadline = 'v2.0' , fix = 'please use single_qubit_rb instead' )
336382def single_qubit_randomized_benchmarking (
337383 sampler : cirq .Sampler ,
338384 qubit : cirq .Qid ,
@@ -376,17 +422,20 @@ def single_qubit_randomized_benchmarking(
376422 A RandomizedBenchMarkResult object that stores and plots the result.
377423 """
378424
379- result = parallel_single_qubit_randomized_benchmarking (
425+ return single_qubit_rb (
380426 sampler ,
381- (qubit ,),
382- use_xy_basis ,
383- num_clifford_range = num_clifford_range ,
384- num_circuits = num_circuits ,
385- repetitions = repetitions ,
427+ qubit ,
428+ RBParameters (
429+ num_clifford_range = num_clifford_range ,
430+ num_circuits = num_circuits ,
431+ repetitions = repetitions ,
432+ use_xy_basis = use_xy_basis ,
433+ strict_basis = False ,
434+ ),
386435 )
387- return result .results_dictionary [qubit ]
388436
389437
438+ @deprecated (deadline = 'v2.0' , fix = 'please use parallel_single_qubit_rb instead' )
390439def parallel_single_qubit_randomized_benchmarking (
391440 sampler : cirq .Sampler ,
392441 qubits : Sequence [cirq .Qid ],
@@ -413,35 +462,90 @@ def parallel_single_qubit_randomized_benchmarking(
413462 num_circuits: The number of random circuits generated for each
414463 number of Cliffords.
415464 repetitions: The number of repetitions of each circuit.
465+ Returns:
466+ A dictionary from qubits to RandomizedBenchMarkResult objects.
467+ """
468+ return parallel_single_qubit_rb (
469+ sampler ,
470+ qubits ,
471+ RBParameters (
472+ num_clifford_range = num_clifford_range ,
473+ num_circuits = num_circuits ,
474+ repetitions = repetitions ,
475+ use_xy_basis = use_xy_basis ,
476+ strict_basis = False ,
477+ ),
478+ )
479+
480+
481+ def single_qubit_rb (
482+ sampler : cirq .Sampler ,
483+ qubit : cirq .Qid ,
484+ parameters : RBParameters = RBParameters (),
485+ rng_or_seed : np .random .Generator | int | None = None ,
486+ ) -> RandomizedBenchMarkResult :
487+ """Clifford-based randomized benchmarking (RB) on a single qubit.
488+
489+ Args:
490+ sampler: The quantum engine or simulator to run the circuits.
491+ qubit: The qubit(s) to benchmark.
492+ parameters: The parameters of the experiment.
493+ rng_or_seed: A np.random.Generator object or seed.
494+ Returns:
495+ A dictionary from qubits to RandomizedBenchMarkResult objects.
496+ """
497+ return parallel_single_qubit_rb (sampler , [qubit ], parameters , rng_or_seed ).results_dictionary [
498+ qubit
499+ ]
500+
501+
502+ def parallel_single_qubit_rb (
503+ sampler : cirq .Sampler ,
504+ qubits : Sequence [cirq .Qid ],
505+ parameters : RBParameters = RBParameters (),
506+ rng_or_seed : np .random .Generator | int | None = None ,
507+ ) -> ParallelRandomizedBenchmarkingResult :
508+ """Clifford-based randomized benchmarking (RB) single qubits in parallel.
416509
510+ Args:
511+ sampler: The quantum engine or simulator to run the circuits.
512+ qubits: The qubit(s) to benchmark.
513+ parameters: The parameters of the experiment.
514+ rng_or_seed: A np.random.Generator object or seed.
417515 Returns:
418516 A dictionary from qubits to RandomizedBenchMarkResult objects.
419517 """
420518
421- clifford_group = _single_qubit_cliffords ()
422- c1 = clifford_group .c1_in_xy if use_xy_basis else clifford_group .c1_in_xz
519+ rng_or_seed = (
520+ rng_or_seed
521+ if isinstance (rng_or_seed , np .random .Generator )
522+ else np .random .default_rng (rng_or_seed )
523+ )
524+
525+ c1 = parameters .gateset ()
423526
424527 # create circuits
425528 circuits_all : list [cirq .AbstractCircuit ] = []
426- for num_cliffords in num_clifford_range :
427- for _ in range (num_circuits ):
428- circuits_all .append (_create_parallel_rb_circuit (qubits , num_cliffords , c1 ))
529+ for num_cliffords in parameters . num_clifford_range :
530+ for _ in range (parameters . num_circuits ):
531+ circuits_all .append (_create_parallel_rb_circuit (qubits , num_cliffords , c1 , rng_or_seed ))
429532
430533 # run circuits
431- results = sampler .run_batch (circuits_all , repetitions = repetitions )
534+ results = sampler .run_batch (circuits_all , repetitions = parameters . repetitions )
432535 gnd_probs : dict = {q : [] for q in qubits }
433536 idx = 0
434- for num_cliffords in num_clifford_range :
537+ for num_cliffords in parameters . num_clifford_range :
435538 excited_probs : dict [cirq .Qid , list [float ]] = {q : [] for q in qubits }
436- for _ in range (num_circuits ):
539+ for _ in range (parameters . num_circuits ):
437540 result = results [idx ][0 ]
438541 for qubit in qubits :
439542 excited_probs [qubit ].append (np .mean (result .measurements [str (qubit )]))
440543 idx += 1
441544 for qubit in qubits :
442545 gnd_probs [qubit ].append (1.0 - np .mean (excited_probs [qubit ]))
546+
443547 return ParallelRandomizedBenchmarkingResult (
444- {q : RandomizedBenchMarkResult (num_clifford_range , gnd_probs [q ]) for q in qubits }
548+ {q : RandomizedBenchMarkResult (parameters . num_clifford_range , gnd_probs [q ]) for q in qubits }
445549 )
446550
447551
@@ -677,9 +781,14 @@ def _measurement(two_qubit_circuit: circuits.Circuit) -> np.ndarray:
677781
678782
679783def _create_parallel_rb_circuit (
680- qubits : Sequence [cirq .Qid ], num_cliffords : int , c1 : list
784+ qubits : Sequence [cirq .Qid ],
785+ num_cliffords : int ,
786+ c1 : list [list [ops .SingleQubitCliffordGate ]],
787+ rng : np .random .Generator | None = None ,
681788) -> cirq .Circuit :
682- sequences_to_zip = [_random_single_q_clifford (qubit , num_cliffords , c1 ) for qubit in qubits ]
789+ sequences_to_zip = [
790+ _random_single_q_clifford (qubit , num_cliffords , c1 , rng ) for qubit in qubits
791+ ]
683792 # Ensure each sequence has the same number of moments.
684793 num_moments = max (len (sequence ) for sequence in sequences_to_zip )
685794 for q , sequence in zip (qubits , sequences_to_zip ):
@@ -730,11 +839,14 @@ def _two_qubit_clifford_matrices(q_0: cirq.Qid, q_1: cirq.Qid, cliffords: Cliffo
730839
731840
732841def _random_single_q_clifford (
733- qubit : cirq .Qid , num_cfds : int , cfds : Sequence [Sequence [cirq .ops .SingleQubitCliffordGate ]]
842+ qubit : cirq .Qid ,
843+ num_cfds : int ,
844+ cfds : Sequence [Sequence [cirq .ops .SingleQubitCliffordGate ]],
845+ rng : np .random .Generator | None = None ,
734846) -> list [cirq .Operation ]:
735- clifford_group_size = 24
736847 operations = [[gate .to_phased_xz_gate ()(qubit ) for gate in gates ] for gates in cfds ]
737- gate_ids = np .random .choice (clifford_group_size , num_cfds ).tolist ()
848+ choice_fn = rng .choice if rng else np .random .choice
849+ gate_ids = choice_fn (len (cfds ), num_cfds ).tolist ()
738850 adjoint = _reduce_gate_seq ([gate for gate_id in gate_ids for gate in cfds [gate_id ]]) ** - 1
739851 return [op for gate_id in gate_ids for op in operations [gate_id ]] + [
740852 adjoint .to_phased_xz_gate ()(qubit )
0 commit comments