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

Avoid blow-up of complexity when squashing symbolic circuits #969

Merged
merged 17 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 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
Loading