Skip to content

Commit 86f3164

Browse files
author
Pablo Le Hénaff
committed
Add special case for single-CNOT decomposition of controlled-U
1 parent 9392bd2 commit 86f3164

File tree

4 files changed

+78
-53
lines changed

4 files changed

+78
-53
lines changed

opensquirrel/cnot_decomposer.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import math
22

3+
from opensquirrel import merger
34
from opensquirrel.common import ATOL
4-
from opensquirrel.default_gates import cnot, ry, rz
5+
from opensquirrel.default_gates import cnot, ry, rz, x
56
from opensquirrel.identity_filter import filter_out_identities
67
from opensquirrel.replacer import Decomposer
78
from opensquirrel.squirrel_ir import BlochSphereRotation, ControlledGate, Float, Gate
@@ -29,30 +30,36 @@ def decompose(g: Gate) -> [Gate]:
2930
# ControlledGate's with 2+ control qubits are ignored.
3031
return [g]
3132

33+
target_qubit = g.target_gate.qubit
34+
3235
# Perform ZYZ decomposition on the target gate.
3336
# This gives us an ABC decomposition (U = AXBXC, ABC = I) of the target gate.
3437
# See https://threeplusone.com/pubs/on_gates.pdf
35-
theta0, theta1, theta2 = get_zyz_decomposition_angles(g.target_gate.angle, g.target_gate.axis)
36-
target_qubit = g.target_gate.qubit
3738

38-
# First try to see if we can get away with a single CNOT.
39-
# FIXME: see https://github.com/QuTech-Delft/OpenSquirrel/issues/99 this could be extended, I believe.
40-
if abs(abs(theta0 + theta2) % (2 * math.pi)) < ATOL and abs(abs(theta1 - math.pi) % (2 * math.pi)) < ATOL:
41-
# g == rz(theta0) Y rz(theta2) == rz(theta0 - pi / 2) X rz(theta2 + pi / 2)
42-
# theta0 + theta2 == 0
39+
# Try special case first, see https://arxiv.org/pdf/quant-ph/9503016.pdf lemma 5.5
40+
controlled_rotation_times_x = merger.compose_bloch_sphere_rotations(x(target_qubit), g.target_gate)
41+
theta0_with_x, theta1_with_x, theta2_with_x = get_zyz_decomposition_angles(
42+
controlled_rotation_times_x.angle, controlled_rotation_times_x.axis
43+
)
44+
if abs((theta0_with_x - theta2_with_x) % (2 * math.pi)) < ATOL:
45+
# The decomposition can use a single CNOT according to the lemma.
46+
47+
A = [ry(q=target_qubit, theta=Float(-theta1_with_x / 2)), rz(q=target_qubit, theta=Float(-theta2_with_x))]
4348

44-
alpha0 = theta0 - math.pi / 2
45-
alpha2 = theta2 + math.pi / 2
49+
B = [
50+
rz(q=target_qubit, theta=Float(theta2_with_x)),
51+
ry(q=target_qubit, theta=Float(theta1_with_x / 2)),
52+
]
4653

4754
return filter_out_identities(
48-
[
49-
rz(q=target_qubit, theta=Float(alpha2)),
50-
cnot(control=g.control_qubit, target=target_qubit),
51-
rz(q=target_qubit, theta=Float(alpha0)),
52-
rz(q=g.control_qubit, theta=Float(g.target_gate.phase - math.pi / 2)),
53-
]
55+
B
56+
+ [cnot(control=g.control_qubit, target=target_qubit)]
57+
+ A
58+
+ [rz(q=g.control_qubit, theta=Float(g.target_gate.phase - math.pi / 2))]
5459
)
5560

61+
theta0, theta1, theta2 = get_zyz_decomposition_angles(g.target_gate.angle, g.target_gate.axis)
62+
5663
A = [ry(q=target_qubit, theta=Float(theta1 / 2)), rz(q=target_qubit, theta=Float(theta2))]
5764

5865
B = [

opensquirrel/merger.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ def compose_bloch_sphere_rotations(a: BlochSphereRotation, b: BlochSphereRotatio
1616
"""
1717
assert a.qubit == b.qubit, "Cannot merge two BlochSphereRotation's on different qubits"
1818

19-
combined_angle = 2 * acos(
20-
cos(a.angle / 2) * cos(b.angle / 2) - sin(a.angle / 2) * sin(b.angle / 2) * np.dot(a.axis, b.axis)
21-
)
19+
acos_argument = cos(a.angle / 2) * cos(b.angle / 2) - sin(a.angle / 2) * sin(b.angle / 2) * np.dot(a.axis, b.axis)
20+
# This fixes float approximations like 1.0000000000002 which acos doesn't like.
21+
acos_argument = max(min(acos_argument, 1.0), -1.0)
22+
23+
combined_angle = 2 * acos(acos_argument)
2224

2325
if abs(sin(combined_angle / 2)) < ATOL:
2426
return BlochSphereRotation.identity(a.qubit)

test/test_cnot_decomposer.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@ def test_cnot(self):
2929
def test_cz(self):
3030
self.assertEqual(
3131
CNOTDecomposer.decompose(cz(Qubit(0), Qubit(1))),
32-
# FIXME: this should only be H-CNOT-H no? Check https://github.com/QuTech-Delft/OpenSquirrel/issues/99
3332
[
34-
rz(Qubit(1), Float(math.pi / 2)),
33+
rz(Qubit(1), Float(math.pi)),
34+
ry(Qubit(1), Float(math.pi / 2)),
3535
cnot(Qubit(0), Qubit(1)),
36-
rz(Qubit(1), Float(-math.pi / 2)),
37-
cnot(Qubit(0), Qubit(1)),
38-
rz(Qubit(0), Float(math.pi / 2)),
36+
ry(Qubit(1), Float(-math.pi / 2)),
37+
rz(Qubit(1), Float(math.pi)),
3938
],
4039
)
4140

test/test_integration.py

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,6 @@ def test_libqasm_error(self):
166166
use_libqasm=True,
167167
)
168168

169-
@unittest.skipIf(
170-
importlib.util.find_spec("quantify_scheduler") is None, reason="quantify_scheduler is not installed"
171-
)
172169
def test_export_quantify_scheduler(self):
173170
myCircuit = Circuit.from_string(
174171
"""
@@ -177,12 +174,16 @@ def test_export_quantify_scheduler(self):
177174
qubit[3] qreg
178175
179176
h qreg[1]
177+
cz qreg[0], qreg[1]
178+
cnot qreg[0], qreg[1]
180179
crk qreg[0], qreg[1], 4
181180
h qreg[0]
182181
"""
183182
)
184183

185184
myCircuit.decompose(decomposer=CNOTDecomposer)
185+
186+
# Quantify-scheduler prefers CZ.
186187
myCircuit.replace(
187188
cnot,
188189
lambda control, target: [
@@ -191,34 +192,50 @@ def test_export_quantify_scheduler(self):
191192
h(target),
192193
],
193194
)
194-
myCircuit.merge_single_qubit_gates()
195-
myCircuit.decompose(decomposer=ZYZDecomposer) # FIXME: for best gate count we need a Z-XY decomposer.
196-
197-
exported_schedule = myCircuit.export(format=ExportFormat.QUANTIFY_SCHEDULER)
198-
199-
self.assertEqual(exported_schedule.name, "Exported OpenSquirrel circuit")
200195

201-
operation_ids = [v["operation_id"] for k, v in exported_schedule.schedulables.items()]
202-
operations = [exported_schedule.operations[operation_id].name for operation_id in operation_ids]
196+
# Reduce gate count by single-qubit gate fusion.
197+
myCircuit.merge_single_qubit_gates()
203198

204-
self.assertEqual(
205-
operations,
206-
[
207-
"Rz(1.5707963, 'qreg[1]')",
208-
"Rxy(0.19634954, 1.5707963, 'qreg[1]')",
209-
"Rz(-1.5707963, 'qreg[1]')",
210-
"CZ (qreg[0], qreg[1])",
211-
"Rz(1.5707963, 'qreg[1]')",
212-
"Rxy(-0.19634954, 1.5707963, 'qreg[1]')",
213-
"Rz(-1.5707963, 'qreg[1]')",
214-
"CZ (qreg[0], qreg[1])",
215-
"Rz(0.19634954, 'qreg[0]')",
216-
"Rxy(-1.5707963, 1.5707963, 'qreg[0]')",
217-
"Rz(3.1415927, 'qreg[0]')",
218-
"Rz(3.1415927, 'qreg[1]')",
219-
"Rxy(1.5707963, 1.5707963, 'qreg[1]')",
220-
],
221-
)
199+
# FIXME: for best gate count we need a Z-XY decomposer.
200+
# See https://github.com/QuTech-Delft/OpenSquirrel/issues/98
201+
myCircuit.decompose(decomposer=ZYZDecomposer)
202+
203+
if importlib.util.find_spec("quantify_scheduler") is None:
204+
with self.assertRaisesRegex(
205+
Exception, "quantify-scheduler is not installed, or cannot be installed on " "your system"
206+
):
207+
myCircuit.export(format=ExportFormat.QUANTIFY_SCHEDULER)
208+
else:
209+
exported_schedule = myCircuit.export(format=ExportFormat.QUANTIFY_SCHEDULER)
210+
211+
self.assertEqual(exported_schedule.name, "Exported OpenSquirrel circuit")
212+
213+
operations = [exported_schedule.operations[schedulable["operation_id"]].name for schedulable in exported_schedule.schedulables.values()]
214+
215+
self.assertEqual(
216+
operations,
217+
[
218+
"Rz(3.1415927, 'qreg[1]')",
219+
"Rxy(1.5707963, 1.5707963, 'qreg[1]')",
220+
"CZ (qreg[0], qreg[1])",
221+
"Rz(3.1415927, 'qreg[1]')",
222+
"Rxy(1.5707963, 1.5707963, 'qreg[1]')",
223+
"CZ (qreg[0], qreg[1])",
224+
"Rz(1.5707963, 'qreg[1]')",
225+
"Rxy(0.19634954, 1.5707963, 'qreg[1]')",
226+
"Rz(-1.5707963, 'qreg[1]')",
227+
"CZ (qreg[0], qreg[1])",
228+
"Rz(1.5707963, 'qreg[1]')",
229+
"Rxy(-0.19634954, 1.5707963, 'qreg[1]')",
230+
"Rz(-1.5707963, 'qreg[1]')",
231+
"CZ (qreg[0], qreg[1])",
232+
"Rz(0.19634954, 'qreg[0]')",
233+
"Rxy(-1.5707963, 1.5707963, 'qreg[0]')",
234+
"Rz(3.1415927, 'qreg[0]')",
235+
"Rz(3.1415927, 'qreg[1]')",
236+
"Rxy(1.5707963, 1.5707963, 'qreg[1]')",
237+
],
238+
)
222239

223240

224241
if __name__ == "__main__":

0 commit comments

Comments
 (0)