From 3df27b84628c094ec2e9c4de2db9be3939f08b85 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:28:33 +0100 Subject: [PATCH] Modify and fix `CliffordResynthesis` pass (#1476) --- pytket/binders/passes.cpp | 4 +- pytket/conanfile.py | 2 +- pytket/docs/changelog.rst | 1 + pytket/pytket/_tket/passes.pyi | 2 +- tket/conanfile.py | 2 +- .../Transformations/CliffordResynthesis.cpp | 53 +++++++++++++++---- .../src/Passes/test_CliffordResynthesis.cpp | 26 ++++++++- 7 files changed, 74 insertions(+), 16 deletions(-) diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index a5e4e87864..3f0a13f111 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -846,8 +846,8 @@ PYBIND11_MODULE(passes, m) { m.def( "CliffordResynthesis", &gen_clifford_resynthesis_pass, - "An optimisation pass that resynhesises all Clifford subcircuits and " - "then applies some rewrite rules to simplify them further." + "An optimisation pass that resynthesises Clifford subcircuits, trying " + "to reduce the 2-qubit gate count as much as possible." "\n\n:param transform: optional user-provided resynthesis method to " "apply to all Clifford subcircuits (a function taking a Clifford " "circuit as an argument and returning an equivalent circuit); if not " diff --git a/pytket/conanfile.py b/pytket/conanfile.py index ff48b51782..b6b25a946b 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -32,7 +32,7 @@ def package(self): cmake.install() def requirements(self): - self.requires("tket/1.3.12@tket/stable") + self.requires("tket/1.3.13@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 4571e30eda..0a7a39728d 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -9,6 +9,7 @@ Unreleased * Add `PauliSynthStrat.Greedy` strategy to `TermSequenceBox`. * Update version requirements on dependencies, removing all upper bounds (in particular allowing compatibility with numpy 2.0). +* Fix bug in `CliffordResynthesis()` pass. 1.29.2 (June 2024) ------------------ diff --git a/pytket/pytket/_tket/passes.pyi b/pytket/pytket/_tket/passes.pyi index f8c835b43f..8f3bde120f 100644 --- a/pytket/pytket/_tket/passes.pyi +++ b/pytket/pytket/_tket/passes.pyi @@ -250,7 +250,7 @@ def CliffordPushThroughMeasures() -> BasePass: """ def CliffordResynthesis(transform: typing.Callable[[pytket._tket.circuit.Circuit], pytket._tket.circuit.Circuit] | None = None, allow_swaps: bool = True) -> BasePass: """ - An optimisation pass that resynhesises all Clifford subcircuits and then applies some rewrite rules to simplify them further. + An optimisation pass that resynthesises Clifford subcircuits, trying to reduce the 2-qubit gate count as much as possible. :param transform: optional user-provided resynthesis method to apply to all Clifford subcircuits (a function taking a Clifford circuit as an argument and returning an equivalent circuit); if not provided, a default resynthesis method is applied :param allow_swaps: whether the rewriting may introduce wire swaps (only relevant to the default resynthesis method used when the `transform` argument is not provided) diff --git a/tket/conanfile.py b/tket/conanfile.py index 147a38330c..6feee148b0 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.3.12" + version = "1.3.13" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/src/Transformations/CliffordResynthesis.cpp b/tket/src/Transformations/CliffordResynthesis.cpp index 9edf89c930..d24d93d29f 100644 --- a/tket/src/Transformations/CliffordResynthesis.cpp +++ b/tket/src/Transformations/CliffordResynthesis.cpp @@ -14,7 +14,11 @@ #include "tket/Transformations/CliffordResynthesis.hpp" +#include +#include + #include "tket/Circuit/Circuit.hpp" +#include "tket/Circuit/DAGDefs.hpp" #include "tket/Clifford/UnitaryTableau.hpp" #include "tket/Converters/Converters.hpp" #include "tket/Transformations/BasicOptimisation.hpp" @@ -27,19 +31,20 @@ namespace tket { namespace Transforms { static Circuit resynthesised_circuit( - Circuit &circ, + const Circuit &circ, std::optional> transform = nullptr, bool allow_swaps = true) { if (transform.has_value()) { return (*transform)(circ); } else { + Circuit circ1(circ); // Ensure all recognized Clifford gates are Clifford types: - decompose_multi_qubits_CX().apply(circ); - decompose_cliffords_std().apply(circ); - remove_redundancies().apply(circ); + decompose_multi_qubits_CX().apply(circ1); + decompose_cliffords_std().apply(circ1); + remove_redundancies().apply(circ1); // Convert to tableau and back: - const UnitaryTableau tab = circuit_to_unitary_tableau(circ); + const UnitaryTableau tab = circuit_to_unitary_tableau(circ1); Circuit newcirc = unitary_tableau_to_circuit(tab); // Simplify: @@ -49,18 +54,46 @@ static Circuit resynthesised_circuit( } } +// Returns the two-qubit-gate reduction resulting from resynthesising the given +// (convex, connected) vertex set using the given transform. +static int n_2q_reduction( + const Circuit &circ, const VertexSet &verts, + std::optional> transform, + bool allow_swaps) { + Subcircuit subcircuit = circ.make_subcircuit(verts); + const Circuit subc = circ.subcircuit(subcircuit); + Circuit newsubc = resynthesised_circuit(subc, transform, allow_swaps); + return int(subc.count_n_qubit_gates(2)) - int(newsubc.count_n_qubit_gates(2)); +} + static bool resynthesise_cliffords( Circuit &circ, std::optional> transform = nullptr, bool allow_swaps = true) { bool changed = false; - for (const VertexSet &verts : - circ.get_subcircuits([](Op_ptr op) { return op->is_clifford(); })) { + while (true) { + std::vector subcircuits = + circ.get_subcircuits([](Op_ptr op) { return op->is_clifford(); }); + if (subcircuits.empty()) { + break; + } + // Pick the one that reduces the 2q gate count the most. + VertexSet verts = *std::max_element( + subcircuits.begin(), subcircuits.end(), + [&circ, &transform, &allow_swaps]( + const VertexSet &A, const VertexSet &B) { + return n_2q_reduction(circ, A, transform, allow_swaps) < + n_2q_reduction(circ, B, transform, allow_swaps); + }); Subcircuit subcircuit = circ.make_subcircuit(verts); - Circuit subc = circ.subcircuit(subcircuit); + const Circuit subc = circ.subcircuit(subcircuit); Circuit newsubc = resynthesised_circuit(subc, transform, allow_swaps); - circ.substitute(newsubc, subcircuit); - changed = true; + if (newsubc.count_n_qubit_gates(2) < subc.count_n_qubit_gates(2)) { + circ.substitute(newsubc, subcircuit); + changed = true; + } else { + break; + } } return changed; } diff --git a/tket/test/src/Passes/test_CliffordResynthesis.cpp b/tket/test/src/Passes/test_CliffordResynthesis.cpp index 06a66de7a2..c05ad5b1ba 100644 --- a/tket/test/src/Passes/test_CliffordResynthesis.cpp +++ b/tket/test/src/Passes/test_CliffordResynthesis.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include +#include #include "../testutil.hpp" #include "tket/Circuit/Circuit.hpp" @@ -23,7 +25,7 @@ namespace tket { namespace test_CliffordResynthesis { -void check_clifford_resynthesis(const Circuit& c) { +void check_clifford_resynthesis(const Circuit &c) { Circuit c0 = c; CompilationUnit cu(c0); gen_clifford_resynthesis_pass()->apply(cu); @@ -120,6 +122,28 @@ SCENARIO("SynthesiseCliffordResynthesis correctness") { c.add_op(OpType::NPhasedX, {0.5, 0.0}, {0, 1}); check_clifford_resynthesis(c); } + + GIVEN("A troublesome circuit (3)") { + // https://github.com/CQCL/tket/issues/1468 + Circuit c0(6); + c0.add_op(OpType::XXPhase, 0.5, {1, 4}); + c0.add_op(OpType::XXPhase, 1.5, {2, 3}); + c0.add_op(OpType::XXPhase, 2.5, {1, 3}); + c0.add_op(OpType::YYPhase, 0.5, {4, 5}); + c0.add_op(OpType::YYPhase, 1.5, {4, 2}); + c0.add_op(OpType::YYPhase, 2.5, {3, 1}); + c0.add_op(OpType::ZZPhase, 0.5, {0, 3}); + c0.add_op(OpType::ZZPhase, 1.5, {4, 1}); + c0.add_op(OpType::ZZPhase, 2.5, {0, 5}); + CompilationUnit cu(c0); + gen_clifford_resynthesis_pass()->apply(cu); + Circuit c1 = cu.get_circ_ref(); + std::vector cmds = c1.get_commands(); + CHECK(std::all_of(cmds.begin(), cmds.end(), [](const Command &cmd) { + return cmd.get_op_ptr()->is_clifford(); + })); + CHECK(c1.count_n_qubit_gates(2) <= c0.count_n_qubit_gates(2)); + } } } // namespace test_CliffordResynthesis