Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6845628
Merge pull request #213 from QuTech-Delft/develop
juanboschero Jun 3, 2024
cd6c838
Add XYX decomposition
juanboschero Jun 3, 2024
b1ab2f5
Fix isort & black
juanboschero Jun 3, 2024
3b0aea7
Add ABADecomposer class.
rturrado Jun 12, 2024
f87717a
Merge branch 'develop' into 98-create-x-yz-decomposer-for-quantify-sc…
juanboschero Jun 13, 2024
918bc2a
Introduce general ABAdecomposer class
juanboschero Jun 14, 2024
b425c52
Merge remote-tracking branch 'origin/98-create-x-yz-decomposer-for-qu…
rturrado Jun 14, 2024
a7f3fc8
Fix ABADecomposer.
rturrado Jun 14, 2024
99d21d2
Run isort and black.
rturrado Jun 14, 2024
6383173
Run mypy.
rturrado Jun 14, 2024
76c9312
Update CONTRIBUTING.md.
rturrado Jun 14, 2024
0f962c5
Remove unused imports.
rturrado Jun 14, 2024
413586d
Trying to fix Python 3.8 errors.
rturrado Jun 14, 2024
e1515b7
Fix imports in integration and remove deprecated files
juanboschero Jun 17, 2024
a621401
Fix imports in integration
juanboschero Jun 17, 2024
b334b61
Fix mypy errors in decomposer
juanboschero Jun 17, 2024
6fbd4e0
Merge pull request #231 from QuTech-Delft/98-fix-aba-decomposer
juanboschero Jun 17, 2024
d852265
Modify aba_decomposer init and modify CONTRIBUTING.md
juanboschero Jun 17, 2024
3eb92f5
Add docstrings to aba_decompose
juanboschero Jun 18, 2024
aa39451
Fix mypy issues
juanboschero Jun 18, 2024
6ee9335
Fix docstrings and type hinting
juanboschero Jun 18, 2024
366ed0d
Modify Self import in circuit_builder
juanboschero Jun 19, 2024
2a74a85
Modify Self import in circuit_builder
juanboschero Jun 19, 2024
f0ce3c1
Update opensquirrel/decomposer/aba_decomposer.py
juanboschero Jun 19, 2024
4b98ead
Update opensquirrel/decomposer/aba_decomposer.py
juanboschero Jun 19, 2024
f8fa823
Merge branch 'develop' into 98-create-x-yz-decomposer-for-quantify-sc…
juanboschero Jun 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ From a `poetry` shell (started from an OpenSquirrel checkout):

```
$ pytest -vv
$ mypy . --strict
$ poetry run isort .
$ poetry run black .
$ poetry run mypy opensquirrel --strict
```

## Setting the Python interpreter (PyCharm)
Expand Down
184 changes: 184 additions & 0 deletions opensquirrel/decomposer/aba_decomposer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
""" Module containing classes that inherit from the ABADecomposer class to decompose a circuit into one of the Pauli
ABA decompositions."""

from __future__ import annotations

import math
from abc import ABC, abstractmethod
from collections.abc import Callable

from opensquirrel.common import ATOL
from opensquirrel.decomposer.general_decomposer import Decomposer
from opensquirrel.default_gates import Rx, Ry, Rz
from opensquirrel.ir import Axis, AxisLike, BlochSphereRotation, Float, Gate
from opensquirrel.utils.identity_filter import filter_out_identities


class ABADecomposer(Decomposer, ABC):
@property
@abstractmethod
def ra(self) -> Callable[..., BlochSphereRotation]: ...

@property
@abstractmethod
def rb(self) -> Callable[..., BlochSphereRotation]: ...

_gate_list: list[Callable[..., BlochSphereRotation]] = [Rx, Ry, Rz]

def __init__(self) -> None:
self.index_a = self._gate_list.index(self.ra)
self.index_b = self._gate_list.index(self.rb)

def get_decomposition_angles(self, alpha: float, axis: AxisLike) -> tuple[float, float, float]:
"""Gives the angles used in the A-B-A decomposition of the Bloch sphere rotation
characterized by a rotation around `axis` of angle `alpha`.

Parameters:
alpha: angle of the Bloch sphere rotation
axis: _normalized_ axis of the Bloch sphere rotation

Returns:
A triple (theta1, theta2, theta3) corresponding to the decomposition of the
arbitrary Bloch sphere rotation into U = Ra(theta3) Rb(theta2) Ra(theta1)

"""
axis = Axis(axis)
a_axis_value = axis[self.index_a]
b_axis_value = axis[self.index_b]
if not (-math.pi + ATOL < alpha <= math.pi + ATOL):
raise ValueError("Angle needs to be normalized")

if abs(alpha - math.pi) < ATOL:
# alpha == pi, math.tan(alpha / 2) is not defined.

if abs(a_axis_value) < ATOL:
theta2 = math.pi
p = 0.0
m = 2 * math.acos(b_axis_value)

else:
p = math.pi
theta2 = 2 * math.acos(a_axis_value)

if abs(a_axis_value - 1) < ATOL or abs(a_axis_value + 1) < ATOL:
m = p # This can be anything, but setting m = p means theta3 == 0, which is better for gate count.
else:
m = 2 * math.acos(b_axis_value / math.sqrt(1 - a_axis_value**2))

else:
p = 2 * math.atan2(a_axis_value * math.sin(alpha / 2), math.cos(alpha / 2))

acos_argument = math.cos(alpha / 2) * math.sqrt(1 + (a_axis_value * math.tan(alpha / 2)) ** 2)

# This fixes float approximations like 1.0000000000002 which acos doesn't like.
acos_argument = max(min(acos_argument, 1.0), -1.0)

theta2 = 2 * math.acos(acos_argument)
theta2 = math.copysign(theta2, alpha)

if abs(math.sin(theta2 / 2)) < ATOL:
m = p # This can be anything, but setting m = p means theta3 == 0, which is better for gate count.
else:
acos_argument = float(b_axis_value) * math.sin(alpha / 2) / math.sin(theta2 / 2)

# This fixes float approximations like 1.0000000000002 which acos doesn't like.
acos_argument = max(min(acos_argument, 1.0), -1.0)

m = 2 * math.acos(acos_argument)

theta1 = (p + m) / 2

theta3 = p - theta1
return theta1, theta2, theta3

def decompose(self, g: Gate) -> list[Gate]:
"""General A-B-A decomposition function for a single gate.

Args:
g: gate to decompose.

Returns:
Three gates, following the A-B-A convention, corresponding to the decomposition of the input gate.
"""
if not isinstance(g, BlochSphereRotation):
# We only decompose Bloch sphere rotations.
return [g]

theta1, theta2, theta3 = self.get_decomposition_angles(g.angle, g.axis)
a1 = self.ra(g.qubit, Float(theta1))
b = self.rb(g.qubit, Float(theta2))
a2 = self.ra(g.qubit, Float(theta3))

return filter_out_identities([a1, b, a2])


class XYXDecomposer(ABADecomposer):
"""Class responsible for the X-Y-X decomposition."""

@property
def ra(self) -> Callable[..., BlochSphereRotation]:
return Rx

@property
def rb(self) -> Callable[..., BlochSphereRotation]:
return Ry


class XZXDecomposer(ABADecomposer):
"""Class responsible for the X-Z-X decomposition."""

@property
def ra(self) -> Callable[..., BlochSphereRotation]:
return Rx

@property
def rb(self) -> Callable[..., BlochSphereRotation]:
return Rz


class YXYDecomposer(ABADecomposer):
"""Class responsible for the Y-X-Y decomposition."""

@property
def ra(self) -> Callable[..., BlochSphereRotation]:
return Ry

@property
def rb(self) -> Callable[..., BlochSphereRotation]:
return Rx


class YZYDecomposer(ABADecomposer):
"""Class responsible for the Y-Z-Y decomposition."""

@property
def ra(self) -> Callable[..., BlochSphereRotation]:
return Ry

@property
def rb(self) -> Callable[..., BlochSphereRotation]:
return Rz


class ZXZDecomposer(ABADecomposer):
"""Class responsible for the Z-X-Z decomposition."""

@property
def ra(self) -> Callable[..., BlochSphereRotation]:
return Rz

@property
def rb(self) -> Callable[..., BlochSphereRotation]:
return Rx


class ZYZDecomposer(ABADecomposer):
"""Class responsible for the Z-Y-Z decomposition."""

@property
def ra(self) -> Callable[..., BlochSphereRotation]:
return Rz

@property
def rb(self) -> Callable[..., BlochSphereRotation]:
return Ry
6 changes: 3 additions & 3 deletions opensquirrel/decomposer/cnot_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import math

from opensquirrel.common import ATOL
from opensquirrel.decomposer.aba_decomposer import ZYZDecomposer
from opensquirrel.decomposer.general_decomposer import Decomposer
from opensquirrel.decomposer.zyz_decomposer import get_zyz_decomposition_angles
from opensquirrel.default_gates import CNOT, Ry, Rz, X
from opensquirrel.ir import BlochSphereRotation, ControlledGate, Float, Gate
from opensquirrel.merger import general_merger
Expand Down Expand Up @@ -39,7 +39,7 @@ def decompose(self, g: Gate) -> list[Gate]:

# Try special case first, see https://arxiv.org/pdf/quant-ph/9503016.pdf lemma 5.5
controlled_rotation_times_x = general_merger.compose_bloch_sphere_rotations(X(target_qubit), g.target_gate)
theta0_with_x, theta1_with_x, theta2_with_x = get_zyz_decomposition_angles(
theta0_with_x, theta1_with_x, theta2_with_x = ZYZDecomposer().get_decomposition_angles(
controlled_rotation_times_x.angle, controlled_rotation_times_x.axis
)
if abs((theta0_with_x - theta2_with_x) % (2 * math.pi)) < ATOL:
Expand All @@ -59,7 +59,7 @@ def decompose(self, g: Gate) -> list[Gate]:
+ [Rz(q=g.control_qubit, theta=Float(g.target_gate.phase - math.pi / 2))]
)

theta0, theta1, theta2 = get_zyz_decomposition_angles(g.target_gate.angle, g.target_gate.axis)
theta0, theta1, theta2 = ZYZDecomposer().get_decomposition_angles(g.target_gate.angle, g.target_gate.axis)

A = [Ry(q=target_qubit, theta=Float(theta1 / 2)), Rz(q=target_qubit, theta=Float(theta2))]

Expand Down
96 changes: 0 additions & 96 deletions opensquirrel/decomposer/zyz_decomposer.py

This file was deleted.

4 changes: 2 additions & 2 deletions test/decomposer/test_cnot_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import pytest

from opensquirrel.decomposer.cnot_decomposer import CNOTDecomposer
from opensquirrel.default_gates import CNOT, CZ, SWAP, ControlledGate, H, Ry, Rz, X
from opensquirrel.ir import Float, Gate, Qubit
from opensquirrel.default_gates import CNOT, CZ, SWAP, H, Ry, Rz, X
from opensquirrel.ir import ControlledGate, Float, Gate, Qubit


@pytest.fixture(name="decomposer")
Expand Down
44 changes: 44 additions & 0 deletions test/decomposer/test_xyx_decomposer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from __future__ import annotations

import math

import pytest

from opensquirrel.decomposer.aba_decomposer import XYXDecomposer
from opensquirrel.default_gates import CNOT, CR, H, I, Rx, Ry, S, X, Y
from opensquirrel.ir import BlochSphereRotation, Float, Gate, Qubit


@pytest.fixture(name="decomposer")
def decomposer_fixture() -> XYXDecomposer:
return XYXDecomposer()


@pytest.mark.parametrize(
"gate, expected_result",
[
(CNOT(Qubit(0), Qubit(1)), [CNOT(Qubit(0), Qubit(1))]),
(CR(Qubit(2), Qubit(3), Float(2.123)), [CR(Qubit(2), Qubit(3), Float(2.123))]),
(I(Qubit(0)), []),
(
S(Qubit(0)),
[Rx(Qubit(0), Float(math.pi / 2)), Ry(Qubit(0), Float(math.pi / 2)), Rx(Qubit(0), Float(-math.pi / 2))],
),
(Y(Qubit(0)), [Ry(Qubit(0), Float(math.pi))]),
(Ry(Qubit(0), Float(0.9)), [Ry(Qubit(0), Float(0.9))]),
(X(Qubit(0)), [Rx(Qubit(0), Float(math.pi))]),
(Rx(Qubit(0), Float(0.123)), [Rx(Qubit(0), Float(0.123))]),
(H(Qubit(0)), [Rx(Qubit(0), Float(math.pi)), Ry(Qubit(0), Float(math.pi / 2))]),
(
BlochSphereRotation(qubit=Qubit(0), angle=5.21, axis=(1, 2, 3), phase=0.324),
[
Rx(Qubit(0), Float(0.8251439260060653)),
Ry(Qubit(0), Float(-1.030183660156084)),
Rx(Qubit(0), Float(-1.140443520488592)),
],
),
],
ids=["CNOT", "CR", "I", "S", "Y", "Ry", "X", "Rx", "H", "arbitrary"],
)
def test_xyx_decomposer(decomposer: XYXDecomposer, gate: Gate, expected_result: list[Gate]) -> None:
assert decomposer.decompose(gate) == expected_result
Loading