Skip to content

Commit

Permalink
Avoid blow-up of complexity when squashing symbolic circuits (#969)
Browse files Browse the repository at this point in the history
  • Loading branch information
cqc-alec authored Aug 15, 2023
1 parent 5523f6f commit cd78e00
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 47 deletions.
9 changes: 7 additions & 2 deletions pytket/binders/passes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
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.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")
Expand Down
4 changes: 4 additions & 0 deletions pytket/docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
--------------------
Expand Down
9 changes: 7 additions & 2 deletions schemas/compiler_pass_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down Expand Up @@ -351,9 +355,10 @@
"then": {
"required": [
"basis_singleqs",
"basis_tk1_replacement"
"basis_tk1_replacement",
"always_squash_symbols"
],
"maxProperties": 3
"maxProperties": 4
}
},
{
Expand Down
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.33"
version = "1.2.34"
package_type = "library"
license = "Apache 2"
homepage = "https://github.com/CQCL/tket"
Expand Down
3 changes: 2 additions & 1 deletion tket/include/tket/Predicates/PassGenerators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ PassPtr gen_rebase_pass_via_tk2(
PassPtr gen_squash_pass(
const OpTypeSet& singleqs,
const std::function<Circuit(const Expr&, const Expr&, const Expr&)>&
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);

Expand Down
3 changes: 2 additions & 1 deletion tket/include/tket/Transformations/BasicOptimisation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ Transform squash_1qb_to_pqp(
Transform squash_factory(
const OpTypeSet& singleqs,
const std::function<Circuit(const Expr&, const Expr&, const Expr&)>&
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
Expand Down
13 changes: 8 additions & 5 deletions tket/include/tket/Transformations/SingleQubitSquash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<AbstractSquasher> 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.
Expand Down Expand Up @@ -135,6 +137,7 @@ class SingleQubitSquash {
std::unique_ptr<AbstractSquasher> squasher_;
Circuit &circ_;
bool reversed_;
bool always_squash_symbols_;

// substitute chain by a sub circuit, handling conditions
// and backing up + restoring current edge
Expand Down
7 changes: 5 additions & 2 deletions tket/src/Predicates/PassGenerators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ PassPtr gen_rebase_pass_via_tk2(
PassPtr gen_squash_pass(
const OpTypeSet& singleqs,
const std::function<Circuit(const Expr&, const Expr&, const Expr&)>&
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
Expand All @@ -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<StandardPass>(precons, t, postcon, j);
}

Expand Down
58 changes: 34 additions & 24 deletions tket/src/Transformations/SingleQubitSquash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,30 @@

#include "tket/Transformations/SingleQubitSquash.hpp"

#include <cstddef>
#include <numeric>

#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<AbstractSquasher> 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;

Expand Down Expand Up @@ -199,8 +192,25 @@ void SingleQubitSquash::insert_left_over_gate(
bool SingleQubitSquash::sub_is_better(
const Circuit &sub, const std::vector<Gate_ptr> 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
Expand Down
13 changes: 9 additions & 4 deletions tket/src/Transformations/StandardSquash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,22 @@ std::unique_ptr<AbstractSquasher> StandardSquasher::clone() const {
static bool standard_squash(
Circuit &circ, const OpTypeSet &singleqs,
const std::function<Circuit(const Expr &, const Expr &, const Expr &)>
&tk1_replacement) {
&tk1_replacement,
bool always_squash_symbols) {
auto squasher = std::make_unique<StandardSquasher>(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<Circuit(const Expr &, const Expr &, const Expr &)>
&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);
});
}

Expand Down
34 changes: 30 additions & 4 deletions tket/test/src/Circuit/test_Symbolic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <tket/Transformations/BasicOptimisation.hpp>
#include <tket/Transformations/CliffordOptimisation.hpp>
#include <tket/Transformations/OptimisationPass.hpp>
#include <tket/Transformations/PQPSquash.hpp>
#include <vector>

#include "symengine/eval_double.h"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -147,8 +150,8 @@ SCENARIO("Symbolic squashing, correctness") {
symbol_map_t smap = {{asym, 0}, {bsym, 0}};
circ.symbol_substitution(smap);
std::vector<Command> 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<Expr> params = op->get_params();
CHECK_NOTHROW(SymEngine::eval_double(params[0]));
Expand All @@ -169,8 +172,8 @@ SCENARIO("Symbolic squashing, correctness") {
symbol_map_t smap = {{asym, 0}};
circ.symbol_substitution(smap);
std::vector<Command> 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<Expr> params = op->get_params();
CHECK_NOTHROW(SymEngine::eval_double(params[0]));
Expand All @@ -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<Transforms::PQPSquasher>(OpType::Ry, OpType::Rz);
return SingleQubitSquash(
std::move(squasher), c, false, always_squash_symbols)
.squash();
};
Circuit circ0(1);
circ0.add_op<unsigned>(OpType::Rz, 0.5, {0});
circ0.add_op<unsigned>(OpType::Ry, 0.5, {0});
circ0.add_op<unsigned>(OpType::Rz, {alpha}, {0});
circ0.add_op<unsigned>(OpType::Ry, {beta}, {0});
circ0.add_op<unsigned>(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
Expand Down
10 changes: 10 additions & 0 deletions tket/test/src/test_CompilerPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned>(OpType::CX, {0, 1});
circ.add_op<unsigned>(OpType::Rz, Expr(a), {0});
circ.add_op<unsigned>(OpType::CX, {0, 2});
CompilationUnit cu(circ);
REQUIRE(FullPeepholeOptimise(true, OpType::TK2)->apply(cu));
}
GIVEN("YYPhase") {
// TKET-1302
Circuit circ(2);
Expand Down

0 comments on commit cd78e00

Please sign in to comment.