Skip to content

Commit 918bc2a

Browse files
committed
Introduce general ABAdecomposer class
1 parent f87717a commit 918bc2a

10 files changed

+381
-65
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
from __future__ import annotations
2+
3+
import math
4+
from abc import ABC, abstractmethod
5+
from collections.abc import Callable, Iterable
6+
7+
import numpy as np
8+
9+
from opensquirrel.common import ATOL
10+
from opensquirrel.decomposer.general_decomposer import Decomposer
11+
from opensquirrel.default_gates import Rx, Ry, Rz
12+
from opensquirrel.ir import Axis, AxisLike, BlochSphereRotation, Float, Gate, Qubit
13+
from opensquirrel.utils.identity_filter import filter_out_identities
14+
15+
16+
class ABADecomposer(Decomposer, ABC):
17+
@property
18+
@abstractmethod
19+
def ra(self) -> Callable[..., BlochSphereRotation]: ...
20+
21+
@property
22+
@abstractmethod
23+
def rb(self) -> Callable[..., BlochSphereRotation]: ...
24+
25+
_gate_list: list[str] = [Rx.__name__, Ry.__name__, Rz.__name__]
26+
27+
def __init__(self) -> None:
28+
self.index_a = self._gate_list.index(self.ra.__dict__["__wrapped__"].__name__)
29+
self.index_b = self._gate_list.index(self.rb.__dict__["__wrapped__"].__name__)
30+
31+
def get_decomposition_angles(self, alpha: float, axis: AxisLike) -> tuple[float, float, float]:
32+
"""
33+
Gives the angles used in the A-B-A decomposition of the Bloch sphere rotation
34+
characterized by a rotation around `axis` of angle `alpha`.
35+
36+
Parameters:
37+
alpha: angle of the Bloch sphere rotation
38+
axis: _normalized_ axis of the Bloch sphere rotation
39+
40+
Returns:
41+
A triple (theta1, theta2, theta3) corresponding to the decomposition of the
42+
arbitrary Bloch sphere rotation into U = Ra(theta3) Rb(theta2) Ra(theta1)
43+
44+
"""
45+
axis = Axis(axis)
46+
47+
if not (-math.pi + ATOL < alpha <= math.pi + ATOL):
48+
ValueError("Angle needs to be normalized")
49+
50+
if abs(alpha - math.pi) < ATOL:
51+
# alpha == pi, math.tan(alpha / 2) is not defined.
52+
53+
p: float
54+
if abs(axis[self.index_a]) < ATOL:
55+
theta2 = math.pi
56+
p = 0
57+
m = 2 * math.acos(axis[self.index_b])
58+
59+
else:
60+
p = math.pi
61+
theta2 = 2 * math.acos(axis[self.index_a])
62+
63+
if abs(axis[self.index_a] - 1) < ATOL or abs(axis[self.index_a] + 1) < ATOL:
64+
m = p # This can be anything, but setting m = p means theta3 == 0, which is better for gate count.
65+
else:
66+
m = 2 * math.acos(axis[self.index_b] / math.sqrt(1 - axis[self.index_a] ** 2))
67+
68+
else:
69+
p = 2 * math.atan2(axis[self.index_a] * math.sin(alpha / 2), math.cos(alpha / 2))
70+
71+
acos_argument = math.cos(alpha / 2) * math.sqrt(1 + (axis[self.index_a] * math.tan(alpha / 2)) ** 2)
72+
73+
# This fixes float approximations like 1.0000000000002 which acos doesn't like.
74+
acos_argument = max(min(acos_argument, 1.0), -1.0)
75+
76+
theta2 = 2 * math.acos(acos_argument)
77+
theta2 = math.copysign(theta2, alpha)
78+
79+
if abs(math.sin(theta2 / 2)) < ATOL:
80+
m = p # This can be anything, but setting m = p means theta3 == 0, which is better for gate count.
81+
else:
82+
acos_argument = axis[self.index_b] * math.sin(alpha / 2) / math.sin(theta2 / 2)
83+
84+
# This fixes float approximations like 1.0000000000002 which acos doesn't like.
85+
acos_argument = max(min(acos_argument, 1.0), -1.0)
86+
87+
m = 2 * math.acos(acos_argument)
88+
89+
theta1 = (p + m) / 2
90+
91+
theta3 = p - theta1
92+
return theta1, theta2, theta3
93+
94+
def decompose(self, g: Gate) -> list[Gate]:
95+
if not isinstance(g, BlochSphereRotation):
96+
# Only decomposer single-qubit gates.
97+
return [g]
98+
99+
theta1, theta2, theta3 = self.get_decomposition_angles(g.angle, g.axis)
100+
a1 = self.ra.__dict__["__wrapped__"](g.qubit, Float(theta1))
101+
b = self.rb.__dict__["__wrapped__"](g.qubit, Float(theta2))
102+
a2 = self.ra.__dict__["__wrapped__"](g.qubit, Float(theta3))
103+
104+
# Note: written like this, the decomposition doesn't preserve the global phase, which is fine
105+
# since the global phase is a physically irrelevant artifact of the mathematical
106+
# model we use to describe the quantum system.
107+
108+
# Should we want to preserve it, we would need to use a raw BlochSphereRotation, which would then
109+
# be an anonymous gate in the resulting decomposed circuit:
110+
# z2 = BlochSphereRotation(qubit=g.qubit, angle=theta3, axis=(0, 0, 1), phase = g.phase)
111+
112+
return filter_out_identities([a1, b, a2])
113+
114+
115+
class ZYZDecomposer(ABADecomposer):
116+
ra = Rz
117+
rb = Ry
118+
119+
120+
class XYXDecomposer(ABADecomposer):
121+
ra = Rx
122+
rb = Ry
123+
124+
125+
class YZYDecomposer(ABADecomposer):
126+
ra = Ry
127+
rb = Rz
128+
129+
130+
class XZXDecomposer(ABADecomposer):
131+
ra = Rx
132+
rb = Rz
133+
134+
135+
class YXYDecomposer(ABADecomposer):
136+
ra = Ry
137+
rb = Rx
138+
139+
140+
class ZXZDecomposer(ABADecomposer):
141+
ra = Rz
142+
rb = Rx

opensquirrel/decomposer/xyx_decomposer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def decompose(g: Gate) -> [Gate]:
8080
# Only decomposer single-qubit gates.
8181
return [g]
8282

83-
theta1, theta2, theta3 = get_xyx_decomposition_angles(g.angle, g.axis)
83+
theta1, theta2, theta3 = XYXDecomposer().get_decomposition_angles(g.angle, g.axis)
8484

8585
x1 = Rx(g.qubit, Float(theta1))
8686
y = Ry(g.qubit, Float(theta2))

opensquirrel/decomposer/zyz_decomposer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def decompose(self, g: Gate) -> list[Gate]:
7979
# Only decomposer single-qubit gates.
8080
return [g]
8181

82-
theta1, theta2, theta3 = get_zyz_decomposition_angles(g.angle, g.axis)
82+
theta1, theta2, theta3 = ZYZDecomposer().get_decomposition_angles(g.angle, g.axis)
8383

8484
z1 = Rz(g.qubit, Float(theta1))
8585
y = Ry(g.qubit, Float(theta2))
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import math
2+
3+
import pytest
4+
5+
from opensquirrel.decomposer.aba_decomposer import XYXDecomposer
6+
from opensquirrel.default_gates import CNOT, CR, H, I, Rx, Ry, S, X, Y
7+
from opensquirrel.ir import BlochSphereRotation, Float, Gate, Qubit
8+
9+
10+
@pytest.fixture(name="decomposer")
11+
def decomposer_fixture() -> XYXDecomposer:
12+
return XYXDecomposer()
13+
14+
15+
@pytest.mark.parametrize(
16+
"gate, expected_result",
17+
[
18+
(CNOT(Qubit(0), Qubit(1)), [CNOT(Qubit(0), Qubit(1))]),
19+
(CR(Qubit(2), Qubit(3), Float(2.123)), [CR(Qubit(2), Qubit(3), Float(2.123))]),
20+
(I(Qubit(0)), []),
21+
(
22+
S(Qubit(0)),
23+
[Rx(Qubit(0), Float(math.pi / 2)), Ry(Qubit(0), Float(math.pi / 2)), Rx(Qubit(0), Float(-math.pi / 2))],
24+
),
25+
(Y(Qubit(0)), [Ry(Qubit(0), Float(math.pi))]),
26+
(Ry(Qubit(0), Float(0.9)), [Ry(Qubit(0), Float(0.9))]),
27+
(X(Qubit(0)), [Rx(Qubit(0), Float(math.pi))]),
28+
(Rx(Qubit(0), Float(0.123)), [Rx(Qubit(0), Float(0.123))]),
29+
(H(Qubit(0)), [Rx(Qubit(0), Float(math.pi)), Ry(Qubit(0), Float(math.pi / 2))]),
30+
(
31+
BlochSphereRotation(qubit=Qubit(0), angle=5.21, axis=(1, 2, 3), phase=0.324),
32+
[
33+
Rx(Qubit(0), Float(0.8251439260060653)),
34+
Ry(Qubit(0), Float(-1.030183660156084)),
35+
Rx(Qubit(0), Float(-1.140443520488592)),
36+
],
37+
),
38+
],
39+
ids=["CNOT", "CR", "I", "S", "Y", "Ry", "X", "Rx", "H", "arbitrary"],
40+
)
41+
def test_xyx_decomposer(decomposer: XYXDecomposer, gate: Gate, expected_result: list[Gate]) -> None:
42+
assert decomposer.decompose(gate) == expected_result
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import math
2+
3+
import pytest
4+
5+
from opensquirrel.decomposer.aba_decomposer import XZXDecomposer
6+
from opensquirrel.default_gates import CNOT, CR, H, I, Rx, Rz, S, X, Z
7+
from opensquirrel.ir import BlochSphereRotation, Float, Gate, Qubit
8+
9+
10+
@pytest.fixture(name="decomposer")
11+
def decomposer_fixture() -> XZXDecomposer:
12+
return XZXDecomposer()
13+
14+
15+
@pytest.mark.parametrize(
16+
"gate, expected_result",
17+
[
18+
(CNOT(Qubit(0), Qubit(1)), [CNOT(Qubit(0), Qubit(1))]),
19+
(CR(Qubit(2), Qubit(3), Float(2.123)), [CR(Qubit(2), Qubit(3), Float(2.123))]),
20+
(I(Qubit(0)), []),
21+
(S(Qubit(0)), [Rz(Qubit(0), Float(math.pi / 2))]),
22+
(Z(Qubit(0)), [Rz(Qubit(0), Float(math.pi))]),
23+
(Rz(Qubit(0), Float(0.9)), [Rz(Qubit(0), Float(0.9))]),
24+
(X(Qubit(0)), [Rx(Qubit(0), Float(math.pi))]),
25+
(Rx(Qubit(0), Float(0.123)), [Rx(Qubit(0), Float(0.123))]),
26+
(
27+
H(Qubit(0)),
28+
[Rx(Qubit(0), Float(math.pi / 2)), Rz(Qubit(0), Float(math.pi / 2)), Rx(Qubit(0), Float(math.pi / 2))],
29+
),
30+
(
31+
BlochSphereRotation(qubit=Qubit(0), angle=5.21, axis=(1, 2, 3), phase=0.324),
32+
[
33+
Rx(Qubit(0), Float(0.43035280630630446)),
34+
Rz(Qubit(0), Float(-1.030183660156084)),
35+
Rx(Qubit(0), Float(-0.7456524007888308)),
36+
],
37+
),
38+
],
39+
ids=["CNOT", "CR", "I", "S", "Y", "Ry", "X", "Rx", "H", "arbitrary"],
40+
)
41+
def test_xzx_decomposer(decomposer: XZXDecomposer, gate: Gate, expected_result: list[Gate]) -> None:
42+
assert decomposer.decompose(gate) == expected_result
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import math
2+
3+
import pytest
4+
5+
from opensquirrel.decomposer.aba_decomposer import YXYDecomposer
6+
from opensquirrel.default_gates import CNOT, CR, H, I, Rx, Ry, S, X, Y
7+
from opensquirrel.ir import BlochSphereRotation, Float, Gate, Qubit
8+
9+
10+
@pytest.fixture(name="decomposer")
11+
def decomposer_fixture() -> YXYDecomposer:
12+
return YXYDecomposer()
13+
14+
15+
@pytest.mark.parametrize(
16+
"gate, expected_result",
17+
[
18+
(CNOT(Qubit(0), Qubit(1)), [CNOT(Qubit(0), Qubit(1))]),
19+
(CR(Qubit(2), Qubit(3), Float(2.123)), [CR(Qubit(2), Qubit(3), Float(2.123))]),
20+
(I(Qubit(0)), []),
21+
(
22+
S(Qubit(0)),
23+
[Ry(Qubit(0), Float(math.pi / 2)), Rx(Qubit(0), Float(math.pi / 2)), Ry(Qubit(0), Float(-math.pi / 2))],
24+
),
25+
(Y(Qubit(0)), [Ry(Qubit(0), Float(math.pi))]),
26+
(Ry(Qubit(0), Float(0.9)), [Ry(Qubit(0), Float(0.9))]),
27+
(X(Qubit(0)), [Rx(Qubit(0), Float(math.pi))]),
28+
(Rx(Qubit(0), Float(0.123)), [Rx(Qubit(0), Float(0.123))]),
29+
(
30+
H(Qubit(0)),
31+
[Ry(Qubit(0), Float(math.pi / 4)), Rx(Qubit(0), Float(math.pi)), Ry(Qubit(0), Float(-math.pi / 4))],
32+
),
33+
(
34+
BlochSphereRotation(qubit=Qubit(0), angle=5.21, axis=(1, 2, 3), phase=0.324),
35+
[
36+
Ry(Qubit(0), Float(0.9412144817800217)),
37+
Rx(Qubit(0), Float(-0.893533136099803)),
38+
Ry(Qubit(0), Float(-1.5568770630164868)),
39+
],
40+
),
41+
],
42+
ids=["CNOT", "CR", "I", "S", "Y", "Ry", "X", "Rx", "H", "arbitrary"],
43+
)
44+
def test_yxy_decomposer(decomposer: YXYDecomposer, gate: Gate, expected_result: list[Gate]) -> None:
45+
assert decomposer.decompose(gate) == expected_result
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import math
2+
3+
import pytest
4+
5+
from opensquirrel.decomposer.aba_decomposer import YZYDecomposer
6+
from opensquirrel.default_gates import CNOT, CR, H, I, Rx, Ry, Rz, S, X, Y
7+
from opensquirrel.ir import BlochSphereRotation, Float, Gate, Qubit
8+
9+
10+
@pytest.fixture(name="decomposer")
11+
def decomposer_fixture() -> YZYDecomposer:
12+
return YZYDecomposer()
13+
14+
15+
@pytest.mark.parametrize(
16+
"gate, expected_result",
17+
[
18+
(CNOT(Qubit(0), Qubit(1)), [CNOT(Qubit(0), Qubit(1))]),
19+
(CR(Qubit(2), Qubit(3), Float(2.123)), [CR(Qubit(2), Qubit(3), Float(2.123))]),
20+
(I(Qubit(0)), []),
21+
(S(Qubit(0)), [Rz(Qubit(0), Float(math.pi / 2))]),
22+
(Y(Qubit(0)), [Ry(Qubit(0), Float(math.pi))]),
23+
(Ry(Qubit(0), Float(0.9)), [Ry(Qubit(0), Float(0.9))]),
24+
(
25+
X(Qubit(0)),
26+
[Ry(Qubit(0), Float(math.pi / 2)), Rz(Qubit(0), Float(math.pi)), Ry(Qubit(0), Float(-math.pi / 2))],
27+
),
28+
(
29+
Rx(Qubit(0), Float(0.123)),
30+
[
31+
Ry(Qubit(0), Float(math.pi / 2)),
32+
Rz(Qubit(0), Float(0.12300000000000022)),
33+
Ry(Qubit(0), Float(-math.pi / 2)),
34+
],
35+
),
36+
(
37+
H(Qubit(0)),
38+
[Ry(Qubit(0), Float(math.pi / 4)), Rz(Qubit(0), Float(math.pi)), Ry(Qubit(0), Float(-math.pi / 4))],
39+
),
40+
(
41+
BlochSphereRotation(qubit=Qubit(0), angle=5.21, axis=(1, 2, 3), phase=0.324),
42+
[
43+
Ry(Qubit(0), Float(0.013919263778408464)),
44+
Rz(Qubit(0), Float(-0.893533136099803)),
45+
Ry(Qubit(0), Float(-0.6295818450148737)),
46+
],
47+
),
48+
],
49+
ids=["CNOT", "CR", "I", "S", "Y", "Ry", "X", "Rx", "H", "arbitrary"],
50+
)
51+
def test_yzy_decomposer(decomposer: YZYDecomposer, gate: Gate, expected_result: list[Gate]) -> None:
52+
assert decomposer.decompose(gate) == expected_result

0 commit comments

Comments
 (0)