Skip to content

Update RB to reduce the size of the generated circuits #7411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions cirq-core/cirq/experiments/qubit_characterizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import itertools
from typing import Any, cast, Iterator, Mapping, Sequence, TYPE_CHECKING

import attrs
import numpy as np
from matplotlib import pyplot as plt

Expand All @@ -36,7 +37,37 @@
import cirq


@dataclasses.dataclass
def _canonize_clifford_sequences(
sequences: list[list[ops.SingleQubitCliffordGate]],
) -> list[tuple[ops.SingleQubitCliffordGate]]:
return [(_reduce_gate_seq(seq),) for seq in sequences]


@attrs.frozen
class _CliffordGateSequence:
"""Wrap around a list of sequences of clifford gates.

This class wraps around a list of sequences of clifford gates and re-exposes them as
a list of tuple where each tuple contains a single clifford gates.
Comment on lines +50 to +51
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - clifford --> Clifford

"""

gate_sequence: list[list[ops.SingleQubitCliffordGate]]

@functools.cached_property
def _reduced_gate_sequence(self) -> list[tuple[ops.SingleQubitCliffordGate]]:
return _canonize_clifford_sequences(self.gate_sequence)

def __iter__(self) -> Iterator[tuple[ops.SingleQubitCliffordGate]]:
yield from self._reduced_gate_sequence

def __getitem__(self, idx) -> tuple[ops.SingleQubitCliffordGate]:
return self._reduced_gate_sequence[idx]

def __len__(self) -> int:
return len(self._reduced_gate_sequence)


@attrs.frozen
class Cliffords:
"""The single-qubit Clifford group, decomposed into elementary gates.

Copy link
Collaborator

@pavoljuhas pavoljuhas Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short note to docstring saying the arguments are converted to a reduced gate sequence.

Expand All @@ -54,11 +85,11 @@ class Cliffords:
s1_y
"""

c1_in_xy: list[list[ops.SingleQubitCliffordGate]]
c1_in_xz: list[list[ops.SingleQubitCliffordGate]]
s1: list[list[ops.SingleQubitCliffordGate]]
s1_x: list[list[ops.SingleQubitCliffordGate]]
s1_y: list[list[ops.SingleQubitCliffordGate]]
c1_in_xy: _CliffordGateSequence = attrs.field(converter=_CliffordGateSequence)
c1_in_xz: _CliffordGateSequence = attrs.field(converter=_CliffordGateSequence)
s1: _CliffordGateSequence = attrs.field(converter=_CliffordGateSequence)
s1_x: _CliffordGateSequence = attrs.field(converter=_CliffordGateSequence)
s1_y: _CliffordGateSequence = attrs.field(converter=_CliffordGateSequence)
Comment on lines +88 to +92
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, we should avoid the _CliffordGateSequence wrapper.
How about just keeping the original list type and using converter=_canonize_clifford_sequences?

Current unit test expands all fields so there is no time saved on their delayed conversion through _CliffordGateSequence.

Copy link
Collaborator Author

@NoureldinYosri NoureldinYosri Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that was the first version of the code, but I needed to add the class to keep the tests the explicitly check the content of the lists ... that is some tests check that the clifford are written in terms of X/Z or X/Y which wouldn't be possible if all they see is the merged gate

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then perhaps we can add some canonized: bool flag for the _single_qubit_cliffords factory which will return old Cliffords for the X/Z, X/Y test and canonized elsewhere. Would that work?



class RandomizedBenchMarkResult:
Expand Down Expand Up @@ -336,7 +367,7 @@ def plot(
def single_qubit_randomized_benchmarking(
sampler: cirq.Sampler,
qubit: cirq.Qid,
use_xy_basis: bool = True,
use_xy_basis: bool = False,
*,
num_clifford_range: Sequence[int] = tuple(np.logspace(np.log10(5), 3, 5, dtype=int)),
num_circuits: int = 10,
Expand Down Expand Up @@ -390,7 +421,7 @@ def single_qubit_randomized_benchmarking(
def parallel_single_qubit_randomized_benchmarking(
sampler: cirq.Sampler,
qubits: Sequence[cirq.Qid],
use_xy_basis: bool = True,
use_xy_basis: bool = False,
*,
num_clifford_range: Sequence[int] = tuple(
np.logspace(np.log10(5), np.log10(1000), 5, dtype=int)
Expand Down Expand Up @@ -677,7 +708,9 @@ def _measurement(two_qubit_circuit: circuits.Circuit) -> np.ndarray:


def _create_parallel_rb_circuit(
qubits: Sequence[cirq.Qid], num_cliffords: int, c1: list
qubits: Sequence[cirq.Qid],
num_cliffords: int,
c1: _CliffordGateSequence | list[list[ops.SingleQubitCliffordGate]],
) -> cirq.Circuit:
sequences_to_zip = [_random_single_q_clifford(qubit, num_cliffords, c1) for qubit in qubits]
# Ensure each sequence has the same number of moments.
Expand Down Expand Up @@ -730,7 +763,9 @@ def _two_qubit_clifford_matrices(q_0: cirq.Qid, q_1: cirq.Qid, cliffords: Cliffo


def _random_single_q_clifford(
qubit: cirq.Qid, num_cfds: int, cfds: Sequence[Sequence[cirq.ops.SingleQubitCliffordGate]]
qubit: cirq.Qid,
num_cfds: int,
cfds: Sequence[Sequence[cirq.ops.SingleQubitCliffordGate]] | _CliffordGateSequence,
) -> list[cirq.Operation]:
clifford_group_size = 24
operations = [[gate.to_phased_xz_gate()(qubit) for gate in gates] for gates in cfds]
Expand Down
9 changes: 6 additions & 3 deletions cirq-core/cirq/experiments/qubit_characterizations_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def check_distinct(unitaries):
assert is_pauli(u @ p @ u.conj().T), str(u)

# Check that XZ decomposition has at most one X gate per clifford.
for gates in cliffords.c1_in_xz:
for gates in cliffords.c1_in_xz.gate_sequence:
num_i = len([gate for gate in gates if gate == cirq.ops.SingleQubitCliffordGate.I])
num_x = len(
[
Expand Down Expand Up @@ -229,13 +229,16 @@ def test_tomography_plot_raises_for_incorrect_number_of_axes():
result.plot(axes)


def test_single_qubit_cliffords_gateset():
@pytest.mark.parametrize('num_cliffords', range(5, 10))
def test_single_qubit_cliffords_gateset(num_cliffords):
qubits = [GridQubit(0, i) for i in range(4)]
clifford_group = cirq.experiments.qubit_characterizations._single_qubit_cliffords()
c = cirq.experiments.qubit_characterizations._create_parallel_rb_circuit(
qubits, 5, clifford_group.c1_in_xy
qubits, num_cliffords, clifford_group.c1_in_xy
)
device = cirq.testing.ValidatingTestDevice(
qubits=qubits, allowed_gates=(cirq.ops.PhasedXZGate, cirq.MeasurementGate)
)
device.validate_circuit(c)

assert len(c) == num_cliffords + 2
Loading