From a8f1dabb7023f3ab131b9891860ce48c2a99f061 Mon Sep 17 00:00:00 2001 From: seyon Date: Thu, 17 Feb 2022 14:26:09 +0000 Subject: [PATCH] add decompositions used in extensions to auto_rebase --- pytket/pytket/passes/_decompositions.py | 114 ++++++++++++++++++++++++ pytket/pytket/passes/auto_rebase.py | 21 +++-- 2 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 pytket/pytket/passes/_decompositions.py diff --git a/pytket/pytket/passes/_decompositions.py b/pytket/pytket/passes/_decompositions.py new file mode 100644 index 0000000000..941d2311fb --- /dev/null +++ b/pytket/pytket/passes/_decompositions.py @@ -0,0 +1,114 @@ +# Copyright 2019-2022 Cambridge Quantum Computing +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Union +from sympy import Expr # type: ignore +from pytket.circuit import Circuit, OpType # type: ignore + +Param = Union[float, "Expr"] + + +def approx_0_mod_2(x: Param, eps: float = 1e-10) -> bool: + """Check if parameter is approximately 0 mod 2 up to eps precision. + + :param param: Parameter, float or sympy expression. + :type param: Param + :param eps: Tolerance, defaults to 1e-10 + :type eps: float, optional + :return: Approximately 0 boolean. + :rtype: bool + """ + if isinstance(x, Expr) and not x.is_constant(): + return False + x = float(x) + x %= 2 + return min(x, 2 - x) < eps + + +def int_half(angle: float) -> int: + """Assume angle is approximately an even integer, and return the half + + :param angle: Float angle + :type angle: float + :return: Integer half of angle + :rtype: int + """ + # + two_x = round(angle) + assert not two_x % 2 + return two_x // 2 + + +def _TK1_to_RxRy(a: Param, b: Param, c: Param) -> Circuit: + return Circuit(1).Rx(-0.5, 0).Ry(c, 0).Rx(b, 0).Ry(a, 0).Rx(0.5, 0) + + +def _TK1_to_X_SX_Rz(a: Param, b: Param, c: Param) -> Circuit: + circ = Circuit(1) + correction_phase = 0.0 + + # all phase identities use, for integer k, + # Rx(2k) = Rz(2k) = (-1)^{k}I + + # _approx_0_mod_2 checks if parameters are constant + # so they can be assumed to be constant + if approx_0_mod_2(b): + circ.Rz(a + c, 0) + # b = 2k, if k is odd, then Rx(b) = -I + correction_phase += int_half(float(b)) + + elif approx_0_mod_2(b + 1): + # Use Rx(2k-1) = i(-1)^{k}X + correction_phase += -0.5 + int_half(float(b) - 1) + if approx_0_mod_2(a - c): + circ.X(0) + # a - c = 2m + # overall operation is (-1)^{m}Rx(2k -1) + correction_phase += int_half(float(a - c)) + + else: + circ.Rz(c, 0).X(0).Rz(a, 0) + + elif approx_0_mod_2(b - 0.5) and approx_0_mod_2(a) and approx_0_mod_2(c): + # a = 2k, b = 2m+0.5, c = 2n + # Rz(2k)Rx(2m + 0.5)Rz(2n) = (-1)^{k+m+n}e^{-i \pi /4} SX + circ.SX(0) + correction_phase += ( + int_half(float(b) - 0.5) + int_half(float(a)) + int_half(float(c)) - 0.25 + ) + + elif approx_0_mod_2(b + 0.5) and approx_0_mod_2(a) and approx_0_mod_2(c): + # a = 2k, b = 2m-0.5, c = 2n + # Rz(2k)Rx(2m - 0.5)Rz(2n) = (-1)^{k+m+n}e^{i \pi /4} X.SX + circ.X(0).SX(0) + correction_phase += ( + int_half(float(b) + 0.5) + int_half(float(a)) + int_half(float(c)) + 0.25 + ) + elif approx_0_mod_2(a - 0.5) and approx_0_mod_2(c - 0.5): + # Rz(2k + 0.5)Rx(b)Rz(2m + 0.5) = -i(-1)^{k+m}SX.Rz(1-b).SX + circ.SX(0).Rz(1 - b, 0).SX(0) + correction_phase += int_half(float(a) - 0.5) + int_half(float(c) - 0.5) - 0.5 + else: + circ.Rz(c + 0.5, 0).SX(0).Rz(b - 1, 0).SX(0).Rz(a + 0.5, 0) + correction_phase += -0.5 + + circ.add_phase(correction_phase) + return circ + + +def _TK1_to_U(a: Param, b: Param, c: Param) -> Circuit: + circ = Circuit(1) + circ.add_gate(OpType.U3, [b, a - 0.5, c + 0.5], [0]) + circ.add_phase(-0.5 * (a + c)) + return circ diff --git a/pytket/pytket/passes/auto_rebase.py b/pytket/pytket/passes/auto_rebase.py index f9623a2247..af3946a4b0 100644 --- a/pytket/pytket/passes/auto_rebase.py +++ b/pytket/pytket/passes/auto_rebase.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Set, Union, Callable, Dict, FrozenSet, TYPE_CHECKING +from typing import Set, Callable, Dict, FrozenSet from pytket.circuit import Circuit, OpType # type: ignore from pytket._tket.circuit import _library # type: ignore -from pytket.passes import RebaseCustom # type: ignore +from pytket.passes import RebaseCustom, SquashCustom # type: ignore -if TYPE_CHECKING: - from sympy import Expr # type: ignore +from ._decompositions import Param, _TK1_to_X_SX_Rz, _TK1_to_RxRy, _TK1_to_U class NoAutoRebase(Exception): @@ -30,7 +29,7 @@ class NoAutoRebase(Exception): OpType.ZZMax: _library._CX_using_ZZMax, OpType.XXPhase: _library._CX_using_XXPhase_0, OpType.ECR: _library._CX_using_ECR, - OpType.CZ: lambda: Circuit(2).H(1).CZ(0, 1).H(1), + OpType.CZ: _library._H_CZ_H, } @@ -49,14 +48,16 @@ def get_cx_decomposition(gateset: Set[OpType]) -> Circuit: raise NoAutoRebase("No known decomposition from CX to available gateset.") -Param = Union[str, "Expr"] - _TK1_circs: Dict[FrozenSet[OpType], Callable[[Param, Param, Param], "Circuit"]] = { frozenset({OpType.TK1}): _library._TK1_to_TK1, frozenset({OpType.PhasedX, OpType.Rz}): _library._TK1_to_PhasedXRz, frozenset({OpType.Rx, OpType.Rz}): _library._TK1_to_RzRx, + frozenset({OpType.Ry, OpType.Rx}): _TK1_to_RxRy, frozenset({OpType.Rz, OpType.H}): _library._TK1_to_RzH, + frozenset({OpType.Rz, OpType.SX, OpType.X}): _TK1_to_X_SX_Rz, + frozenset({OpType.Rz, OpType.SX}): _TK1_to_X_SX_Rz, frozenset({OpType.Rz, OpType.SX}): _library._TK1_to_RzSX, + frozenset({OpType.U3}): _TK1_to_U, } @@ -71,7 +72,11 @@ def get_TK1_decomposition_function( :return: TK1 decomposition function. :rtype: Callable[[Param, Param, Param], "Circuit"] """ - if any((matching := k).issubset(gateset) for k in _TK1_circs): + subsets = [k for k in _TK1_circs if k.issubset(gateset)] + if subsets: + # find the largest available subset + # as in general more available gates leads to smaller circuits + matching = max(subsets, key=len) return _TK1_circs[matching] raise NoAutoRebase("No known decomposition from TK1 to available gateset.")