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

Add check for graph validity before commuting conditional gates in squashing passes #1716

Merged
merged 4 commits into from
Dec 10, 2024
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
2 changes: 1 addition & 1 deletion pytket/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def requirements(self):
self.requires("pybind11_json/0.2.14")
self.requires("symengine/0.13.0")
self.requires("tkassert/0.3.4@tket/stable")
self.requires("tket/1.3.54@tket/stable")
self.requires("tket/1.3.55@tket/stable")
self.requires("tklog/0.3.3@tket/stable")
self.requires("tkrng/0.3.3@tket/stable")
self.requires("tktokenswap/0.3.9@tket/stable")
Expand Down
2 changes: 2 additions & 0 deletions pytket/docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Fixes:

* Fix circuit iteration giving invalid slices in some cases.
* Use built-in int type for get_counts() instead of numpy int types.
* Add check for validity of resultant graph before pushing conditional gates
through other vertices in squashing passes.

Performance:

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.3.54"
version = "1.3.55"
package_type = "library"
license = "Apache 2"
homepage = "https://github.com/CQCL/tket"
Expand Down
8 changes: 8 additions & 0 deletions tket/include/tket/Transformations/SingleQubitSquash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ class SingleQubitSquash {
const Circuit &sub, const VertexVec &single_chain, Edge &e,
const Condition &condition);

// whether there is a path in the DAG to v from any vertex in vs (if
// reversed_ is true then paths are considered in the reverse direction)
bool path_exists(const Vertex &v, const VertexSet &vs) const;

// whether we are allowed to commute a given conditional single-qubit gate
// through the target of e without invalidating the DAG structure
bool commute_ok(const Edge &e, const Condition &condition) const;

// insert a gate at the given edge, respecting condition
void insert_left_over_gate(
Op_ptr left_over, const Edge &e, const Condition &condition);
Expand Down
55 changes: 53 additions & 2 deletions tket/src/Transformations/SingleQubitSquash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@

#include "tket/Transformations/SingleQubitSquash.hpp"

#include <algorithm>
#include <cstddef>
#include <numeric>
#include <utility>

#include "Circuit/Command.hpp"
#include "Gate/GatePtr.hpp"
Expand Down Expand Up @@ -107,8 +109,13 @@ bool SingleQubitSquash::squash_between(const Edge &in, const Edge &out) {
sub = pair.first;
Gate_ptr left_over_gate = pair.second;
if (left_over_gate != nullptr) {
// => commute leftover through before squashing
insert_left_over_gate(left_over_gate, next_edge(v, e), condition);
if (commute_ok(e, condition)) {
// Commute leftover through before squashing.
insert_left_over_gate(left_over_gate, next_edge(v, e), condition);
} else {
// Don't commute, just add the left-over gate to the replacement.
sub.add_op<unsigned>(left_over_gate, {0});
}
left_over_gate = nullptr;
}
if (reversed_) {
Expand Down Expand Up @@ -165,6 +172,50 @@ void SingleQubitSquash::substitute(
e = prev_edge(backup);
}

bool SingleQubitSquash::path_exists(
const Vertex &v, const VertexSet &vs) const {
VertexSet frontier{v};
while (!frontier.empty()) {
if (std::any_of(vs.begin(), vs.end(), [&frontier](const Vertex &v1) {
return frontier.contains(v1);
})) {
return true;
}
VertexSet new_frontier;
for (const Vertex &v1 : frontier) {
if (reversed_) {
const VertexVec &succs = circ_.get_successors(v1);
new_frontier.insert(succs.begin(), succs.end());
} else {
const VertexVec &preds = circ_.get_predecessors(v1);
new_frontier.insert(preds.begin(), preds.end());
}
}
frontier = std::move(new_frontier);
}
return false;
}

bool SingleQubitSquash::commute_ok(
const Edge &e, const Condition &condition) const {
if (!condition) return true;
std::list<VertPort> vps = condition->first;
VertexSet vs;
for (const VertPort &vp : vps) {
vs.insert(vp.first);
}
if (reversed_) {
// Return true iff there is no path in the DAG from source(e) to any vertex
// in vs. (Such a path would introduce a cycle after the commutation.)
return !path_exists(circ_.source(e), vs);
} else {
// Return true iff there is no path in the DAG from any vertex in vs to
// target(e). (Such a path would imply the condition is no longer live when
// queried.)
return !path_exists(circ_.target(e), vs);
}
}

void SingleQubitSquash::insert_left_over_gate(
Op_ptr left_over, const Edge &e, const Condition &condition) {
if (reversed_) {
Expand Down
40 changes: 40 additions & 0 deletions tket/test/src/test_Synthesis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2288,6 +2288,46 @@ SCENARIO("Test squash Rz PhasedX") {
CHECK(allowed.contains(optype));
}
}

GIVEN("A circuit with classical control (1)") {
// https://github.com/CQCL/tket/issues/1324
Circuit circ(3, 1);
circ.add_op<unsigned>(OpType::CY, {0, 1});
circ.add_conditional_gate<unsigned>(OpType::Rz, {1.0}, {1}, {0}, 1);
circ.add_measure(Qubit(2), Bit(0));
circ.add_op<unsigned>(OpType::CX, {2, 0});
circ.add_op<unsigned>(OpType::CZ, {0, 1});
std::unique_ptr<AbstractSquasher> squasher =
std::make_unique<Transforms::RzPhasedXSquasher>();
SingleQubitSquash sqs(std::move(squasher), circ);
VertexVec inputs = circ.q_inputs();
VertexVec outputs = circ.q_outputs();
Edge in = circ.get_nth_out_edge(inputs[1], 0);
Edge out = circ.get_nth_in_edge(outputs[1], 0);
// The Rz should not be commuted through the CZ, since if it were the source
// of its conditional wire would not be "live" at the time of application.
REQUIRE_FALSE(sqs.squash_between(in, out));
}

GIVEN("A circuit with classical control (2)") {
// https://github.com/CQCL/tket/issues/1324
Circuit circ(3, 1);
circ.add_op<unsigned>(OpType::CZ, {0, 1});
circ.add_op<unsigned>(OpType::CX, {2, 0});
circ.add_measure(Qubit(2), Bit(0));
circ.add_conditional_gate<unsigned>(OpType::Rz, {1.0}, {1}, {0}, 1);
circ.add_op<unsigned>(OpType::CY, {0, 1});
std::unique_ptr<AbstractSquasher> squasher =
std::make_unique<Transforms::RzPhasedXSquasher>();
SingleQubitSquash sqs(std::move(squasher), circ, true);
VertexVec inputs = circ.q_inputs();
VertexVec outputs = circ.q_outputs();
Edge in = circ.get_nth_out_edge(inputs[1], 0);
Edge out = circ.get_nth_in_edge(outputs[1], 0);
// The Rz should not be commuted through the CZ, since if it were a cycle
// (CZ->CX->Measure->Rz->CZ) would be introduced.
REQUIRE_FALSE(sqs.squash_between(out, in));
}
}

// https://github.com/CQCL/tket/issues/535
Expand Down
Loading