Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update auto_rebase_pass to take optional allow_swaps argument #947

Merged
merged 32 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
492c754
Further changes to auto rebase
sjdilkes Jul 20, 2023
95d4902
Undo accidental commited changes
sjdilkes Jul 24, 2023
63de41f
Remove comments, working up to phase
sjdilkes Jul 24, 2023
ca6cfc8
Update CircPool.cpp
sjdilkes Jul 26, 2023
a3af378
Update Rebase.cpp
sjdilkes Jul 26, 2023
0543bbc
Remove comments from transform_test and add gate count assertions
sjdilkes Jul 26, 2023
f30734a
Remove redundant commented out code
sjdilkes Jul 26, 2023
a2d77a5
Merge branch 'develop' into allow-swap-in-auto-rebase
sjdilkes Jul 26, 2023
8e64eb0
bump
sjdilkes Jul 26, 2023
493ce0a
add c++ tests
sjdilkes Jul 26, 2023
f3726c2
update auto rebase and add tests for CX case
sjdilkes Jul 26, 2023
387fbbe
Update CircPool.cpp
sjdilkes Jul 26, 2023
5ef2b9b
clang format update
sjdilkes Jul 27, 2023
b98b9cc
update changes for sycamore gate
sjdilkes Aug 1, 2023
a315e95
Add tests for new zzphase and tk2 sswap cases
sjdilkes Aug 1, 2023
9a65b83
Merge branch 'develop' into allow-swap-in-auto-rebase
sjdilkes Aug 1, 2023
da721c9
Update auto_rebase.py
sjdilkes Aug 1, 2023
28df6fb
Update changelog.rst
sjdilkes Aug 1, 2023
b9b626d
Update test_CompilerPass.cpp
sjdilkes Aug 1, 2023
c376908
Update test_CompilerPass.cpp
sjdilkes Aug 1, 2023
a6e70ec
Update test_CompilerPass.cpp
sjdilkes Aug 1, 2023
73723c7
Update CircPool.cpp
sjdilkes Aug 1, 2023
cf7198d
Remove sycamore tests
sjdilkes Aug 2, 2023
41bfb1c
Update CircPool.cpp
sjdilkes Aug 2, 2023
27ab14e
Update CircPool.cpp
sjdilkes Aug 2, 2023
b6f942e
Move TK2 SWAP decomp into c++, update overall method
sjdilkes Aug 2, 2023
4fc8d9d
Rewrite swap replacement ot be basic
sjdilkes Aug 3, 2023
9702175
Update test_CompilerPass.cpp
sjdilkes Aug 3, 2023
7668bbf
Merge branch 'develop' into allow-swap-in-auto-rebase
sjdilkes Aug 3, 2023
ae47758
Fix missing space and readd python tests
sjdilkes Aug 3, 2023
a65eb14
Update transform_test.py
sjdilkes Aug 3, 2023
5e24c2d
Update transform_test.py
sjdilkes Aug 3, 2023
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
9 changes: 9 additions & 0 deletions pytket/binders/circuit/library.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ void init_library(py::module &m) {
"Given expressions α, β and γ, return circuit equivalent to "
"TK2(α, β, γ) using up to 3 CX and single-qubit gates.\n\n"
"The decomposition minimizes the number of CX gates.");
library_m.def(
"_TK2_using_CX_and_swap", &CircPool::TK2_using_CX_and_swap,
"Given expressions α, β and γ, return circuit equivalent to "
"TK2(α, β, γ), up to a wire swap, using up to 3 CX and single-qubit "
cqc-alec marked this conversation as resolved.
Show resolved Hide resolved
"gates.\n\n"
"The decomposition minimizes the number of CX gates.");
library_m.def(
"_approx_TK2_using_1xCX", &CircPool::approx_TK2_using_1xCX,
"Best approximation of TK2 using 1 CX gate and single-qubit gates, using "
Expand Down Expand Up @@ -227,6 +233,9 @@ void init_library(py::module &m) {
library_m.def(
"_TK2_using_ZZMax", &CircPool::TK2_using_ZZMax,
"Equivalent to TK2, using up to 3 ZZMax gates.");
library_m.def(
"_TK2_using_ZZMax_and_swap", &CircPool::TK2_using_ZZMax_and_swap,
"Equivalent to TK2, up to a wire swap, using up to 3 ZZMax gates.");
library_m.def(
"_XXPhase3_using_TK2", &CircPool::XXPhase3_using_TK2,
"Equivalent to XXPhase3, using three TK2 gates");
Expand Down
2 changes: 1 addition & 1 deletion pytket/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def package(self):
cmake.install()

def requirements(self):
self.requires("tket/1.2.28@tket/stable")
self.requires("tket/1.2.29@tket/stable")
self.requires("tklog/0.3.3@tket/stable")
self.requires("tkrng/0.3.3@tket/stable")
self.requires("tkassert/0.3.3@tket/stable")
Expand Down
31 changes: 26 additions & 5 deletions pytket/pytket/passes/auto_rebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ def _TK2_using_TK2(a: Param, b: Param, c: Param) -> Circuit:
OpType.ZZMax: _library._TK2_using_ZZMax,
}

_TK2_CIRCS_WIRE_SWAP: Dict[OpType, Callable[[Param, Param, Param], "Circuit"]] = {
OpType.TK2: _TK2_using_TK2,
OpType.ZZPhase: _library._TK2_using_ZZPhase,
cqc-alec marked this conversation as resolved.
Show resolved Hide resolved
OpType.CX: _library._TK2_using_CX_and_swap,
OpType.ZZMax: _library._TK2_using_ZZMax_and_swap,
}


def get_cx_decomposition(gateset: Set[OpType]) -> Circuit:
"""Return a Circuit expressing a CX in terms of a two qubit gate in the
Expand All @@ -62,6 +69,7 @@ def get_cx_decomposition(gateset: Set[OpType]) -> Circuit:

def get_tk2_decomposition(
gateset: Set[OpType],
allow_swaps: bool,
) -> Callable[[Param, Param, Param], "Circuit"]:
"""Return a function to construct a circuit expressing a TK2 in terms of gates in
the given gateset, if such a function is available.
Expand All @@ -70,9 +78,15 @@ def get_tk2_decomposition(
:raises NoAutoRebase: no suitable TK2 decomposition found
:return: function to decompose TK2 gates
"""
for k, fn in _TK2_CIRCS.items():
if k in gateset:
return fn
if allow_swaps:
for k, fn in _TK2_CIRCS_WIRE_SWAP.items():
if k in gateset:
return fn
else:
for k, fn in _TK2_CIRCS.items():
if k in gateset:
return fn

raise NoAutoRebase("No known decomposition from TK2 to given gateset")


Expand Down Expand Up @@ -109,7 +123,7 @@ def get_TK1_decomposition_function(
raise NoAutoRebase("No known decomposition from TK1 to available gateset.")


def auto_rebase_pass(gateset: Set[OpType]) -> RebaseCustom:
def auto_rebase_pass(gateset: Set[OpType], allow_swaps: bool = False) -> RebaseCustom:
"""Attempt to generate a rebase pass automatically for the given target
gateset.

Expand All @@ -127,12 +141,19 @@ def auto_rebase_pass(gateset: Set[OpType]) -> RebaseCustom:
"""
tk1 = get_TK1_decomposition_function(gateset)

if allow_swaps:
try:
return RebaseCustom(
gateset, get_tk2_decomposition(gateset, allow_swaps), tk1
)
except NoAutoRebase:
pass
# if the gateset has CX but not TK2, rebase via CX
if OpType.CX in gateset and OpType.TK2 not in gateset:
cqc-alec marked this conversation as resolved.
Show resolved Hide resolved
return RebaseCustom(gateset, _library._CX(), tk1)
# in other cases, try to rebase via TK2 first
try:
return RebaseCustom(gateset, get_tk2_decomposition(gateset), tk1)
return RebaseCustom(gateset, get_tk2_decomposition(gateset, allow_swaps), tk1)
except NoAutoRebase:
pass
try:
Expand Down
137 changes: 136 additions & 1 deletion pytket/tests/transform_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ def test_pauli_graph_synth() -> None:
num_cxs = c.n_gates_of_type(OpType.CX)
cx_counts.append(num_cxs)

for (i, count) in enumerate(cx_counts):
for i, count in enumerate(cx_counts):
if i == 0:
continue
assert count < cx_counts[i - 1]
Expand Down Expand Up @@ -1216,6 +1216,139 @@ def test_round_angles() -> None:
assert circ0 == circ1


def test_auto_rebase_with_swap_cx() -> None:
swap_pass = auto_rebase_pass({OpType.CX, OpType.PhasedX, OpType.Rz}, True)
no_swap_pass = auto_rebase_pass({OpType.CX, OpType.PhasedX, OpType.Rz}, False)

c_swap = Circuit(2).ISWAPMax(0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.CX) == 1
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(1)
assert iqp[Qubit(1)] == Qubit(0)
c_no_swap = Circuit(2).ISWAPMax(0, 1)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.CX) == 2

c_swap = Circuit(2).Sycamore(0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.CX) == 2
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(1)
assert iqp[Qubit(1)] == Qubit(0)
c_no_swap = Circuit(2).Sycamore(0, 1)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.CX) == 3

c_swap = Circuit(2).ISWAP(0.3, 0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.CX) == 2
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(0)
assert iqp[Qubit(1)] == Qubit(1)
c_no_swap = Circuit(2).ISWAP(0.3, 0, 1)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.CX) == 2

c_swap = Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.CX) == 2
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(0)
assert iqp[Qubit(1)] == Qubit(1)
c_no_swap = Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.CX) == 4

c_swap = Circuit(2).SWAP(0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.CX) == 0
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(1)
assert iqp[Qubit(1)] == Qubit(0)
c_no_swap = Circuit(2).SWAP(0, 1)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.CX) == 3

c_swap = Circuit(2).ZZMax(0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.CX) == 1

c_swap = Circuit(2).CX(0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates == 1


def test_auto_rebase_with_swap_zzmax() -> None:
swap_pass = auto_rebase_pass({OpType.ZZMax, OpType.PhasedX, OpType.Rz}, True)
no_swap_pass = auto_rebase_pass({OpType.ZZMax, OpType.PhasedX, OpType.Rz}, False)

c_swap = Circuit(2).ISWAPMax(0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.ZZMax) == 1
assert c_swap.n_gates == 4
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(1)
assert iqp[Qubit(1)] == Qubit(0)
c_no_swap = Circuit(2).ISWAPMax(0, 1)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.ZZMax) == 2
assert c_no_swap.n_gates == 13

c_swap = Circuit(2).Sycamore(0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.ZZMax) == 2
assert c_swap.n_gates == 12
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(1)
assert iqp[Qubit(1)] == Qubit(0)

c_no_swap = Circuit(2).Sycamore(0, 1)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.ZZMax) == 3
assert c_no_swap.n_gates == 16

c_swap = Circuit(2).ISWAP(0.3, 0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.ZZMax) == 2
assert c_swap.n_gates == 13
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(0)
assert iqp[Qubit(1)] == Qubit(1)
c_no_swap = Circuit(2).ISWAP(0.3, 0, 1)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.ZZMax) == 2
assert c_no_swap.n_gates == 13

c_swap = Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.ZZMax) == 2
assert c_swap.n_gates == 8
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(0)
assert iqp[Qubit(1)] == Qubit(1)
c_no_swap = Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.ZZMax) == 4
assert c_no_swap.n_gates == 26

c_swap = Circuit(2).SWAP(0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates_of_type(OpType.ZZMax) == 0
assert c_swap.n_gates == 0
iqp = c_swap.implicit_qubit_permutation()
assert iqp[Qubit(0)] == Qubit(1)
assert iqp[Qubit(1)] == Qubit(0)
c_no_swap = Circuit(2).SWAP(0, 1)
no_swap_pass.apply(c_no_swap)
assert c_no_swap.n_gates_of_type(OpType.ZZMax) == 3
assert c_no_swap.n_gates == 16

c_swap = Circuit(2).ZZMax(0, 1)
swap_pass.apply(c_swap)
assert c_swap.n_gates == 1


if __name__ == "__main__":
test_remove_redundancies()
test_reduce_singles()
Expand All @@ -1242,3 +1375,5 @@ def test_round_angles() -> None:
test_CXMappingPass_terminates()
test_FullMappingPass()
test_KAK_with_ClassicalExpBox()
test_auto_rebase_with_swap_cx()
test_auto_rebase_with_swap_zzmax()
2 changes: 1 addition & 1 deletion tket/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

class TketConan(ConanFile):
name = "tket"
version = "1.2.28"
version = "1.2.29"
package_type = "library"
license = "Apache 2"
homepage = "https://github.com/CQCL/tket"
Expand Down
20 changes: 20 additions & 0 deletions tket/include/tket/Circuit/CircPool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,17 @@ Circuit normalised_TK2_using_CX(
*/
Circuit TK2_using_CX(const Expr &alpha, const Expr &beta, const Expr &gamma);

/**
* @brief Equivalent to TK2(α, β, γ) up to a wire swap, with minimal number of
* CX gates.
*
* A TK2-equivalent circuit with as few CX gates as possible (0, 1, 2 or 3 CX).
*
* @return Circuit Equivalent circuit, up to a wire swap, to TK2(α, β, γ).
cqc-alec marked this conversation as resolved.
Show resolved Hide resolved
*/
Circuit TK2_using_CX_and_swap(
const Expr &alpha, const Expr &beta, const Expr &gamma);

/**
* @brief Equivalent to TK2(α, 0, 0), using 1 ZZPhase gate.
*
Expand Down Expand Up @@ -367,6 +378,15 @@ Circuit TK2_using_ZZPhase(
*/
Circuit TK2_using_ZZMax(const Expr &alpha, const Expr &beta, const Expr &gamma);

/**
* @brief Equivalent to TK2(α, β, γ), up to a wire swap, using up to 3 ZZMax
* gates.
*
* @return Circuit equivalent to TK2(α, β, γ) up to a wire swap.
*/
Circuit TK2_using_ZZMax_and_swap(
const Expr &alpha, const Expr &beta, const Expr &gamma);

/** Equivalent to XXPhase3, using three TK2 gates */
Circuit XXPhase3_using_TK2(const Expr &alpha);

Expand Down
Loading