From ddabf5e0da3c0668669a8789a60c53ed24cd93f3 Mon Sep 17 00:00:00 2001 From: seyon Date: Mon, 14 Feb 2022 16:31:08 +0000 Subject: [PATCH] [feature] auto_rebase_pass function for attempting automatic rebase passes --- pytket/docs/changelog.rst | 6 +++ pytket/pytket/passes/__init__.py | 1 + pytket/pytket/passes/auto_rebase.py | 71 +++++++++++++++++++++++++++++ pytket/tests/transform_test.py | 53 ++++++++++++++++++++- 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 pytket/pytket/passes/auto_rebase.py diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 9cbffbb751..5085d8c57a 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -11,6 +11,12 @@ API changes: * The deprecated ``Backend.compile_circuit`` method is removed. (Use ``get_compiled_circuit`` instead.) + +Minor new features: + +* New ``pytket.passes.auto_rebase_pass`` which attempts to construct a rebase pass given a target gate set from known decompositions. +* ``RebaseCustom`` takes one allowed gateset parameter rather than separate single qubit and multiqubit gatesets. + 0.19.0 (February 2022) ---------------------- diff --git a/pytket/pytket/passes/__init__.py b/pytket/pytket/passes/__init__.py index 07004a66a4..aa68496e71 100644 --- a/pytket/pytket/passes/__init__.py +++ b/pytket/pytket/passes/__init__.py @@ -17,3 +17,4 @@ from pytket._tket.passes import * # type: ignore from .script import compilation_pass_from_script, compilation_pass_grammar +from .auto_rebase import auto_rebase_pass diff --git a/pytket/pytket/passes/auto_rebase.py b/pytket/pytket/passes/auto_rebase.py new file mode 100644 index 0000000000..307407f417 --- /dev/null +++ b/pytket/pytket/passes/auto_rebase.py @@ -0,0 +1,71 @@ +# 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, Callable, Dict, FrozenSet, TYPE_CHECKING +from pytket.circuit import Circuit, OpType, pool # type: ignore +from pytket.passes import RebaseCustom # type: ignore + +if TYPE_CHECKING: + from sympy import Expr # type: ignore + + +class NoAutoRebase(Exception): + """Automatic rebase could not be found""" + + +_CX_CIRCS: Dict[OpType, Callable[[], "Circuit"]] = { + OpType.CX: pool.CX, + OpType.ZZMax: pool.CX_using_ZZMax, + OpType.XXPhase: pool.CX_using_XXPhase_0, + OpType.ECR: pool.CX_using_ECR, + OpType.CZ: lambda: Circuit(2).H(1).CZ(0, 1).H(1), +} + +Param = Union[str, "Expr"] + +_tk1_circs: Dict[FrozenSet[OpType], Callable[[Param, Param, Param], "Circuit"]] = { + frozenset({OpType.TK1}): pool.tk1_to_tk1, + frozenset({OpType.PhasedX, OpType.Rz}): pool.tk1_to_PhasedXRz, + frozenset({OpType.Rx, OpType.Rz}): pool.tk1_to_rzrx, + frozenset({OpType.Rz, OpType.H}): pool.tk1_to_rzh, + frozenset({OpType.Rz, OpType.SX}): pool.tk1_to_rzsx, +} + + +def auto_rebase_pass(gateset: FrozenSet[OpType]) -> RebaseCustom: + """Attempt to generate a rebase pass automatically for the given target + gateset. + Checks if there are known existing decompositions from CX + to target gateset and TK1 to target gateset and uses thsoe to construct a + CustomRebase. + Raises an error if known decompositions can be found, in which case try + using RebaseCustom with your own decompositions. + + :param gateset: Set of supported OpTypes, target gate set + :type gateset: FrozenSet[OpType] + :raises NoAutoRebase: No suitable CX or TK1 decomposition found. + :return: Rebase pass. + :rtype: RebaseCustom + """ + if any((matching := k) in gateset for k in _CX_CIRCS): + cx_circ = _CX_CIRCS[matching]() + else: + raise NoAutoRebase("No known decomposition from CX to available gateset.") + + if any((matching := k).issubset(gateset) for k in _tk1_circs): + tk1_func = _tk1_circs[matching] + else: + raise NoAutoRebase("No known decomposition from TK1 to available gateset.") + + return RebaseCustom(set(gateset), cx_circ, tk1_func) diff --git a/pytket/tests/transform_test.py b/pytket/tests/transform_test.py index 14d5d3afd8..db15b86c55 100644 --- a/pytket/tests/transform_test.py +++ b/pytket/tests/transform_test.py @@ -14,8 +14,11 @@ from pathlib import Path from pytket.circuit import Circuit, OpType, PauliExpBox # type: ignore +from pytket.circuit import pool # type: ignore from pytket.pauli import Pauli # type: ignore -from pytket.passes import RemoveRedundancies, KAKDecomposition, ThreeQubitSquash, CommuteThroughMultis, PauliSquash, FullPeepholeOptimise, GlobalisePhasedX # type: ignore +from pytket.passes import RemoveRedundancies, KAKDecomposition, ThreeQubitSquash, CommuteThroughMultis, PauliSquash, FullPeepholeOptimise, GlobalisePhasedX, RebaseCustom # type: ignore +from pytket.passes import auto_rebase_pass +from pytket.passes.auto_rebase import _CX_CIRCS, NoAutoRebase from pytket.predicates import CompilationUnit # type: ignore from pytket.transform import Transform, CXConfigType, PauliSynthStrat # type: ignore from pytket.qasm import circuit_from_qasm @@ -729,6 +732,54 @@ def test_full_peephole_optimise() -> None: assert n_cx1 < n_cz +def test_auto_rebase() -> None: + pass_params = [ + ({OpType.CX, OpType.Rz, OpType.Rx}, pool.CX(), pool.tk1_to_rzrx), + ( + {OpType.CZ, OpType.Rz, OpType.SX, OpType.ZZPhase}, + _CX_CIRCS[OpType.CZ](), + pool.tk1_to_rzsx, + ), + ( + {OpType.ZZMax, OpType.T, OpType.Rz, OpType.H}, + pool.CX_using_ZZMax(), + pool.tk1_to_rzh, + ), + ( + {OpType.XXPhase, OpType.T, OpType.Rz, OpType.H}, + pool.CX_using_XXPhase_0(), + pool.tk1_to_rzh, + ), + ( + {OpType.ECR, OpType.PhasedX, OpType.Rz, OpType.CnX}, + pool.CX_using_ECR(), + pool.tk1_to_PhasedXRz, + ), + ( + {OpType.CX, OpType.TK1, OpType.U3, OpType.CnX}, + pool.CX(), + pool.tk1_to_tk1, + ), + ] + + circ = get_test_circuit() + + for gateset, cx_circ, tk1_func in pass_params: + rebase = auto_rebase_pass(frozenset(gateset)) + assert rebase.to_dict() == RebaseCustom(gateset, cx_circ, tk1_func).to_dict() + + c2 = circ.copy() + assert rebase.apply(c2) + + with pytest.raises(NoAutoRebase) as cx_err: + _ = auto_rebase_pass(frozenset({OpType.ZZPhase, OpType.TK1})) + assert "CX" in str(cx_err.value) + + with pytest.raises(NoAutoRebase) as cx_err: + _ = auto_rebase_pass(frozenset({OpType.CX, OpType.H, OpType.T})) + assert "TK1" in str(cx_err.value) + + if __name__ == "__main__": test_remove_redundancies() test_reduce_singles()