Skip to content

Commit e3bddbe

Browse files
Add 1d GHZ circuits (#7936)
Add tools for creating GHZ state-prep circuits that grow the state in 1D.
1 parent 3bc3aa4 commit e3bddbe

6 files changed

Lines changed: 273 additions & 2 deletions

File tree

cirq-core/cirq/experiments/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,5 @@
9191
calibrate_z_phases as calibrate_z_phases,
9292
)
9393

94-
from cirq.experiments.ghz_2d import generate_2d_ghz_circuit as generate_2d_ghz_circuit
94+
from cirq.experiments.ghz.ghz_2d import generate_2d_ghz_circuit as generate_2d_ghz_circuit
95+
from cirq.experiments.ghz.ghz_1d import generate_1d_ghz_circuit as generate_1d_ghz_circuit
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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.
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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+
import cirq.circuits as circuits
16+
import cirq.ops as ops
17+
import cirq.transformers as transformers
18+
19+
20+
def _create_odd_ghz(qubits: list[ops.Qid]) -> circuits.Circuit:
21+
"""Circuit to create a GHZ state on an odd number of qubits with 1D connectivity. Example:
22+
23+
24+
0: ───────────────────────────────H───@───H───
25+
26+
1: ───────────────────────H───@───H───@───────
27+
28+
2: ───────────────H───@───H───@───────────────
29+
30+
3: ───H───────@───H───@───────────────────────
31+
32+
4: ───H───@───@───────────────────────────────
33+
34+
5: ───H───@───────H───@───────────────────────
35+
36+
6: ───────────────H───@───H───@───────────────
37+
38+
7: ───────────────────────H───@───H───@───────
39+
40+
8: ───────────────────────────────H───@───H───
41+
42+
Args:
43+
qubits: A list of qubits such that CZ gates are possible between qubits[i] and qubits[i+1].
44+
45+
Returns:
46+
A circuit to prepare the GHZ state.
47+
"""
48+
49+
nq = len(qubits)
50+
assert nq % 2 == 1 and nq >= 3
51+
center_idx = nq // 2
52+
moments = [
53+
circuits.Moment(
54+
ops.H(qubits[center_idx]), ops.H(qubits[center_idx + 1]), ops.H(qubits[center_idx - 1])
55+
),
56+
circuits.Moment(ops.CZ(qubits[center_idx], qubits[center_idx + 1])),
57+
circuits.Moment(ops.CZ(qubits[center_idx], qubits[center_idx - 1])),
58+
]
59+
for d in range(2, nq // 2 + 1):
60+
operations = [
61+
ops.H(qubits[center_idx + d - 1]),
62+
ops.H(qubits[center_idx + d]),
63+
ops.H(qubits[center_idx - d + 1]),
64+
ops.H(qubits[center_idx - d]),
65+
]
66+
moments.append(circuits.Moment(*operations))
67+
moments.append(
68+
circuits.Moment(
69+
ops.CZ(qubits[center_idx + d - 1], qubits[center_idx + d]),
70+
ops.CZ(qubits[center_idx - d + 1], qubits[center_idx - d]),
71+
)
72+
)
73+
74+
operations = [ops.H(qubits[0]), ops.H(qubits[-1])]
75+
moments.append(circuits.Moment(*operations))
76+
77+
return circuits.Circuit.from_moments(*moments)
78+
79+
80+
def _create_even_ghz(qubits: list[ops.Qid]) -> circuits.Circuit:
81+
"""Circuit to create a GHZ state on an even number of qubits with 1D connectivity. Example:
82+
83+
84+
0: ───────────────────────────H───@───H───
85+
86+
1: ───────────────────H───@───H───@───────
87+
88+
2: ───────────H───@───H───@───────────────
89+
90+
3: ───H───@───────@───────────────────────
91+
92+
4: ───H───@───H───@───────────────────────
93+
94+
5: ───────────H───@───H───@───────────────
95+
96+
6: ───────────────────H───@───H───@───────
97+
98+
7: ───────────────────────────H───@───H───
99+
100+
Args:
101+
qubits: A list of qubits such that CZ gates are possible between qubits[i] and qubits[i+1].
102+
103+
Returns:
104+
A circuit to prepare the GHZ state.
105+
"""
106+
107+
nq = len(qubits)
108+
assert nq % 2 == 0 and nq >= 2
109+
center_idx = nq // 2
110+
moments = [
111+
circuits.Moment(ops.H(qubits[center_idx - 1]), ops.H(qubits[center_idx])),
112+
circuits.Moment(ops.CZ(qubits[center_idx - 1], qubits[center_idx])),
113+
]
114+
for d in range(1, nq // 2):
115+
if d == 1:
116+
moments.append(
117+
circuits.Moment(
118+
ops.H.on_each(
119+
qubits[center_idx], qubits[center_idx + 1], qubits[center_idx - 2]
120+
)
121+
)
122+
)
123+
else:
124+
operations = ops.H.on_each(
125+
qubits[center_idx - d - 1],
126+
qubits[center_idx - d],
127+
qubits[center_idx + d],
128+
qubits[center_idx + d - 1],
129+
)
130+
moments.append(circuits.Moment(operations))
131+
moments.append(
132+
circuits.Moment(
133+
ops.CZ(qubits[center_idx - d - 1], qubits[center_idx - d]),
134+
ops.CZ(qubits[center_idx + d], qubits[center_idx + d - 1]),
135+
)
136+
)
137+
138+
operations = [ops.H(qubits[0]), ops.H(qubits[-1])] if nq > 2 else [ops.H(qubits[0])]
139+
moments.append(circuits.Moment(*operations))
140+
141+
return circuits.Circuit.from_moments(*moments)
142+
143+
144+
def _create_ghz_from_one_end(qubits: list[ops.Qid]) -> circuits.Circuit:
145+
"""Circuit to create a GHZ state from one end in a 1D chain. Example:
146+
147+
148+
0: ───H───@───────────────────────────────────────────────────────
149+
150+
1: ───H───@───H───@───────────────────────────────────────────────
151+
152+
2: ───────────H───@───H───@───────────────────────────────────────
153+
154+
3: ───────────────────H───@───H───@───────────────────────────────
155+
156+
4: ───────────────────────────H───@───H───@───────────────────────
157+
158+
5: ───────────────────────────────────H───@───H───@───────────────
159+
160+
6: ───────────────────────────────────────────H───@───H───@───────
161+
162+
7: ───────────────────────────────────────────────────H───@───H───
163+
164+
165+
Args:
166+
qubits: A list of qubits such that CZ gates are possible between qubits[i] and qubits[i+1].
167+
168+
Returns:
169+
A circuit to prepare the GHZ state.
170+
171+
Raises:
172+
NotImplementedError: If requesting x_basis_cheat=True and x_basis
173+
"""
174+
175+
num_qubits = len(qubits)
176+
moments = []
177+
for cycle in range(num_qubits - 1):
178+
moments.append(circuits.Moment(ops.H.on_each(qubits[cycle : cycle + 2])))
179+
moments.append(circuits.Moment(ops.CZ(*qubits[cycle : (cycle + 2)])))
180+
moments.append(circuits.Moment(ops.H(qubits[-1])))
181+
182+
return circuits.Circuit.from_moments(*moments)
183+
184+
185+
def generate_1d_ghz_circuit(
186+
qubits: list[ops.Qid],
187+
add_dd: bool = True,
188+
dd_sequence: tuple[ops.Gate, ...] = (ops.X, ops.Y, ops.X, ops.Y),
189+
from_one_end: bool = False,
190+
) -> circuits.Circuit:
191+
"""Circuit to create a GHZ state on qubits with 1D connectivity.
192+
193+
Args:
194+
qubits: A list of qubits such that CZ gates are possible between qubits[i] and qubits[i+1].
195+
add_dd: Whether to add dynamical decoupling to the circuit, done by adding gates.
196+
dd_sequence: The sequence of gates to insert for dynamical decoupling.
197+
from_one_end: Whether to grow the GHZ state from one end instead of the center.
198+
199+
Returns:
200+
A circuit to prepare the GHZ state.
201+
"""
202+
if from_one_end:
203+
circuit = _create_ghz_from_one_end(qubits)
204+
elif len(qubits) % 2 == 0:
205+
circuit = _create_even_ghz(qubits)
206+
else:
207+
circuit = _create_odd_ghz(qubits)
208+
209+
if add_dd:
210+
# first add cirq.I in final moment to help with transformer:
211+
circuit[-1] += ops.I.on_each(set(qubits) - circuit[-1].qubits)
212+
213+
# next, add dd
214+
circuit = transformers.dynamical_decoupling.add_dynamical_decoupling(
215+
circuit, schema=dd_sequence, single_qubit_gate_moments_only=True
216+
)
217+
218+
return circuit
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
import numpy as np
16+
17+
import cirq.devices as devices
18+
import cirq.experiments.ghz.ghz_1d as ghz_1d
19+
import cirq.sim as sim
20+
21+
22+
def test_generate_1d_ghz_circuit():
23+
simulator = sim.Simulator(dtype=np.complex128)
24+
for nq in [7, 8]:
25+
qubits = devices.LineQubit.range(nq)
26+
circuits = [
27+
ghz_1d.generate_1d_ghz_circuit(qubits, add_dd=False, from_one_end=False),
28+
ghz_1d.generate_1d_ghz_circuit(qubits, add_dd=False, from_one_end=True),
29+
ghz_1d.generate_1d_ghz_circuit(qubits, add_dd=True, from_one_end=False),
30+
ghz_1d.generate_1d_ghz_circuit(qubits, add_dd=True, from_one_end=True),
31+
]
32+
psi0 = simulator.simulate(circuits[0]).final_state_vector
33+
for circuit in circuits[1:]:
34+
assert np.isclose(
35+
np.abs(np.vdot(psi0, simulator.simulate(circuit).final_state_vector)) ** 2, 1.0
36+
)
37+
assert len(circuits[-1]) > len(circuits[0])
38+
assert len(circuits[-2]) == len(circuits[0])
39+
assert len(circuits[-3]) > len(circuits[0])

cirq-core/cirq/experiments/ghz_2d_test.py renamed to cirq-core/cirq/experiments/ghz/ghz_2d_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import pytest
2222

2323
import cirq
24-
import cirq.experiments.ghz_2d as ghz_2d
24+
import cirq.experiments.ghz.ghz_2d as ghz_2d
2525

2626

2727
def _create_mock_graph() -> tuple[nx.Graph, cirq.GridQubit]:

0 commit comments

Comments
 (0)