diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index a4ced2101e..09053af556 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -504,8 +504,13 @@ PYBIND11_MODULE(passes, m) { "already in this set." "\n:param tk1_replacement: A function which, given the parameters of " "an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the " - "desired basis.", - py::arg("singleqs"), py::arg("tk1_replacement")); + "desired basis." + "\n:param always_squash_symbols: If true, always squash symbolic gates " + "regardless of the blow-up in complexity. Default is false, meaning that " + "symbolic gates are only squashed if doing so reduces the overall " + "symbolic complexity.", + py::arg("singleqs"), py::arg("tk1_replacement"), + py::arg("always_squash_symbols") = false); m.def( "DelayMeasures", &DelayMeasures, "Commutes Measure operations to the end of the circuit. Throws an " diff --git a/pytket/conanfile.py b/pytket/conanfile.py index e733427cc3..87e6e801c1 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.2.33@tket/stable") + self.requires("tket/1.2.34@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") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 2aca63b563..02aef3c194 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -8,6 +8,10 @@ Minor new features: * Implement equality checking for all boxes. * Add ``Op.is_clifford`` to python binding. +* Single-qubit squashing ignores chains of symbolic gates if squashing them + would increase the overall complexity of the expressions. This behaviour can + be overridden using the ``always_squash_symbols`` parameter to + ``SquashCustom``. 1.18.0 (August 2023) -------------------- diff --git a/schemas/compiler_pass_v1.json b/schemas/compiler_pass_v1.json index 9baffe0b06..7f862f7679 100644 --- a/schemas/compiler_pass_v1.json +++ b/schemas/compiler_pass_v1.json @@ -183,6 +183,10 @@ "type": "string", "description": "A method for generating optimised single-qubit unitary circuits in a target gate set. This string should be interpreted by Python \"dill\" into a function. Used in \"RebaseCustom\" and \"SquashCustom\"." }, + "always_squash_symbols": { + "type": "boolean", + "description": "Whether to always squash symbolic gates regardless of the complexity blow-up. Used in \"SquashCustom\"." + }, "euler_p": { "type": "string", "description": "The choice of P rotation for \"EulerAngleReduction\" for P-Q-P and Q-P-Q triples.", @@ -351,9 +355,10 @@ "then": { "required": [ "basis_singleqs", - "basis_tk1_replacement" + "basis_tk1_replacement", + "always_squash_symbols" ], - "maxProperties": 3 + "maxProperties": 4 } }, { diff --git a/tket/conanfile.py b/tket/conanfile.py index 68f4771598..c7f87c1498 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.33" + version = "1.2.34" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Predicates/PassGenerators.hpp b/tket/include/tket/Predicates/PassGenerators.hpp index bc44ad84c2..3189c2ea11 100644 --- a/tket/include/tket/Predicates/PassGenerators.hpp +++ b/tket/include/tket/Predicates/PassGenerators.hpp @@ -51,7 +51,8 @@ PassPtr gen_rebase_pass_via_tk2( PassPtr gen_squash_pass( const OpTypeSet& singleqs, const std::function& - tk1_replacement); + tk1_replacement, + bool always_squash_symbols = false); PassPtr gen_euler_pass(const OpType& q, const OpType& p, bool strict = false); PassPtr gen_clifford_simp_pass(bool allow_swaps = true); diff --git a/tket/include/tket/Transformations/BasicOptimisation.hpp b/tket/include/tket/Transformations/BasicOptimisation.hpp index 7534c48d83..8767a921e3 100644 --- a/tket/include/tket/Transformations/BasicOptimisation.hpp +++ b/tket/include/tket/Transformations/BasicOptimisation.hpp @@ -110,7 +110,8 @@ Transform squash_1qb_to_pqp( Transform squash_factory( const OpTypeSet& singleqs, const std::function& - tk1_replacement); + tk1_replacement, + bool always_squash_symbols = false); // commutes single qubit gates through SWAP gates, leaving them on the // PhysicalQubit with best fidelity for the given OP Expects: any single qubit diff --git a/tket/include/tket/Transformations/SingleQubitSquash.hpp b/tket/include/tket/Transformations/SingleQubitSquash.hpp index d657ae3cc1..afe6ffc3d7 100644 --- a/tket/include/tket/Transformations/SingleQubitSquash.hpp +++ b/tket/include/tket/Transformations/SingleQubitSquash.hpp @@ -96,18 +96,20 @@ class SingleQubitSquash { * @param circ The circuit to be squashed. * @param reversed Whether squashing is made back to front or front to back * (default: false, ie front to back). + * @param always_squash_symbols Whether to squash symbolic gates regardless of + * the complexity blow-up (default: false, i.e. symbolic gates are only + * squashed if the overall complexity of the expressions is reduced). */ SingleQubitSquash( std::unique_ptr squasher, Circuit &circ, - bool reversed = false) - : squasher_(std::move(squasher)), circ_(circ), reversed_(reversed) {} + bool reversed = false, bool always_squash_symbols = false); // rule of 5 SingleQubitSquash(const SingleQubitSquash &other); - SingleQubitSquash &operator=(const SingleQubitSquash &other); + SingleQubitSquash &operator=(const SingleQubitSquash &other) = delete; ~SingleQubitSquash() = default; - SingleQubitSquash(SingleQubitSquash &&other); - SingleQubitSquash &operator=(SingleQubitSquash &&other); + SingleQubitSquash(SingleQubitSquash &&other) = delete; + SingleQubitSquash &operator=(SingleQubitSquash &&other) = delete; /** * @brief Squash entire circuit, one qubit at a time. @@ -135,6 +137,7 @@ class SingleQubitSquash { std::unique_ptr squasher_; Circuit &circ_; bool reversed_; + bool always_squash_symbols_; // substitute chain by a sub circuit, handling conditions // and backing up + restoring current edge diff --git a/tket/src/Predicates/PassGenerators.cpp b/tket/src/Predicates/PassGenerators.cpp index dc02be7763..4bbb86f050 100644 --- a/tket/src/Predicates/PassGenerators.cpp +++ b/tket/src/Predicates/PassGenerators.cpp @@ -107,8 +107,10 @@ PassPtr gen_rebase_pass_via_tk2( PassPtr gen_squash_pass( const OpTypeSet& singleqs, const std::function& - tk1_replacement) { - Transform t = Transforms::squash_factory(singleqs, tk1_replacement); + tk1_replacement, + bool always_squash_symbols) { + Transform t = Transforms::squash_factory( + singleqs, tk1_replacement, always_squash_symbols); PostConditions postcon = {{}, {}, Guarantee::Preserve}; PredicatePtrMap precons; // record pass config @@ -117,6 +119,7 @@ PassPtr gen_squash_pass( j["basis_singleqs"] = singleqs; j["basis_tk1_replacement"] = "SERIALIZATION OF FUNCTIONS IS NOT YET SUPPORTED"; + j["always_squash_symbols"] = always_squash_symbols; return std::make_shared(precons, t, postcon, j); } diff --git a/tket/src/Transformations/SingleQubitSquash.cpp b/tket/src/Transformations/SingleQubitSquash.cpp index f1dd8cc462..915e0b7acf 100644 --- a/tket/src/Transformations/SingleQubitSquash.cpp +++ b/tket/src/Transformations/SingleQubitSquash.cpp @@ -14,37 +14,30 @@ #include "tket/Transformations/SingleQubitSquash.hpp" +#include +#include + +#include "Circuit/Command.hpp" +#include "Gate/GatePtr.hpp" #include "tket/Circuit/Circuit.hpp" #include "tket/Circuit/DAGDefs.hpp" #include "tket/Gate/Gate.hpp" namespace tket { +SingleQubitSquash::SingleQubitSquash( + std::unique_ptr squasher, Circuit &circ, bool reversed, + bool always_squash_symbols) + : squasher_(std::move(squasher)), + circ_(circ), + reversed_(reversed), + always_squash_symbols_(always_squash_symbols) {} + SingleQubitSquash::SingleQubitSquash(const SingleQubitSquash &other) : squasher_(other.squasher_->clone()), circ_(other.circ_), - reversed_(other.reversed_) {} - -SingleQubitSquash &SingleQubitSquash::operator=( - const SingleQubitSquash &other) { - squasher_ = other.squasher_->clone(); - circ_ = other.circ_; - reversed_ = other.reversed_; - return *this; -} - -SingleQubitSquash::SingleQubitSquash(SingleQubitSquash &&other) - : squasher_(std::move(other.squasher_)), - circ_(other.circ_), - reversed_(other.reversed_) {} - -SingleQubitSquash &SingleQubitSquash::operator=(SingleQubitSquash &&other) { - squasher_ = std::move(other.squasher_); - circ_ = other.circ_; - reversed_ = other.reversed_; - return *this; -} - + reversed_(other.reversed_), + always_squash_symbols_(other.always_squash_symbols_) {} bool SingleQubitSquash::squash() { bool success = false; @@ -199,8 +192,25 @@ void SingleQubitSquash::insert_left_over_gate( bool SingleQubitSquash::sub_is_better( const Circuit &sub, const std::vector chain) const { const unsigned n_gates = sub.n_gates(); - return n_gates < chain.size() || - (n_gates == chain.size() && !is_equal(sub, chain, reversed_)); + if (n_gates > chain.size()) { + return false; + } + if (!sub.is_symbolic() || always_squash_symbols_) { + return n_gates < chain.size() || !is_equal(sub, chain, reversed_); + } + // For symbolic circuits, we don't want to squash gates if it blows up the + // complexity of the expressions. As a crude but adequate measure, we compare + // the total size of the string representations. + return std::accumulate( + sub.begin(), sub.end(), std::size_t{0}, + [](std::size_t a, const Command &cmd) { + return a + cmd.get_op_ptr()->get_name().size(); + }) < + std::accumulate( + chain.begin(), chain.end(), std::size_t{0}, + [](std::size_t a, const Gate_ptr &gpr) { + return a + gpr->get_name().size(); + }); } // returns a description of the condition of current vertex diff --git a/tket/src/Transformations/StandardSquash.cpp b/tket/src/Transformations/StandardSquash.cpp index e6be583e69..4c57018e94 100644 --- a/tket/src/Transformations/StandardSquash.cpp +++ b/tket/src/Transformations/StandardSquash.cpp @@ -83,17 +83,22 @@ std::unique_ptr StandardSquasher::clone() const { static bool standard_squash( Circuit &circ, const OpTypeSet &singleqs, const std::function - &tk1_replacement) { + &tk1_replacement, + bool always_squash_symbols) { auto squasher = std::make_unique(singleqs, tk1_replacement); - return SingleQubitSquash(std::move(squasher), circ, false).squash(); + return SingleQubitSquash( + std::move(squasher), circ, false, always_squash_symbols) + .squash(); } Transform squash_factory( const OpTypeSet &singleqs, const std::function - &tk1_replacement) { + &tk1_replacement, + bool always_squash_symbols) { return Transform([=](Circuit &circ) { - return standard_squash(circ, singleqs, tk1_replacement); + return standard_squash( + circ, singleqs, tk1_replacement, always_squash_symbols); }); } diff --git a/tket/test/src/Circuit/test_Symbolic.cpp b/tket/test/src/Circuit/test_Symbolic.cpp index d106a6b0a2..f6e3263190 100644 --- a/tket/test/src/Circuit/test_Symbolic.cpp +++ b/tket/test/src/Circuit/test_Symbolic.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "symengine/eval_double.h" @@ -50,6 +51,8 @@ SCENARIO("Symbolic squashing, correctness") { Expr alpha(asym); Sym bsym = SymEngine::symbol("b"); Expr beta(bsym); + Sym csym = SymEngine::symbol("c"); + Expr gamma(csym); GIVEN("squash_1qb_to_pqp") { Circuit circ(1); @@ -147,8 +150,8 @@ SCENARIO("Symbolic squashing, correctness") { symbol_map_t smap = {{asym, 0}, {bsym, 0}}; circ.symbol_substitution(smap); std::vector cmds = circ.get_commands(); - CHECK(cmds.size() == 1); - Op_ptr op = cmds[0].get_op_ptr(); + CHECK(cmds.size() == 4); + Op_ptr op = cmds[1].get_op_ptr(); CHECK(op->get_type() == OpType::TK1); std::vector params = op->get_params(); CHECK_NOTHROW(SymEngine::eval_double(params[0])); @@ -169,8 +172,8 @@ SCENARIO("Symbolic squashing, correctness") { symbol_map_t smap = {{asym, 0}}; circ.symbol_substitution(smap); std::vector cmds = circ.get_commands(); - CHECK(cmds.size() == 1); - Op_ptr op = cmds[0].get_op_ptr(); + CHECK(cmds.size() == 4); + Op_ptr op = cmds[1].get_op_ptr(); CHECK(op->get_type() == OpType::TK1); std::vector params = op->get_params(); CHECK_NOTHROW(SymEngine::eval_double(params[0])); @@ -179,6 +182,29 @@ SCENARIO("Symbolic squashing, correctness") { CHECK(approx_0(params[1])); CHECK(approx_0(params[0] + params[2])); } + + GIVEN("Squashing where expressions are allowed to expand") { + auto squash_circuit = [](Circuit &c, bool always_squash_symbols) { + auto squasher = + std::make_unique(OpType::Ry, OpType::Rz); + return SingleQubitSquash( + std::move(squasher), c, false, always_squash_symbols) + .squash(); + }; + Circuit circ0(1); + circ0.add_op(OpType::Rz, 0.5, {0}); + circ0.add_op(OpType::Ry, 0.5, {0}); + circ0.add_op(OpType::Rz, {alpha}, {0}); + circ0.add_op(OpType::Ry, {beta}, {0}); + circ0.add_op(OpType::Rz, {gamma}, {0}); + Circuit circ1 = circ0; + CHECK_FALSE(squash_circuit(circ0, false)); + CHECK(squash_circuit(circ1, true)); + symbol_map_t smap = {{bsym, 0.3}, {csym, 0.4}}; + circ0.symbol_substitution(smap); + circ1.symbol_substitution(smap); + check_equiv(circ0, circ1); + } } } // namespace test_Symbolic diff --git a/tket/test/src/test_CompilerPass.cpp b/tket/test/src/test_CompilerPass.cpp index bb798770e6..23a7a45837 100644 --- a/tket/test/src/test_CompilerPass.cpp +++ b/tket/test/src/test_CompilerPass.cpp @@ -769,6 +769,16 @@ SCENARIO("PeepholeOptimise2Q and FullPeepholeOptimise") { CompilationUnit cu1(circ1); REQUIRE(FullPeepholeOptimise()->apply(cu1)); } + GIVEN("Symbolic circuit, FullPeepholeOptimise TK2") { + // https://github.com/CQCL/tket/issues/963 + Sym a = SymEngine::symbol("a"); + Circuit circ(3); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::Rz, Expr(a), {0}); + circ.add_op(OpType::CX, {0, 2}); + CompilationUnit cu(circ); + REQUIRE(FullPeepholeOptimise(true, OpType::TK2)->apply(cu)); + } GIVEN("YYPhase") { // TKET-1302 Circuit circ(2);