Skip to content

Commit e42eac2

Browse files
Update RB to reduce the size of the generated circuits (#7411)
the cirq RB sequences were often 2-3 times bigger than they should, this is because for historical reasons the cliffords were written as products of X/Z or X/Y gates. In this PR I merge these gates into a single PhasedXZGate which is the same as we do internally. --------- Co-authored-by: Pavol Juhas <[email protected]>
1 parent 68c5741 commit e42eac2

File tree

5 files changed

+169
-33
lines changed

5 files changed

+169
-33
lines changed

cirq-core/cirq/experiments/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515

1616
from cirq.experiments.qubit_characterizations import (
1717
RandomizedBenchMarkResult as RandomizedBenchMarkResult,
18+
RBParameters as RBParameters,
1819
single_qubit_randomized_benchmarking as single_qubit_randomized_benchmarking,
20+
single_qubit_rb as single_qubit_rb,
1921
single_qubit_state_tomography as single_qubit_state_tomography,
2022
TomographyResult as TomographyResult,
2123
two_qubit_randomized_benchmarking as two_qubit_randomized_benchmarking,
2224
two_qubit_state_tomography as two_qubit_state_tomography,
2325
parallel_single_qubit_randomized_benchmarking as parallel_single_qubit_randomized_benchmarking,
26+
parallel_single_qubit_rb as parallel_single_qubit_rb,
2427
)
2528

2629
from cirq.experiments.fidelity_estimation import (

cirq-core/cirq/experiments/qubit_characterizations.py

Lines changed: 134 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import itertools
2020
from typing import Any, cast, Iterator, Mapping, Sequence, TYPE_CHECKING
2121

22+
import attrs
2223
import numpy as np
2324
from matplotlib import pyplot as plt
2425

@@ -28,6 +29,7 @@
2829
import cirq.vis.heatmap as cirq_heatmap
2930
import cirq.vis.histogram as cirq_histogram
3031
from cirq import circuits, ops, protocols
32+
from cirq._compat import deprecated
3133
from cirq.devices import grid_qubit
3234

3335
if TYPE_CHECKING:
@@ -36,6 +38,12 @@
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
4048
class 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')
336382
def 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')
390439
def 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

679783
def _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

732841
def _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)

cirq-core/cirq/experiments/qubit_characterizations_test.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
from __future__ import annotations
1616

17+
import os
18+
from unittest import mock
19+
1720
import matplotlib.pyplot as plt
1821
import numpy as np
1922
import pytest
@@ -104,6 +107,7 @@ def check_distinct(unitaries):
104107
assert num_x <= 1
105108

106109

110+
@mock.patch.dict(os.environ, clear='CIRQ_TESTING')
107111
def test_single_qubit_randomized_benchmarking():
108112
# Check that the ground state population at the end of the Clifford
109113
# sequences is always unity.
@@ -116,7 +120,8 @@ def test_single_qubit_randomized_benchmarking():
116120
assert np.isclose(results.pauli_error(), 0.0, atol=1e-7) # warning is expected
117121

118122

119-
def test_parallel_single_qubit_randomized_benchmarking():
123+
@mock.patch.dict(os.environ, clear='CIRQ_TESTING')
124+
def test_parallel_single_qubit_parallel_single_qubit_randomized_benchmarking():
120125
# Check that the ground state population at the end of the Clifford
121126
# sequences is always unity.
122127
simulator = sim.Simulator()
@@ -229,13 +234,24 @@ def test_tomography_plot_raises_for_incorrect_number_of_axes():
229234
result.plot(axes)
230235

231236

232-
def test_single_qubit_cliffords_gateset():
237+
@pytest.mark.parametrize('num_cliffords', range(5, 10))
238+
@pytest.mark.parametrize('use_xy_basis', [False, True])
239+
@pytest.mark.parametrize('strict_basis', [False, True])
240+
def test_single_qubit_cliffords_gateset(num_cliffords, use_xy_basis, strict_basis):
233241
qubits = [GridQubit(0, i) for i in range(4)]
234-
clifford_group = cirq.experiments.qubit_characterizations._single_qubit_cliffords()
242+
c1_in_xy = cirq.experiments.qubit_characterizations.RBParameters(
243+
use_xy_basis=use_xy_basis, strict_basis=strict_basis
244+
).gateset()
245+
if strict_basis:
246+
assert len(c1_in_xy) == 20
247+
else:
248+
assert len(c1_in_xy) == 24
235249
c = cirq.experiments.qubit_characterizations._create_parallel_rb_circuit(
236-
qubits, 5, clifford_group.c1_in_xy
250+
qubits, num_cliffords, c1_in_xy
237251
)
238252
device = cirq.testing.ValidatingTestDevice(
239253
qubits=qubits, allowed_gates=(cirq.ops.PhasedXZGate, cirq.MeasurementGate)
240254
)
241255
device.validate_circuit(c)
256+
257+
assert len(c) == num_cliffords + 2

cirq-core/cirq/experiments/two_qubit_xeb.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030
from cirq._compat import cached_method
3131
from cirq.experiments import random_quantum_circuit_generation as rqcg
3232
from cirq.experiments.qubit_characterizations import (
33-
parallel_single_qubit_randomized_benchmarking,
33+
parallel_single_qubit_rb,
3434
ParallelRandomizedBenchmarkingResult,
35+
RBParameters,
3536
)
3637
from cirq.experiments.xeb_fitting import (
3738
benchmark_2q_xeb_fidelities,
@@ -586,12 +587,14 @@ def run_rb_and_xeb(
586587

587588
qubits, pairs = qubits_and_pairs(sampler, qubits, pairs)
588589

589-
rb = parallel_single_qubit_randomized_benchmarking(
590+
rb = parallel_single_qubit_rb(
590591
sampler=sampler,
591592
qubits=qubits,
592-
repetitions=repetitions,
593-
num_circuits=num_circuits,
594-
num_clifford_range=num_clifford_range,
593+
parameters=RBParameters(
594+
num_circuits=num_circuits,
595+
repetitions=repetitions,
596+
num_clifford_range=num_clifford_range,
597+
),
595598
)
596599

597600
xeb = parallel_two_qubit_xeb(

examples/qubit_characterizations_example.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ def main(minimum_cliffords=5, maximum_cliffords=20, cliffords_step=5):
3434
clifford_range = range(minimum_cliffords, maximum_cliffords, cliffords_step)
3535

3636
# Clifford-based randomized benchmarking of single-qubit gates on q_0.
37-
rb_result_1q = cirq.experiments.single_qubit_randomized_benchmarking(
38-
simulator, q_0, num_clifford_range=clifford_range, repetitions=100
37+
rb_result_1q = cirq.experiments.single_qubit_rb(
38+
simulator,
39+
q_0,
40+
cirq.experiments.RBParameters(num_clifford_range=clifford_range, repetitions=100),
3941
)
4042
rb_result_1q.plot()
4143

0 commit comments

Comments
 (0)