|
| 1 | +# Copyright 2026 The Cirq Developers |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +"""Performance tests for circuit construction.""" |
| 16 | + |
| 17 | +import itertools |
| 18 | +from collections.abc import Sequence |
| 19 | + |
| 20 | +import pandas |
| 21 | +import pytest |
| 22 | + |
| 23 | +import cirq |
| 24 | + |
| 25 | + |
| 26 | +def rotated_surface_code_memory_z_cycle( |
| 27 | + data_qubits: set[cirq.GridQubit], |
| 28 | + z_measure_qubits: set[cirq.GridQubit], |
| 29 | + x_measure_qubits: set[cirq.GridQubit], |
| 30 | + z_order: Sequence[tuple[int, int]], |
| 31 | + x_order: Sequence[tuple[int, int]], |
| 32 | +) -> cirq.Circuit: |
| 33 | + """Constructs a circuit for a single round of rotated memory Z surface code. |
| 34 | +
|
| 35 | + Args: |
| 36 | + data_qubits: data qubits for the surface code patch. |
| 37 | + z_measure_qubits: measure qubits to measure Z stabilizers for surface code patch. |
| 38 | + x_measure_qubits: measure qubits to measure X stabilizers for surface code patch. |
| 39 | + z_order: Specifies the order in which the 2/4 data qubit neighbours of a Z measure qubit |
| 40 | + should be processed. |
| 41 | + x_order: Specifies the order in which the 2/4 data qubit neighbours of a X measure qubit |
| 42 | + should be processed. |
| 43 | +
|
| 44 | + Returns: |
| 45 | + A `cirq.Circuit` for a single round of rotated memory Z surface code cycle. |
| 46 | + """ |
| 47 | + |
| 48 | + circuit = cirq.Circuit() |
| 49 | + circuit += cirq.Moment([cirq.H(q) for q in x_measure_qubits]) |
| 50 | + for k in range(4): |
| 51 | + op_list = [] |
| 52 | + for measure_qubits, add, is_x in [ |
| 53 | + (x_measure_qubits, x_order[k], True), |
| 54 | + (z_measure_qubits, z_order[k], False), |
| 55 | + ]: |
| 56 | + for q_meas in measure_qubits: |
| 57 | + q_data = q_meas + add |
| 58 | + if q_data in data_qubits: |
| 59 | + op_list.append(cirq.CNOT(q_meas, q_data) if is_x else cirq.CNOT(q_data, q_meas)) |
| 60 | + circuit += cirq.Moment(op_list) |
| 61 | + circuit += cirq.Moment([cirq.H(q) for q in x_measure_qubits]) |
| 62 | + circuit += cirq.Moment(cirq.measure_each(*x_measure_qubits, *z_measure_qubits)) |
| 63 | + return circuit |
| 64 | + |
| 65 | + |
| 66 | +def surface_code_circuit( |
| 67 | + distance: int, num_rounds: int, moment_by_moment: bool = True |
| 68 | +) -> cirq.Circuit: |
| 69 | + """Constructs a rotated memory Z surface code circuit with `distance` and `num_rounds`. |
| 70 | +
|
| 71 | + The circuit has `dxd` data qubits and `d ** 2 - 1` measure qubits, where `d` is the distance |
| 72 | + of surface code. For more details on rotated surface codes and qubit indexing, see figure 13 |
| 73 | + https://arxiv.org/abs/1111.4022. |
| 74 | +
|
| 75 | + Args: |
| 76 | + distance: Distance of the surface code. |
| 77 | + num_rounds: Number of error correction rounds for memory Z experiment. |
| 78 | + moment_by_moment: If True, the circuit is constructed moment-by-moment instead of |
| 79 | + operation-by-operation. This is useful to benchmark different circuit construction |
| 80 | + patterns for the same circuit. |
| 81 | +
|
| 82 | + Returns: |
| 83 | + A `cirq.Circuit` for surface code memory Z experiment for `distance` and `num_rounds`. |
| 84 | + """ |
| 85 | + |
| 86 | + def ndrange(*ranges: tuple[int, ...]): |
| 87 | + return itertools.product(*[range(*r) for r in ranges]) |
| 88 | + |
| 89 | + data_qubits = {cirq.q(2 * x + 1, 2 * y + 1) for x, y in ndrange((distance,), (distance,))} |
| 90 | + z_measure_qubits = { |
| 91 | + cirq.q(2 * x, 2 * y) for x, y in ndrange((1, distance), (distance + 1,)) if x % 2 != y % 2 |
| 92 | + } |
| 93 | + x_measure_qubits = { |
| 94 | + cirq.q(2 * x, 2 * y) for x, y in ndrange((distance + 1,), (1, distance)) if x % 2 == y % 2 |
| 95 | + } |
| 96 | + x_order = [(1, 1), (1, -1), (-1, 1), (-1, -1)] |
| 97 | + z_order = [(1, 1), (-1, 1), (1, -1), (-1, -1)] |
| 98 | + surface_code_cycle = rotated_surface_code_memory_z_cycle( |
| 99 | + data_qubits, x_measure_qubits, z_measure_qubits, x_order, z_order |
| 100 | + ) |
| 101 | + if moment_by_moment: |
| 102 | + return cirq.Circuit( |
| 103 | + surface_code_cycle * num_rounds, cirq.Moment(cirq.measure_each(*data_qubits)) |
| 104 | + ) |
| 105 | + else: |
| 106 | + return cirq.Circuit( |
| 107 | + [*surface_code_cycle.all_operations()] * num_rounds, cirq.measure_each(*data_qubits) |
| 108 | + ) |
| 109 | + |
| 110 | + |
| 111 | +class TestSurfaceCodeRotatedMemoryZ: |
| 112 | + """Surface Code Rotated Memory-Z Benchmarks.""" |
| 113 | + |
| 114 | + group = "circuit_construction" |
| 115 | + expected = pandas.DataFrame.from_dict( |
| 116 | + { |
| 117 | + 3: [64, 369], |
| 118 | + 5: [176, 3225], |
| 119 | + 7: [344, 12985], |
| 120 | + 9: [568, 36369], |
| 121 | + 11: [848, 82401], |
| 122 | + 13: [1184, 162409], |
| 123 | + 15: [1576, 290025], |
| 124 | + 17: [2024, 481185], |
| 125 | + 19: [2528, 754129], |
| 126 | + 21: [3088, 1129401], |
| 127 | + 23: [3704, 1629849], |
| 128 | + 25: [4376, 2280625], |
| 129 | + }, |
| 130 | + columns=["depth", "operation_count"], |
| 131 | + orient="index", |
| 132 | + ) |
| 133 | + |
| 134 | + @pytest.mark.parametrize("distance", expected.index) |
| 135 | + @pytest.mark.benchmark(group=group) |
| 136 | + def test_circuit_construction_moment_by_moment(self, benchmark, distance: int) -> None: |
| 137 | + """Benchmark circuit construction for Rotated Bottom-Z Surface code.""" |
| 138 | + circuit = benchmark( |
| 139 | + surface_code_circuit, distance, num_rounds=distance * distance, moment_by_moment=True |
| 140 | + ) |
| 141 | + assert len(circuit) == self.expected.depth[distance] |
| 142 | + |
| 143 | + @pytest.mark.parametrize("distance", expected.index) |
| 144 | + @pytest.mark.benchmark(group=group) |
| 145 | + def test_circuit_construction_operations_by_operation(self, benchmark, distance: int) -> None: |
| 146 | + """Benchmark circuit construction for Rotated Bottom-Z Surface code.""" |
| 147 | + circuit = benchmark( |
| 148 | + surface_code_circuit, distance, num_rounds=distance * distance, moment_by_moment=False |
| 149 | + ) |
| 150 | + assert sum(1 for _ in circuit.all_operations()) == self.expected.operation_count[distance] |
| 151 | + |
| 152 | + |
| 153 | +class TestXOnAllQubitsCircuit: |
| 154 | + """N * D times X gate on all qubits.""" |
| 155 | + |
| 156 | + group = "circuit_operations" |
| 157 | + |
| 158 | + @pytest.mark.parametrize( |
| 159 | + ["qubit_count", "depth"], itertools.product([1, 10, 100, 1000], [1, 10, 100, 1000]) |
| 160 | + ) |
| 161 | + @pytest.mark.benchmark(group=group) |
| 162 | + def test_circuit_construction(self, benchmark, qubit_count: int, depth: int) -> None: |
| 163 | + q = cirq.LineQubit.range(qubit_count) |
| 164 | + f = lambda: cirq.Circuit(cirq.Moment(cirq.X.on_each(*q)) for _ in range(depth)) |
| 165 | + circuit = benchmark(f) |
| 166 | + assert len(circuit) == depth |
0 commit comments