Skip to content

Commit 755949b

Browse files
eliottrosenbergShashwat Kumar
andauthored
Add DFL experiment code (#443)
This PR adds code for the Disorder-Free Localization (DFL) simulation in both 1d and 2d. This contains code to run the experiments for Figures 1 and 3 of arXiv:2410.06557. --------- Co-authored-by: Shashwat Kumar <kshashwat@google.com>
1 parent 3c3542c commit 755949b

File tree

6 files changed

+3007
-0
lines changed

6 files changed

+3007
-0
lines changed

recirq/dfl/__init__.py

Whitespace-only changes.

recirq/dfl/dfl_1d.py

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
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

Comments
 (0)