Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions asv.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build_command": [
"PIP_NO_BUILD_ISOLATION=false python -m pip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}"
],
"install_command": ["in-dir={env_dir} python -m pip install {wheel_file} pytest"],
"branches": ["main"],
"dvcs": "git",
"environment_type": "virtualenv",
Expand Down
166 changes: 166 additions & 0 deletions benchmarks/circuit_construction_perf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Copyright 2026 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Performance tests for circuit construction."""

import itertools
from collections.abc import Sequence

import pandas
import pytest

import cirq


def rotated_surface_code_memory_z_cycle(
data_qubits: set[cirq.GridQubit],
z_measure_qubits: set[cirq.GridQubit],
x_measure_qubits: set[cirq.GridQubit],
z_order: Sequence[tuple[int, int]],
x_order: Sequence[tuple[int, int]],
) -> cirq.Circuit:
"""Constructs a circuit for a single round of rotated memory Z surface code.

Args:
data_qubits: data qubits for the surface code patch.
z_measure_qubits: measure qubits to measure Z stabilizers for surface code patch.
x_measure_qubits: measure qubits to measure X stabilizers for surface code patch.
z_order: Specifies the order in which the 2/4 data qubit neighbours of a Z measure qubit
should be processed.
x_order: Specifies the order in which the 2/4 data qubit neighbours of a X measure qubit
should be processed.

Returns:
A `cirq.Circuit` for a single round of rotated memory Z surface code cycle.
"""

circuit = cirq.Circuit()
circuit += cirq.Moment([cirq.H(q) for q in x_measure_qubits])
for k in range(4):
op_list = []
for measure_qubits, add, is_x in [
(x_measure_qubits, x_order[k], True),
(z_measure_qubits, z_order[k], False),
]:
for q_meas in measure_qubits:
q_data = q_meas + add
if q_data in data_qubits:
op_list.append(cirq.CNOT(q_meas, q_data) if is_x else cirq.CNOT(q_data, q_meas))
circuit += cirq.Moment(op_list)
circuit += cirq.Moment([cirq.H(q) for q in x_measure_qubits])
circuit += cirq.Moment(cirq.measure_each(*x_measure_qubits, *z_measure_qubits))
return circuit


def surface_code_circuit(
distance: int, num_rounds: int, moment_by_moment: bool = True
) -> cirq.Circuit:
"""Constructs a rotated memory Z surface code circuit with `distance` and `num_rounds`.

The circuit has `dxd` data qubits and `d ** 2 - 1` measure qubits, where `d` is the distance
of surface code. For more details on rotated surface codes and qubit indexing, see figure 13
https://arxiv.org/abs/1111.4022.

Args:
distance: Distance of the surface code.
num_rounds: Number of error correction rounds for memory Z experiment.
moment_by_moment: If True, the circuit is constructed moment-by-moment instead of
operation-by-operation. This is useful to benchmark different circuit construction
patterns for the same circuit.

Returns:
A `cirq.Circuit` for surface code memory Z experiment for `distance` and `num_rounds`.
"""

def ndrange(*ranges: tuple[int, ...]):
return itertools.product(*[range(*r) for r in ranges])

data_qubits = {cirq.q(2 * x + 1, 2 * y + 1) for x, y in ndrange((distance,), (distance,))}
z_measure_qubits = {
cirq.q(2 * x, 2 * y) for x, y in ndrange((1, distance), (distance + 1,)) if x % 2 != y % 2
}
x_measure_qubits = {
cirq.q(2 * x, 2 * y) for x, y in ndrange((distance + 1,), (1, distance)) if x % 2 == y % 2
}
x_order = [(1, 1), (1, -1), (-1, 1), (-1, -1)]
z_order = [(1, 1), (-1, 1), (1, -1), (-1, -1)]
surface_code_cycle = rotated_surface_code_memory_z_cycle(
data_qubits, x_measure_qubits, z_measure_qubits, x_order, z_order
)
if moment_by_moment:
return cirq.Circuit(
surface_code_cycle * num_rounds, cirq.Moment(cirq.measure_each(*data_qubits))
)
else:
return cirq.Circuit(
[*surface_code_cycle.all_operations()] * num_rounds, cirq.measure_each(*data_qubits)
)


class TestSurfaceCodeRotatedMemoryZ:
"""Surface Code Rotated Memory-Z Benchmarks."""

group = "circuit_construction"
expected = pandas.DataFrame.from_dict(
{
3: [64, 369],
5: [176, 3225],
7: [344, 12985],
9: [568, 36369],
11: [848, 82401],
13: [1184, 162409],
15: [1576, 290025],
17: [2024, 481185],
19: [2528, 754129],
21: [3088, 1129401],
23: [3704, 1629849],
25: [4376, 2280625],
},
columns=["depth", "operation_count"],
orient="index",
)

@pytest.mark.parametrize("distance", expected.index)
@pytest.mark.benchmark(group=group)
def test_circuit_construction_moment_by_moment(self, benchmark, distance: int) -> None:
"""Benchmark circuit construction for Rotated Bottom-Z Surface code."""
circuit = benchmark(
surface_code_circuit, distance, num_rounds=distance * distance, moment_by_moment=True
)
assert len(circuit) == self.expected.depth[distance]

@pytest.mark.parametrize("distance", expected.index)
@pytest.mark.benchmark(group=group)
def test_circuit_construction_operations_by_operation(self, benchmark, distance: int) -> None:
"""Benchmark circuit construction for Rotated Bottom-Z Surface code."""
circuit = benchmark(
surface_code_circuit, distance, num_rounds=distance * distance, moment_by_moment=False
)
assert sum(1 for _ in circuit.all_operations()) == self.expected.operation_count[distance]


class TestXOnAllQubitsCircuit:
"""N * D times X gate on all qubits."""

group = "circuit_operations"

@pytest.mark.parametrize(
["qubit_count", "depth"], itertools.product([1, 10, 100, 1000], [1, 10, 100, 1000])
)
@pytest.mark.benchmark(group=group)
def test_circuit_construction(self, benchmark, qubit_count: int, depth: int) -> None:
q = cirq.LineQubit.range(qubit_count)
f = lambda: cirq.Circuit(cirq.Moment(cirq.X.on_each(*q)) for _ in range(depth))
circuit = benchmark(f)
assert len(circuit) == depth
26 changes: 26 additions & 0 deletions benchmarks/linalg_decompositions_perf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2026 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest

import cirq


@pytest.mark.parametrize(
"gate", [cirq.IdentityGate(2), cirq.SWAP, cirq.ISWAP, cirq.CZ, cirq.CNOT], ids=str
)
@pytest.mark.benchmark(group="linalg_decompositions")
def test_kak_decomposition(benchmark, gate: cirq.Gate) -> None:
"""Benchmark kak_decomposition."""
benchmark(cirq.kak_decomposition, gate)
41 changes: 41 additions & 0 deletions benchmarks/parameter_resolution_perf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright 2026 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import itertools
import random

import numpy as np
import pytest
import sympy

import cirq


@pytest.mark.parametrize(
["num_qubits", "num_scan_points"], itertools.product([50, 100, 150, 200], [20, 40, 60, 80, 100])
)
@pytest.mark.benchmark(group="parameter_resolution")
def test_parameter_resolution(benchmark, num_qubits: int, num_scan_points: int) -> None:
qubits = cirq.GridQubit.rect(1, num_qubits)
symbols = {q: sympy.Symbol(f'a_{q}') for q in qubits}
circuit = cirq.Circuit([cirq.X(q) ** symbols[q] for q in qubits], cirq.measure_each(*qubits))
qubit_amps = {q: random.uniform(0.48, 0.52) for q in qubits}
diff_amps = np.linspace(-0.3, 0.3, num=num_scan_points)

def _f():
for diff in diff_amps:
resolver = {symbols[q]: amp + diff for q, amp in qubit_amps.items()}
_ = cirq.resolve_parameters(circuit, resolver)

benchmark(_f)
101 changes: 101 additions & 0 deletions benchmarks/randomized_benchmarking_perf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copyright 2026 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import functools
import itertools
from collections.abc import Iterable, Sequence

import numpy as np
import pytest

import cirq
from cirq.experiments.qubit_characterizations import _find_inv_matrix, _single_qubit_cliffords


def dot(args: Iterable[np.ndarray]) -> np.ndarray:
return functools.reduce(np.dot, args)


class TestSingleQubitRandomizedBenchmarking:
"""Benchmarks circuit construction time for single qubit randomized benchmarking circuits.

Given a combination of `depth`, `num_qubits` and `num_circuits`, the benchmark constructs
`num_circuits` different circuits, each spanning `num_qubits` and containing `depth` moments.
Each moment of the circuit contains a single qubit clifford operation for each qubit.

Thus, the generated circuits have `depth * num_qubits` single qubit clifford operations.
"""

group = "randomized_benchmarking"
depth = [1, 10, 50, 100, 250, 500, 1000]
num_qubits = [100]
num_circuits = [20]

# assigned in setup_class
sq_xz_matrices: np.ndarray
sq_xz_cliffords: Sequence[cirq.Gate]

@classmethod
def setup_class(cls):
cls.sq_xz_matrices = np.array(
[
dot([cirq.unitary(c) for c in reversed(group)])
for group in _single_qubit_cliffords().c1_in_xz
]
)
cls.sq_xz_cliffords = [cirq.PhasedXZGate.from_matrix(mat) for mat in cls.sq_xz_matrices]

def _get_op_grid(self, qubits: Sequence[cirq.Qid], depth: int) -> list[list[cirq.Operation]]:
op_grid: list[list[cirq.Operation]] = []
for q in qubits:
gate_ids = np.random.choice(len(self.sq_xz_cliffords), depth)
idx = _find_inv_matrix(dot(self.sq_xz_matrices[gate_ids][::-1]), self.sq_xz_matrices)
op_sequence = [self.sq_xz_cliffords[gate_id].on(q) for gate_id in gate_ids]
op_sequence.append(self.sq_xz_cliffords[idx].on(q))
op_grid.append(op_sequence)
return op_grid

@pytest.mark.parametrize(
["depth", "num_qubits", "num_circuits"], itertools.product(depth, num_qubits, num_circuits)
)
@pytest.mark.benchmark(group=group)
def test_rb_op_grid_generation(
self, benchmark, depth: int, num_qubits: int, num_circuits: int
) -> None:
qubits = cirq.GridQubit.rect(1, num_qubits)

def _f() -> None:
for _ in range(num_circuits):
self._get_op_grid(qubits, depth)

benchmark(_f)

@pytest.mark.parametrize(
["depth", "num_qubits", "num_circuits"], itertools.product(depth, num_qubits, num_circuits)
)
@pytest.mark.benchmark(group=group)
def test_rb_circuit_construction(
self, benchmark, depth: int, num_qubits: int, num_circuits: int
) -> None:
qubits = cirq.GridQubit.rect(1, num_qubits)

def _f() -> None:
for _ in range(num_circuits):
op_grid = self._get_op_grid(qubits, depth)
cirq.Circuit(
[cirq.Moment(ops[d] for ops in op_grid) for d in range(depth + 1)],
cirq.Moment(cirq.measure(*qubits)),
)

benchmark(_f)
Loading
Loading