|
| 1 | +# Copyright 2025 Google |
| 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 | +"""Generates the Trotterized circuits for 1D Disorder-Free Localization (DFL) experiments. |
| 16 | +""" |
| 17 | + |
| 18 | +from collections.abc import Sequence |
| 19 | +from typing import List |
| 20 | + |
| 21 | +import cirq |
| 22 | +import matplotlib.pyplot as plt |
| 23 | +import numpy as np |
| 24 | +import numpy.typing as npt |
| 25 | +from tqdm import tqdm |
| 26 | + |
| 27 | +#import Enums |
| 28 | +from recirq.dfl.dfl_enums import TwoQubitGate, InitialState, Basis |
| 29 | + |
| 30 | +def trotter_circuit( |
| 31 | + grid: Sequence[cirq.GridQubit], |
| 32 | + n_cycles: int, |
| 33 | + dt: float, |
| 34 | + h: float, |
| 35 | + mu: float, |
| 36 | + two_qubit_gate: TwoQubitGate = TwoQubitGate.CZ, |
| 37 | +) -> cirq.Circuit: |
| 38 | + """Constructs a Trotter circuit for 1D Disorder-Free Localization (DFL) simulation. |
| 39 | +
|
| 40 | + Args: |
| 41 | + grid: The 1D sequence of qubits used in the experiment. |
| 42 | + n_cycles: The number of Trotter steps/cycles to include. |
| 43 | + dt: The time step size for the Trotterization. |
| 44 | + h: The gauge field strength coefficient. |
| 45 | + mu: The matter field strength coefficient. |
| 46 | + two_qubit_gate: The type of two-qubit gate to use in the layers. |
| 47 | + Use an Enum member from TwoQubitGate (CZ or CPHASE). |
| 48 | +
|
| 49 | + Returns: |
| 50 | + The complete cirq.Circuit for the Trotter evolution. |
| 51 | +
|
| 52 | + Raises: |
| 53 | + ValueError: If an invalid `two_qubit_gate` option is provided. |
| 54 | + """ |
| 55 | + if two_qubit_gate.value == TwoQubitGate.CZ.value: |
| 56 | + return _layer_floquet_cz(grid, dt, h, mu) * n_cycles |
| 57 | + |
| 58 | + elif two_qubit_gate.value == TwoQubitGate.CPHASE.value: |
| 59 | + if n_cycles == 0: |
| 60 | + return cirq.Circuit() |
| 61 | + if n_cycles == 1: |
| 62 | + return _layer_floquet_cphase_first( |
| 63 | + grid, dt, h, mu |
| 64 | + ) + _layer_floquet_cphase_last_missing_piece(grid, dt, h, mu) |
| 65 | + else: |
| 66 | + return ( |
| 67 | + _layer_floquet_cphase_first(grid, dt, h, mu) |
| 68 | + + (n_cycles - 1) * _layer_floquet_cphase_middle(grid, dt, h, mu) |
| 69 | + + _layer_floquet_cphase_last_missing_piece(grid, dt, h, mu) |
| 70 | + ) |
| 71 | + else: |
| 72 | + raise ValueError("Two-qubit gate can only be cz or cphase") |
| 73 | + |
| 74 | + |
| 75 | +def _layer_floquet_cz( |
| 76 | + grid: Sequence[cirq.GridQubit], dt: float, h: float, mu: float |
| 77 | +) -> cirq.Circuit: |
| 78 | + moment_rz = [] |
| 79 | + moment_rx = [] |
| 80 | + moment_h = [] |
| 81 | + for i in range(len(grid)): |
| 82 | + q = grid[i] |
| 83 | + if i % 2 == 1: |
| 84 | + moment_rz.append(cirq.rz(dt).on(q)) |
| 85 | + moment_h.append(cirq.H(q)) |
| 86 | + moment_rx.append(cirq.rx(2 * h * dt).on(q)) |
| 87 | + else: |
| 88 | + moment_rx.append(cirq.rx(2 * mu * dt).on(q)) |
| 89 | + return ( |
| 90 | + cirq.Circuit.from_moments(cirq.Moment(moment_rz)) |
| 91 | + + _change_basis(grid) |
| 92 | + + cirq.Circuit.from_moments(cirq.Moment(moment_rx)) |
| 93 | + + _change_basis(grid) |
| 94 | + + cirq.Circuit.from_moments(cirq.Moment(moment_rz)) |
| 95 | + ) |
| 96 | + |
| 97 | + |
| 98 | +def _layer_floquet_cphase_middle( |
| 99 | + grid: Sequence[cirq.GridQubit], dt: float, h: float, mu: float |
| 100 | +) -> cirq.Circuit: |
| 101 | + n = len(grid) |
| 102 | + moment_0 = [] |
| 103 | + moment_1 = [] |
| 104 | + moment_h = [] |
| 105 | + moment_rz = [] |
| 106 | + moment_rx = [] |
| 107 | + |
| 108 | + for i in range(n // 2): |
| 109 | + q0 = grid[(2 * i)] |
| 110 | + q = grid[2 * i + 1] |
| 111 | + q1 = grid[(2 * i + 2) % n] |
| 112 | + moment_0.append(cirq.CZ(q0, q)) # left to right |
| 113 | + moment_1.append(cirq.cphase(-4 * dt).on(q1, q)) # right to left |
| 114 | + |
| 115 | + moment_h.append(cirq.H(q)) |
| 116 | + |
| 117 | + moment_rz.append(cirq.rz(2 * dt).on(q)) |
| 118 | + moment_rz.append(cirq.rz(2 * dt).on(q1)) |
| 119 | + |
| 120 | + for i in range(n): |
| 121 | + q = grid[i] |
| 122 | + if i % 2 == 1: |
| 123 | + moment_rx.append(cirq.rx(2 * h * dt).on(q)) |
| 124 | + else: |
| 125 | + moment_rx.append(cirq.rx(2 * mu * dt).on(q)) |
| 126 | + |
| 127 | + moment_rz_new = [] |
| 128 | + qubits_covered = [] |
| 129 | + for gate in moment_rz: |
| 130 | + count = moment_rz.count(gate) |
| 131 | + qubit = gate.qubits[0] |
| 132 | + if qubit not in qubits_covered: |
| 133 | + moment_rz_new.append(cirq.rz(2 * count * dt).on(qubit)) |
| 134 | + qubits_covered.append(qubit) |
| 135 | + |
| 136 | + return cirq.Circuit.from_moments( |
| 137 | + cirq.Moment(moment_h), |
| 138 | + cirq.Moment(moment_0), |
| 139 | + cirq.Moment(moment_h), |
| 140 | + cirq.Moment(moment_rz_new), |
| 141 | + cirq.Moment(moment_1), |
| 142 | + cirq.Moment(moment_h), |
| 143 | + cirq.Moment(moment_0), |
| 144 | + cirq.Moment(moment_h), |
| 145 | + cirq.Moment(moment_rx), |
| 146 | + ) |
| 147 | + |
| 148 | + |
| 149 | +def _layer_floquet_cphase_first( |
| 150 | + grid: Sequence[cirq.GridQubit], dt: float, h: float, mu: float |
| 151 | +) -> cirq.Circuit: |
| 152 | + moment_rz = [] |
| 153 | + moment_rx = [] |
| 154 | + |
| 155 | + for i in range(len(grid)): |
| 156 | + q = grid[i] |
| 157 | + if i % 2 == 1: |
| 158 | + moment_rz.append(cirq.rz(dt).on(q)) |
| 159 | + moment_rx.append(cirq.rx(2 * h * dt).on(q)) |
| 160 | + else: |
| 161 | + moment_rx.append(cirq.rx(2 * mu * dt).on(q)) |
| 162 | + |
| 163 | + return ( |
| 164 | + cirq.Circuit.from_moments(cirq.Moment(moment_rz)) |
| 165 | + + _change_basis(grid) |
| 166 | + + cirq.Circuit.from_moments(cirq.Moment(moment_rx)) |
| 167 | + ) |
| 168 | + |
| 169 | + |
| 170 | +def _layer_floquet_cphase_last_missing_piece( |
| 171 | + grid: Sequence[cirq.GridQubit], dt: float, h: float, mu: float |
| 172 | +) -> cirq.Circuit: |
| 173 | + moment_rz = [] |
| 174 | + for i in range(len(grid)): |
| 175 | + q = grid[i] |
| 176 | + if i % 2 == 1: |
| 177 | + moment_rz.append(cirq.rz(dt).on(q)) |
| 178 | + |
| 179 | + return _change_basis(grid) + cirq.Circuit.from_moments(cirq.Moment(moment_rz)) |
| 180 | + |
| 181 | + |
| 182 | +def _layer_hadamard(grid: Sequence[cirq.GridQubit], which_qubits="all") -> cirq.Circuit: |
| 183 | + moment = [] |
| 184 | + for i in range(len(grid)): |
| 185 | + q = grid[i] |
| 186 | + |
| 187 | + if i % 2 == 1 and (which_qubits == "gauge" or which_qubits == "all"): |
| 188 | + moment.append(cirq.H(q)) |
| 189 | + |
| 190 | + elif i % 2 == 0 and (which_qubits == "matter" or which_qubits == "all"): |
| 191 | + moment.append(cirq.H(q)) |
| 192 | + return cirq.Circuit.from_moments(cirq.Moment(moment)) |
| 193 | + |
| 194 | + |
| 195 | +def _layer_measure(grid: Sequence[cirq.GridQubit]) -> cirq.Circuit: |
| 196 | + moment = [] |
| 197 | + for i in range(len(grid) // 2): |
| 198 | + moment.append(cirq.measure(grid[2 * i], key="m")) |
| 199 | + return cirq.Circuit.from_moments(cirq.Moment(moment)) |
| 200 | + |
| 201 | + |
| 202 | +def _change_basis(grid: Sequence[cirq.GridQubit]) -> cirq.Circuit: |
| 203 | + """change the basis so that the matter sites encode the gauge charge.""" |
| 204 | + n = len(grid) |
| 205 | + moment_1 = [] |
| 206 | + moment_2 = [] |
| 207 | + moment_h = [] |
| 208 | + for i in range(0, n // 2): |
| 209 | + q1 = grid[(2 * i)] |
| 210 | + q2 = grid[2 * i + 1] |
| 211 | + q3 = grid[(2 * i + 2) % n] |
| 212 | + moment_1.append(cirq.CZ(q1, q2)) |
| 213 | + moment_2.append(cirq.CZ(q3, q2)) |
| 214 | + moment_h.append(cirq.H(q2)) |
| 215 | + return cirq.Circuit.from_moments( |
| 216 | + cirq.Moment(moment_h), |
| 217 | + cirq.Moment(moment_1), |
| 218 | + cirq.Moment(moment_2), |
| 219 | + cirq.Moment(moment_h), |
| 220 | + ) |
| 221 | + |
| 222 | + |
| 223 | +def _energy_bump_initial_state( |
| 224 | + grid, h: float, matter_config: InitialState, excited_qubits: Sequence[cirq.GridQubit] |
| 225 | +) -> cirq.Circuit: |
| 226 | + """Circuit for energy bump initial state.""" |
| 227 | + |
| 228 | + theta = np.arctan(h) |
| 229 | + moment = [] |
| 230 | + for i in range(len(grid)): |
| 231 | + q = grid[i] |
| 232 | + if i % 2 == 1: |
| 233 | + if q in excited_qubits: |
| 234 | + moment.append(cirq.ry(np.pi + theta).on(q)) |
| 235 | + else: |
| 236 | + moment.append(cirq.ry(theta).on(q)) |
| 237 | + |
| 238 | + else: |
| 239 | + if matter_config.value == InitialState.SINGLE_SECTOR.value: |
| 240 | + moment.append(cirq.H(q)) |
| 241 | + return cirq.Circuit.from_moments(moment) |
| 242 | + |
| 243 | + |
| 244 | +def get_1d_dfl_experiment_circuits( |
| 245 | + grid: Sequence[cirq.GridQubit], |
| 246 | + initial_state: InitialState, |
| 247 | + n_cycles: Sequence[int] | npt.NDArray, |
| 248 | + excited_qubits: Sequence[cirq.GridQubit], |
| 249 | + dt: float, |
| 250 | + h: float, |
| 251 | + mu: float, |
| 252 | + n_instances: int = 10, |
| 253 | + two_qubit_gate: TwoQubitGate = TwoQubitGate.CZ, |
| 254 | + basis: Basis = Basis.DUAL, |
| 255 | +) -> List[cirq.Circuit]: |
| 256 | + """Generates the circuits needed for the 1D DFL experiment. |
| 257 | +
|
| 258 | + Args: |
| 259 | + grid: The 1D sequence of qubits used in the experiment. |
| 260 | + initial_state: The initial state preparation. Use an Enum member from |
| 261 | + InitialState (SINGLE_SECTOR or SUPERPOSITION). |
| 262 | + n_cycles: The number of Trotter steps (cycles) to simulate. |
| 263 | + excited_qubits: Qubits to be excited in the initial state. |
| 264 | + dt: The time step size for the Trotterization. |
| 265 | + h: The gauge field strength coefficient. |
| 266 | + mu: The matter field strength coefficient. |
| 267 | + n_instances: The number of instances to generate. |
| 268 | + two_qubit_gate: The type of two-qubit gate to use in the Trotter step. |
| 269 | + Use an Enum member from TwoQubitGate (CZ or CPHASE). |
| 270 | + basis: The basis for the final circuit structure. Use an Enum member from |
| 271 | + Basis (LGT or DUAL). |
| 272 | +
|
| 273 | + Returns: |
| 274 | + A list of all generated cirq.Circuit objects. |
| 275 | +
|
| 276 | + Raises: |
| 277 | + ValueError: If an invalid option for `initial_state` or |
| 278 | + `basis` is given. |
| 279 | + """ |
| 280 | + if initial_state.value == InitialState.SINGLE_SECTOR.value: |
| 281 | + initial_circuit = _energy_bump_initial_state( |
| 282 | + grid, h, InitialState.SINGLE_SECTOR, excited_qubits |
| 283 | + ) |
| 284 | + elif initial_state.value == InitialState.SUPERPOSITION.value: |
| 285 | + initial_circuit = _energy_bump_initial_state( |
| 286 | + grid, h, InitialState.SUPERPOSITION, excited_qubits |
| 287 | + ) |
| 288 | + else: |
| 289 | + raise ValueError("Invalid initial state") |
| 290 | + circuits = [] |
| 291 | + for n_cycle in tqdm(n_cycles): |
| 292 | + print(int(np.max([0, n_cycle - 1]))) |
| 293 | + circ = initial_circuit + trotter_circuit( |
| 294 | + grid, n_cycle, dt, h, mu, two_qubit_gate |
| 295 | + ) |
| 296 | + if basis.value == Basis.LGT.value: |
| 297 | + circ += _change_basis(grid) |
| 298 | + elif basis.value == Basis.DUAL.value: |
| 299 | + pass |
| 300 | + else: |
| 301 | + raise ValueError("Invalid option for basis") |
| 302 | + for _ in range(n_instances): |
| 303 | + |
| 304 | + if basis.value == Basis.LGT.value: |
| 305 | + circ_z = circ + cirq.measure([q for q in grid], key="m") |
| 306 | + elif basis.value == Basis.DUAL.value: |
| 307 | + circ_z = ( |
| 308 | + circ |
| 309 | + + _layer_hadamard(grid, "matter") |
| 310 | + + cirq.measure([q for q in grid], key="m") |
| 311 | + ) |
| 312 | + else: |
| 313 | + raise ValueError("Invalid option for basis") |
| 314 | + circ_x = ( |
| 315 | + circ |
| 316 | + + _layer_hadamard(grid, "all") |
| 317 | + + cirq.measure([q for q in grid], key="m") |
| 318 | + ) |
| 319 | + circuits.append(circ_z) |
| 320 | + circuits.append(circ_x) |
| 321 | + return circuits |
0 commit comments