From 2b871b8f0cf98e9875a4e9d77bded30fdfdfad55 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:34:45 +0000 Subject: [PATCH] Check liveness of bits when computing slice iterator (#1707) --- pytket/conanfile.py | 2 +- pytket/docs/changelog.rst | 7 + pytket/tests/classical_test.py | 14 + tket/CMakeLists.txt | 2 + tket/conanfile.py | 2 +- tket/include/tket/Circuit/Circuit.hpp | 71 +---- tket/include/tket/Circuit/Slices.hpp | 91 ++++++ tket/src/Characterisation/Cycles.cpp | 4 +- tket/src/Circuit/Slices.cpp | 339 +++++++++++++++++++++ tket/src/Circuit/macro_circ_info.cpp | 378 +----------------------- tket/test/CMakeLists.txt | 1 + tket/test/src/Circuit/test_Circ.cpp | 3 +- tket/test/src/Circuit/test_Slices.cpp | 35 +++ tket/test/src/Ops/test_ClassicalOps.cpp | 7 +- 14 files changed, 507 insertions(+), 449 deletions(-) create mode 100644 tket/include/tket/Circuit/Slices.hpp create mode 100644 tket/src/Circuit/Slices.cpp create mode 100644 tket/test/src/Circuit/test_Slices.cpp diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 0bc83653b6..c50c6c9909 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -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.52@tket/stable") + self.requires("tket/1.3.53@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") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index bda811ef41..ab87fb34a7 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,13 @@ Changelog ========= +1.37.0 (Unreleased) +------------------- + +Fixes: + +* Fix circuit iteration giving invalid slices in some cases. + 1.36.0 (November 2024) ---------------------- diff --git a/pytket/tests/classical_test.py b/pytket/tests/classical_test.py index d45c1c052c..61fb0fdda1 100644 --- a/pytket/tests/classical_test.py +++ b/pytket/tests/classical_test.py @@ -1538,5 +1538,19 @@ def test_decompose_clexpbox_overwrite() -> None: assert args1[1] == bits[0] +def test_depth_classical_only() -> None: + # https://github.com/CQCL/tket/issues/1673 + set_bits = SetBitsOp([True, True]) + multi_bit = MultiBitOp(set_bits, 2) + eq_pred_values = [True, False, False, True] + and_values = [bool(i) for i in [0, 0, 0, 1]] + circ = Circuit(4, 4, name="test") + circ.add_gate(multi_bit, [0, 1, 2, 3]) + circ.add_c_predicate(eq_pred_values, [0, 1], 2, "EQ") + circ.add_c_modifier(and_values, [1], 2) + circ.measure_all() + assert circ.depth() == 4 + + if __name__ == "__main__": test_wasm() diff --git a/tket/CMakeLists.txt b/tket/CMakeLists.txt index 41808ece11..58fa4797f8 100644 --- a/tket/CMakeLists.txt +++ b/tket/CMakeLists.txt @@ -164,6 +164,7 @@ target_sources(tket src/Circuit/Simulation/GateNode.cpp src/Circuit/Simulation/GateNodesBuffer.cpp src/Circuit/Simulation/PauliExpBoxUnitaryCalculator.cpp + src/Circuit/Slices.cpp src/Circuit/StatePreparation.cpp src/Circuit/SubcircuitFinder.cpp src/Circuit/ThreeQubitConversion.cpp @@ -319,6 +320,7 @@ target_sources(tket include/tket/Circuit/ResourceData.hpp include/tket/Circuit/Simulation/CircuitSimulator.hpp include/tket/Circuit/Simulation/PauliExpBoxUnitaryCalculator.hpp + include/tket/Circuit/Slices.hpp include/tket/Circuit/StatePreparation.hpp include/tket/Circuit/ThreeQubitConversion.hpp include/tket/Circuit/ToffoliBox.hpp diff --git a/tket/conanfile.py b/tket/conanfile.py index 60aceeff08..2602d319f1 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.3.52" + version = "1.3.53" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Circuit/Circuit.hpp b/tket/include/tket/Circuit/Circuit.hpp index 362d75ecd9..441d4ca095 100644 --- a/tket/include/tket/Circuit/Circuit.hpp +++ b/tket/include/tket/Circuit/Circuit.hpp @@ -46,6 +46,7 @@ #include "Conditional.hpp" #include "DAGDefs.hpp" #include "ResourceData.hpp" +#include "Slices.hpp" #include "tket/Gate/OpPtrFunctions.hpp" #include "tket/Utils/Json.hpp" #include "tket/Utils/SequencedContainers.hpp" @@ -55,7 +56,6 @@ namespace tket { typedef std::vector BundleVec; -typedef VertexVec Slice; typedef std::vector SliceVec; typedef std::vector QPathDetailed; @@ -108,11 +108,6 @@ typedef boost::multi_index::multi_index_container< BoundaryElement, std::string, &BoundaryElement::reg_name>>>> boundary_t; -typedef sequenced_map_t unit_frontier_t; -// typedef sequenced_map_t q_frontier_t; -typedef sequenced_map_t b_frontier_t; -// typedef sequenced_map_t c_frontier_t; - typedef std::unordered_map permutation_t; /** @@ -173,17 +168,6 @@ struct TraversalPoint { Edge edge; }; -struct CutFrontier { - std::shared_ptr slice; - std::shared_ptr u_frontier; - std::shared_ptr b_frontier; - void init() { - slice = std::make_shared(); - u_frontier = std::make_shared(); - b_frontier = std::make_shared(); - } -}; - // list of error types to throw out class CircuitInequality : public std::logic_error { public: @@ -253,53 +237,6 @@ class Circuit { E_iterator &eend) const; public: - /*SliceIterator class is used for lazy evaluation of slices */ - class SliceIterator { - public: // these are currently public to allow skip_func slicing. - CutFrontier cut_; - std::shared_ptr prev_b_frontier_; - const Circuit *circ_; - - class Sliceholder { - private: - Slice current_slice_; - - public: - explicit Sliceholder(Slice slice) : current_slice_(slice) {} - Slice operator*() const { return current_slice_; } - }; - - // take in an unsigned 'n' and a circuit and give the 'n'th slice - // note: n=0 gives an empty SliceIterator - // n=1 gives the first slice - - SliceIterator( - const Circuit &circ, const std::function &skip_func); - explicit SliceIterator(const Circuit &circ); - SliceIterator() : cut_(), circ_() { cut_.init(); } - Slice operator*() const { return *cut_.slice; } - bool operator==(const SliceIterator &other) const { - return *cut_.slice == *other.cut_.slice; - } - bool operator!=(const SliceIterator &other) const { - return !(*this == other); - } - std::shared_ptr get_u_frontier() const { - return cut_.u_frontier; - } - std::shared_ptr get_b_frontier() const { - return cut_.b_frontier; - } - std::shared_ptr get_prev_b_frontier() const { - return prev_b_frontier_; - } - // A postfix increment operator overload - Sliceholder operator++(int); - // A prefix increment operator overload - SliceIterator &operator++(); - bool finished() const; - }; - SliceIterator slice_begin() const; static SliceIterator slice_end(); static const SliceIterator nullsit; @@ -1164,14 +1101,10 @@ class Circuit { // given current slice and a set of slices, returns the next slice // O(q log^2(q!) alpha log(alpha!)) - CutFrontier next_cut( - std::shared_ptr u_frontier, - std::shared_ptr b_frontier) const; - CutFrontier next_cut( std::shared_ptr u_frontier, std::shared_ptr b_frontier, - const std::function &skip_func) const; + const std::function &skip_func = 0) const; // given current slice of quantum frontier, returns the next slice. // ignore classical and boolean edges diff --git a/tket/include/tket/Circuit/Slices.hpp b/tket/include/tket/Circuit/Slices.hpp new file mode 100644 index 0000000000..5049293c80 --- /dev/null +++ b/tket/include/tket/Circuit/Slices.hpp @@ -0,0 +1,91 @@ +// Copyright 2019-2024 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include "tket/Circuit/DAGDefs.hpp" +#include "tket/Utils/SequencedContainers.hpp" + +namespace tket { + +typedef VertexVec Slice; + +typedef sequenced_map_t unit_frontier_t; +// typedef sequenced_map_t q_frontier_t; +typedef sequenced_map_t b_frontier_t; +// typedef sequenced_map_t c_frontier_t; + +struct CutFrontier { + std::shared_ptr slice; + std::shared_ptr u_frontier; + std::shared_ptr b_frontier; + void init() { + slice = std::make_shared(); + u_frontier = std::make_shared(); + b_frontier = std::make_shared(); + } +}; + +class Circuit; + +/*SliceIterator class is used for lazy evaluation of slices */ +class SliceIterator { + public: // these are currently public to allow skip_func slicing. + CutFrontier cut_; + std::shared_ptr prev_b_frontier_; + const Circuit *circ_; + + class Sliceholder { + private: + Slice current_slice_; + + public: + explicit Sliceholder(Slice slice) : current_slice_(slice) {} + Slice operator*() const { return current_slice_; } + }; + + // take in an unsigned 'n' and a circuit and give the 'n'th slice + // note: n=0 gives an empty SliceIterator + // n=1 gives the first slice + + SliceIterator( + const Circuit &circ, const std::function &skip_func); + explicit SliceIterator(const Circuit &circ); + SliceIterator() : cut_(), circ_() { cut_.init(); } + Slice operator*() const { return *cut_.slice; } + bool operator==(const SliceIterator &other) const { + return *cut_.slice == *other.cut_.slice; + } + bool operator!=(const SliceIterator &other) const { + return !(*this == other); + } + std::shared_ptr get_u_frontier() const { + return cut_.u_frontier; + } + std::shared_ptr get_b_frontier() const { + return cut_.b_frontier; + } + std::shared_ptr get_prev_b_frontier() const { + return prev_b_frontier_; + } + // A postfix increment operator overload + Sliceholder operator++(int); + // A prefix increment operator overload + SliceIterator &operator++(); + bool finished() const; +}; + +} // namespace tket diff --git a/tket/src/Characterisation/Cycles.cpp b/tket/src/Characterisation/Cycles.cpp index e82cc8c615..91b601fe18 100644 --- a/tket/src/Characterisation/Cycles.cpp +++ b/tket/src/Characterisation/Cycles.cpp @@ -16,6 +16,8 @@ #include +#include "tket/Circuit/Slices.hpp" + namespace tket { class CycleError : public std::logic_error { @@ -314,7 +316,7 @@ std::vector CycleFinder::get_cycles() { return (cycle_types_.find(op->get_type()) == cycle_types_.end()); }; - Circuit::SliceIterator slice_iter(circ, skip_func); + SliceIterator slice_iter(circ, skip_func); this->cycle_history.key = 0; // initialization diff --git a/tket/src/Circuit/Slices.cpp b/tket/src/Circuit/Slices.cpp new file mode 100644 index 0000000000..cebbdb574d --- /dev/null +++ b/tket/src/Circuit/Slices.cpp @@ -0,0 +1,339 @@ +// Copyright 2019-2024 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tket/Circuit/Slices.hpp" + +#include "tket/Circuit/Circuit.hpp" + +namespace tket { + +/*SliceIterator related methods*/ +SliceIterator::SliceIterator(const Circuit& circ) : cut_(), circ_(&circ) { + cut_.init(); + // add qubits to u_frontier + for (const Qubit& q : circ.all_qubits()) { + Vertex in = circ.get_in(q); + cut_.slice->push_back(in); + cut_.u_frontier->insert({q, circ.get_nth_out_edge(in, 0)}); + } + + // add bits to u_frontier and b_frontier + for (const Bit& b : circ.all_bits()) { + Vertex in = circ.get_in(b); + cut_.slice->push_back(in); + cut_.b_frontier->insert({b, circ.get_nth_b_out_bundle(in, 0)}); + cut_.u_frontier->insert({b, circ.get_nth_out_edge(in, 0)}); + } + + // add WasmState to u_frontier + for (unsigned i = 0; i < circ._number_of_wasm_wires; ++i) { + Vertex in = circ.get_in(circ.wasmwire[i]); + cut_.slice->push_back(in); + cut_.u_frontier->insert({circ.wasmwire[i], circ.get_nth_out_edge(in, 0)}); + } + + prev_b_frontier_ = cut_.b_frontier; + cut_ = circ.next_cut(cut_.u_frontier, cut_.b_frontier); + + // Add all vertices that have no Quantum or Classical edges (e.g. Phase) and + // no Boolean inputs: + BGL_FORALL_VERTICES(v, circ.dag, DAG) { + if (circ.n_in_edges(v) == 0 && + circ.n_out_edges_of_type(v, EdgeType::Quantum) == 0 && + circ.n_out_edges_of_type(v, EdgeType::Classical) == 0 && + circ.n_out_edges_of_type(v, EdgeType::WASM) == 0) { + cut_.slice->push_back(v); + } + } +} + +SliceIterator::SliceIterator( + const Circuit& circ, const std::function& skip_func) + : cut_(), circ_(&circ) { + cut_.init(); + // add qubits to u_frontier + for (const Qubit& q : circ.all_qubits()) { + Vertex in = circ.get_in(q); + cut_.u_frontier->insert({q, circ.get_nth_out_edge(in, 0)}); + } + + // add bits to u_frontier and b_frontier + for (const Bit& b : circ.all_bits()) { + Vertex in = circ.get_in(b); + cut_.b_frontier->insert({b, circ.get_nth_b_out_bundle(in, 0)}); + cut_.u_frontier->insert({b, circ.get_nth_out_edge(in, 0)}); + } + + // add WasmState to u_frontier + for (unsigned i = 0; i < circ._number_of_wasm_wires; ++i) { + Vertex in = circ.get_in(circ.wasmwire[i]); + cut_.slice->push_back(in); + cut_.u_frontier->insert({circ.wasmwire[i], circ.get_nth_out_edge(in, 0)}); + } + + prev_b_frontier_ = cut_.b_frontier; + cut_ = circ.next_cut(cut_.u_frontier, cut_.b_frontier, skip_func); +} + +SliceIterator Circuit::slice_begin() const { return SliceIterator(*this); } + +SliceIterator Circuit::slice_end() { return nullsit; } +const SliceIterator Circuit::nullsit = SliceIterator(); + +SliceIterator::Sliceholder SliceIterator::operator++(int) { + Sliceholder ret(*cut_.slice); + ++*this; + return ret; +} + +SliceIterator& SliceIterator::operator++() { + if (this->finished()) { + *this = circ_->slice_end(); + return *this; + } + prev_b_frontier_ = cut_.b_frontier; + cut_ = circ_->next_cut(cut_.u_frontier, cut_.b_frontier); + return *this; +} + +bool SliceIterator::finished() const { + for (const std::pair& pair : + this->cut_.u_frontier->get()) { + if (!circ_->detect_final_Op(circ_->target(pair.second))) return false; + } + for (const std::pair& pair : + this->cut_.b_frontier->get()) { + if (!pair.second.empty()) return false; + } + return true; +} + +static std::shared_ptr get_next_u_frontier( + const Circuit& circ, std::shared_ptr u_frontier, + const VertexSet& next_slice_lookup) { + std::shared_ptr next_frontier = + std::make_shared(); + for (const std::pair& pair : u_frontier->get()) { + Vertex next_v = circ.target(pair.second); + if (next_slice_lookup.find(next_v) == next_slice_lookup.end()) { + next_frontier->insert(pair); + } else { + next_frontier->insert( + {pair.first, circ.get_next_edge(next_v, pair.second)}); + } + } + return next_frontier; +} + +static std::shared_ptr get_next_b_frontier( + const Circuit& circ, std::shared_ptr b_frontier, + std::shared_ptr u_frontier, + const VertexSet& next_slice_lookup) { + std::shared_ptr next_b_frontier = + std::make_shared(); + // Copy any remaining edges + for (const std::pair& pair : b_frontier->get()) { + EdgeVec remaining; + for (const Edge& e : pair.second) { + Vertex targ = circ.target(e); + if (next_slice_lookup.find(targ) == next_slice_lookup.end()) { + remaining.push_back(e); + } + } + if (!remaining.empty()) { + next_b_frontier->insert({pair.first, remaining}); + } + } + // Add any new bits introduced in this slice + for (const std::pair& pair : u_frontier->get()) { + switch (circ.get_edgetype(pair.second)) { + case EdgeType::Quantum: + case EdgeType::WASM: { + break; + } + case EdgeType::Classical: { + Vertex next_v = circ.target(pair.second); + if (next_slice_lookup.find(next_v) == next_slice_lookup.end()) continue; + if (next_b_frontier->get().find(Bit(pair.first)) != + next_b_frontier->end()) { + TKET_ASSERT(!"RAW hazard created in slicing"); + } + port_t p = circ.get_target_port(pair.second); + EdgeVec reads = circ.get_nth_b_out_bundle(next_v, p); + if (!reads.empty()) next_b_frontier->insert({Bit(pair.first), reads}); + break; + } + case EdgeType::Boolean: { + throw CircuitInvalidity("Boolen edge not allowed in b_frontier_t"); + } + default: { + TKET_ASSERT(!"get_next_b_frontier found invalid edge type in sig"); + } + } + } + return next_b_frontier; +} + +CutFrontier Circuit::next_cut( + std::shared_ptr u_frontier, + std::shared_ptr b_frontier, + const std::function& skip_func) const { + auto next_slice = std::make_shared(); + VertexSet next_slice_lookup; + VertexSet bad_vertices; + std::list all_edges; + EdgeSet edge_lookup; + for (const std::pair& pair : u_frontier->get()) { + const UnitID& unit = pair.first; + const Edge& e = pair.second; + if (unit.type() == UnitType::Bit) { + Vertex targ = target(e); + b_frontier_t::const_iterator found = + b_frontier->get().find(Bit(unit)); + if (found != b_frontier->get().end()) { + bool still_live = false; + for (const Edge& e : found->second) { + if (target(e) != targ) { + still_live = true; + break; + } + } + if (still_live) continue; + } + } + all_edges.push_back(e); + edge_lookup.insert(e); + } + for (const std::pair& pair : b_frontier->get()) { + for (const Edge& e : pair.second) { + all_edges.push_back(e); + edge_lookup.insert(e); + } + } + if (skip_func) { + // advance through skippable + bool can_skip; + do { + can_skip = false; + VertexSet skip_slice_lookup; + for (const Edge& e : all_edges) { + Vertex try_v = target(e); + if (detect_final_Op(try_v) || + (!skip_func(get_Op_ptr_from_Vertex(try_v)))) + continue; + if (skip_slice_lookup.find(try_v) != skip_slice_lookup.end()) continue; + bool good_vertex = bad_vertices.find(try_v) == bad_vertices.end(); + if (!good_vertex) continue; + const EdgeVec ins = get_in_edges(try_v); + for (const Edge& in : ins) { + if (edge_lookup.find(in) == edge_lookup.end()) { + good_vertex = false; + break; + } + } + if (!good_vertex) { + bad_vertices.insert(try_v); + continue; + } + skip_slice_lookup.insert(try_v); + } + if (!skip_slice_lookup.empty()) { + b_frontier = get_next_b_frontier( + *this, b_frontier, u_frontier, skip_slice_lookup); + u_frontier = get_next_u_frontier(*this, u_frontier, skip_slice_lookup); + bad_vertices = {}; + all_edges = {}; + edge_lookup = {}; + + for (const std::pair& pair : u_frontier->get()) { + Edge e = pair.second; + all_edges.push_back(e); + edge_lookup.insert(e); + } + for (const std::pair& pair : b_frontier->get()) { + for (const Edge& edge : pair.second) { + Edge e = edge; + all_edges.push_back(e); + edge_lookup.insert(e); + } + } + can_skip = true; + } + } while (can_skip); + } + // find the next slice first + for (const Edge& e : all_edges) { + Vertex try_v = target(e); + if (detect_final_Op(try_v)) continue; + if (next_slice_lookup.find(try_v) != next_slice_lookup.end()) + continue; // already going to be in next slice + bool good_vertex = bad_vertices.find(try_v) == bad_vertices.end(); + if (!good_vertex) continue; + const EdgeVec ins = get_in_edges(try_v); + for (const Edge& in : ins) { + if (edge_lookup.find(in) == edge_lookup.end()) { + good_vertex = false; + bad_vertices.insert(try_v); + break; + } + } + if (good_vertex) { + next_slice_lookup.insert(try_v); + next_slice->push_back(try_v); + } + } + return { + next_slice, get_next_u_frontier(*this, u_frontier, next_slice_lookup), + get_next_b_frontier(*this, b_frontier, u_frontier, next_slice_lookup)}; +} + +CutFrontier Circuit::next_q_cut( + std::shared_ptr u_frontier) const { + auto next_slice = std::make_shared(); + VertexSet next_slice_lookup; + VertexSet bad_vertices; + EdgeSet edge_lookup; + for (const std::pair& pair : u_frontier->get()) { + edge_lookup.insert(pair.second); + } + + // find the next slice first + for (const std::pair& pair : u_frontier->get()) { + Vertex try_v = target(pair.second); + if (detect_final_Op(try_v)) continue; + if (next_slice_lookup.contains(try_v)) + continue; // already going to be in next slice + bool good_vertex = !bad_vertices.contains(try_v); + if (!good_vertex) continue; + EdgeVec ins = get_in_edges(try_v); + for (const Edge& in : ins) { + if (!edge_lookup.contains(in) && (get_edgetype(in) == EdgeType::Quantum || + get_edgetype(in) == EdgeType::WASM)) { + good_vertex = false; + bad_vertices.insert(try_v); + break; + } + } + if (good_vertex) { + next_slice_lookup.insert(try_v); + next_slice->push_back(try_v); + } + } + + return { + next_slice, get_next_u_frontier(*this, u_frontier, next_slice_lookup), + std::make_shared()}; +} + +} // namespace tket diff --git a/tket/src/Circuit/macro_circ_info.cpp b/tket/src/Circuit/macro_circ_info.cpp index 73fc0a3b31..9e9eaf7e2e 100644 --- a/tket/src/Circuit/macro_circ_info.cpp +++ b/tket/src/Circuit/macro_circ_info.cpp @@ -20,6 +20,7 @@ #include "tket/Circuit/Circuit.hpp" #include "tket/Circuit/DAGDefs.hpp" +#include "tket/Circuit/Slices.hpp" #include "tket/OpType/EdgeType.hpp" #include "tket/OpType/OpType.hpp" #include "tket/Ops/OpPtr.hpp" @@ -84,7 +85,7 @@ std::list Circuit::get_commands_of_type(OpType op_type) const { std::function skip_func = [=](Op_ptr op) { return (op->get_type() != op_type); }; - Circuit::SliceIterator slice_iter(*this, skip_func); + SliceIterator slice_iter(*this, skip_func); for (const Vertex& v : *slice_iter) { coms.push_back(command_from_vertex( v, slice_iter.get_u_frontier(), slice_iter.get_prev_b_frontier())); @@ -349,269 +350,6 @@ Edge Circuit::skip_irrelevant_edges(Edge current) const { return current; } -static std::shared_ptr get_next_u_frontier( - const Circuit& circ, std::shared_ptr u_frontier, - const VertexSet& next_slice_lookup) { - std::shared_ptr next_frontier = - std::make_shared(); - for (const std::pair& pair : u_frontier->get()) { - Vertex next_v = circ.target(pair.second); - if (next_slice_lookup.find(next_v) == next_slice_lookup.end()) { - next_frontier->insert(pair); - } else { - next_frontier->insert( - {pair.first, circ.get_next_edge(next_v, pair.second)}); - } - } - return next_frontier; -} - -static std::shared_ptr get_next_b_frontier( - const Circuit& circ, std::shared_ptr b_frontier, - std::shared_ptr u_frontier, - const VertexSet& next_slice_lookup) { - std::shared_ptr next_b_frontier = - std::make_shared(); - // Copy any remaining edges - for (const std::pair& pair : b_frontier->get()) { - EdgeVec remaining; - for (const Edge& e : pair.second) { - Vertex targ = circ.target(e); - if (next_slice_lookup.find(targ) == next_slice_lookup.end()) { - remaining.push_back(e); - } - } - if (!remaining.empty()) { - next_b_frontier->insert({pair.first, remaining}); - } - } - // Add any new bits introduced in this slice - for (const std::pair& pair : u_frontier->get()) { - switch (circ.get_edgetype(pair.second)) { - case EdgeType::Quantum: - case EdgeType::WASM: { - break; - } - case EdgeType::Classical: { - Vertex next_v = circ.target(pair.second); - if (next_slice_lookup.find(next_v) == next_slice_lookup.end()) continue; - if (next_b_frontier->get().find(Bit(pair.first)) != - next_b_frontier->end()) { - TKET_ASSERT(!"RAW hazard created in slicing"); - } - port_t p = circ.get_target_port(pair.second); - EdgeVec reads = circ.get_nth_b_out_bundle(next_v, p); - if (!reads.empty()) next_b_frontier->insert({Bit(pair.first), reads}); - break; - } - case EdgeType::Boolean: { - throw CircuitInvalidity("Boolen edge not allowed in b_frontier_t"); - } - default: { - TKET_ASSERT(!"get_next_b_frontier found invalid edge type in sig"); - } - } - } - return next_b_frontier; -} - -CutFrontier Circuit::next_cut( - std::shared_ptr u_frontier, - std::shared_ptr b_frontier) const { - auto next_slice = std::make_shared(); - VertexSet next_slice_lookup; - VertexSet bad_vertices; - std::list all_edges; - EdgeSet edge_lookup; - for (const std::pair& pair : u_frontier->get()) { - if (pair.first.type() == UnitType::Bit) { - Vertex targ = target(pair.second); - b_frontier_t::const_iterator found = - b_frontier->get().find(Bit(pair.first)); - if (found != b_frontier->get().end()) { - bool still_live = false; - for (const Edge& e : found->second) { - if (target(e) != targ) { - still_live = true; - break; - } - } - if (still_live) continue; - } - } - all_edges.push_back(pair.second); - edge_lookup.insert(pair.second); - } - - for (const std::pair& pair : b_frontier->get()) { - for (const Edge& e : pair.second) { - all_edges.push_back(e); - edge_lookup.insert(e); - } - } - // find the next slice first - for (const Edge& e : all_edges) { - Vertex try_v = target(e); - if (detect_final_Op(try_v)) continue; - if (next_slice_lookup.find(try_v) != next_slice_lookup.end()) - continue; // already going to be in next slice - bool good_vertex = bad_vertices.find(try_v) == bad_vertices.end(); - if (!good_vertex) continue; - EdgeVec ins = get_in_edges(try_v); - for (const Edge& in : ins) { - if (edge_lookup.find(in) == edge_lookup.end()) { - good_vertex = false; - bad_vertices.insert(try_v); - break; - } - } - if (good_vertex) { - next_slice_lookup.insert(try_v); - next_slice->push_back(try_v); - } - } - - return { - next_slice, get_next_u_frontier(*this, u_frontier, next_slice_lookup), - get_next_b_frontier(*this, b_frontier, u_frontier, next_slice_lookup)}; -} - -CutFrontier Circuit::next_cut( - std::shared_ptr u_frontier, - std::shared_ptr b_frontier, - const std::function& skip_func) const { - VertexSet bad_vertices; - std::list all_edges; - EdgeSet edge_lookup; - for (const std::pair& pair : u_frontier->get()) { - Edge e = pair.second; - all_edges.push_back(e); - edge_lookup.insert(e); - } - for (const std::pair& pair : b_frontier->get()) { - for (const Edge& edge : pair.second) { - Edge e = edge; - all_edges.push_back(e); - edge_lookup.insert(e); - } - } - // advance through skippable - bool can_skip; - do { - can_skip = false; - VertexSet skip_slice_lookup; - for (const Edge& e : all_edges) { - Vertex try_v = target(e); - if (detect_final_Op(try_v) || (!skip_func(get_Op_ptr_from_Vertex(try_v)))) - continue; - if (skip_slice_lookup.find(try_v) != skip_slice_lookup.end()) continue; - bool good_vertex = bad_vertices.find(try_v) == bad_vertices.end(); - if (!good_vertex) continue; - const EdgeVec ins = get_in_edges(try_v); - for (const Edge& in : ins) { - if (edge_lookup.find(in) == edge_lookup.end()) { - good_vertex = false; - break; - } - } - if (!good_vertex) { - bad_vertices.insert(try_v); - continue; - } - skip_slice_lookup.insert(try_v); - } - if (!skip_slice_lookup.empty()) { - b_frontier = - get_next_b_frontier(*this, b_frontier, u_frontier, skip_slice_lookup); - u_frontier = get_next_u_frontier(*this, u_frontier, skip_slice_lookup); - bad_vertices = {}; - all_edges = {}; - edge_lookup = {}; - - for (const std::pair& pair : u_frontier->get()) { - Edge e = pair.second; - all_edges.push_back(e); - edge_lookup.insert(e); - } - for (const std::pair& pair : b_frontier->get()) { - for (const Edge& edge : pair.second) { - Edge e = edge; - all_edges.push_back(e); - edge_lookup.insert(e); - } - } - can_skip = true; - } - } while (can_skip); - - // find the next slice first - auto next_slice = std::make_shared(); - VertexSet next_slice_lookup; - - for (const Edge& e : all_edges) { - Vertex try_v = target(e); - if (detect_final_Op(try_v)) continue; - if (next_slice_lookup.find(try_v) != next_slice_lookup.end()) - continue; // already going to be in next slice - bool good_vertex = bad_vertices.find(try_v) == bad_vertices.end(); - if (!good_vertex) continue; - const EdgeVec ins = get_in_edges(try_v); - for (const Edge& in : ins) { - if (edge_lookup.find(in) == edge_lookup.end()) { - good_vertex = false; - bad_vertices.insert(try_v); - break; - } - } - if (good_vertex) { - next_slice_lookup.insert(try_v); - next_slice->push_back(try_v); - } - } - - return { - next_slice, get_next_u_frontier(*this, u_frontier, next_slice_lookup), - get_next_b_frontier(*this, b_frontier, u_frontier, next_slice_lookup)}; -} - -CutFrontier Circuit::next_q_cut( - std::shared_ptr u_frontier) const { - auto next_slice = std::make_shared(); - VertexSet next_slice_lookup; - VertexSet bad_vertices; - EdgeSet edge_lookup; - for (const std::pair& pair : u_frontier->get()) { - edge_lookup.insert(pair.second); - } - - // find the next slice first - for (const std::pair& pair : u_frontier->get()) { - Vertex try_v = target(pair.second); - if (detect_final_Op(try_v)) continue; - if (next_slice_lookup.contains(try_v)) - continue; // already going to be in next slice - bool good_vertex = !bad_vertices.contains(try_v); - if (!good_vertex) continue; - EdgeVec ins = get_in_edges(try_v); - for (const Edge& in : ins) { - if (!edge_lookup.contains(in) && (get_edgetype(in) == EdgeType::Quantum || - get_edgetype(in) == EdgeType::WASM)) { - good_vertex = false; - bad_vertices.insert(try_v); - break; - } - } - if (good_vertex) { - next_slice_lookup.insert(try_v); - next_slice->push_back(try_v); - } - } - - return { - next_slice, get_next_u_frontier(*this, u_frontier, next_slice_lookup), - std::make_shared()}; -} - SliceVec Circuit::get_reverse_slices() const { vertex_map_t mapping; vertex_map_t rev_mapping; @@ -685,7 +423,7 @@ unsigned Circuit::depth() const { std::function skip_func = [&](Op_ptr op) { return (op->get_type() == OpType::Barrier); }; - Circuit::SliceIterator slice_iter(*this, skip_func); + SliceIterator slice_iter(*this, skip_func); if (!(*slice_iter).empty()) count++; while (!slice_iter.finished()) { slice_iter.cut_ = this->next_cut( @@ -700,7 +438,7 @@ unsigned Circuit::depth_by_type(OpType _type) const { std::function skip_func = [&](Op_ptr op) { return (op->get_type() != _type); }; - Circuit::SliceIterator slice_iter(*this, skip_func); + SliceIterator slice_iter(*this, skip_func); if (!(*slice_iter).empty()) count++; while (!slice_iter.finished()) { slice_iter.cut_ = this->next_cut( @@ -715,7 +453,7 @@ unsigned Circuit::depth_by_types(const OpTypeSet& _types) const { std::function skip_func = [&](Op_ptr op) { return (_types.find(op->get_type()) == _types.end()); }; - Circuit::SliceIterator slice_iter(*this, skip_func); + SliceIterator slice_iter(*this, skip_func); if (!(*slice_iter).empty()) count++; while (!slice_iter.finished()) { slice_iter.cut_ = this->next_cut( @@ -730,7 +468,7 @@ unsigned Circuit::depth_2q() const { std::function skip_func = [&](Op_ptr op) { return (op->n_qubits() != 2 || op->get_type() == OpType::Barrier); }; - Circuit::SliceIterator slice_iter(*this, skip_func); + SliceIterator slice_iter(*this, skip_func); if (!(*slice_iter).empty()) count++; while (!slice_iter.finished()) { slice_iter.cut_ = this->next_cut( @@ -793,110 +531,6 @@ std::map Circuit::edge_unit_map() const { return map; } -/*SliceIterator related methods*/ -Circuit::SliceIterator::SliceIterator(const Circuit& circ) - : cut_(), circ_(&circ) { - cut_.init(); - // add qubits to u_frontier - for (const Qubit& q : circ.all_qubits()) { - Vertex in = circ.get_in(q); - cut_.slice->push_back(in); - cut_.u_frontier->insert({q, circ.get_nth_out_edge(in, 0)}); - } - - // add bits to u_frontier and b_frontier - for (const Bit& b : circ.all_bits()) { - Vertex in = circ.get_in(b); - cut_.slice->push_back(in); - cut_.b_frontier->insert({b, circ.get_nth_b_out_bundle(in, 0)}); - cut_.u_frontier->insert({b, circ.get_nth_out_edge(in, 0)}); - } - - // add WasmState to u_frontier - for (unsigned i = 0; i < circ._number_of_wasm_wires; ++i) { - Vertex in = circ.get_in(circ.wasmwire[i]); - cut_.slice->push_back(in); - cut_.u_frontier->insert({circ.wasmwire[i], circ.get_nth_out_edge(in, 0)}); - } - - prev_b_frontier_ = cut_.b_frontier; - cut_ = circ.next_cut(cut_.u_frontier, cut_.b_frontier); - - // Add all vertices that have no Quantum or Classical edges (e.g. Phase) and - // no Boolean inputs: - BGL_FORALL_VERTICES(v, circ.dag, DAG) { - if (circ.n_in_edges(v) == 0 && - circ.n_out_edges_of_type(v, EdgeType::Quantum) == 0 && - circ.n_out_edges_of_type(v, EdgeType::Classical) == 0 && - circ.n_out_edges_of_type(v, EdgeType::WASM) == 0) { - cut_.slice->push_back(v); - } - } -} - -Circuit::SliceIterator::SliceIterator( - const Circuit& circ, const std::function& skip_func) - : cut_(), circ_(&circ) { - cut_.init(); - // add qubits to u_frontier - for (const Qubit& q : circ.all_qubits()) { - Vertex in = circ.get_in(q); - cut_.u_frontier->insert({q, circ.get_nth_out_edge(in, 0)}); - } - - // add bits to u_frontier and b_frontier - for (const Bit& b : circ.all_bits()) { - Vertex in = circ.get_in(b); - cut_.b_frontier->insert({b, circ.get_nth_b_out_bundle(in, 0)}); - cut_.u_frontier->insert({b, circ.get_nth_out_edge(in, 0)}); - } - - // add WasmState to u_frontier - for (unsigned i = 0; i < circ._number_of_wasm_wires; ++i) { - Vertex in = circ.get_in(circ.wasmwire[i]); - cut_.slice->push_back(in); - cut_.u_frontier->insert({circ.wasmwire[i], circ.get_nth_out_edge(in, 0)}); - } - - prev_b_frontier_ = cut_.b_frontier; - cut_ = circ.next_cut(cut_.u_frontier, cut_.b_frontier, skip_func); -} - -Circuit::SliceIterator Circuit::slice_begin() const { - return Circuit::SliceIterator(*this); -} - -Circuit::SliceIterator Circuit::slice_end() { return nullsit; } -const Circuit::SliceIterator Circuit::nullsit = Circuit::SliceIterator(); - -Circuit::SliceIterator::Sliceholder Circuit::SliceIterator::operator++(int) { - Sliceholder ret(*cut_.slice); - ++*this; - return ret; -} - -Circuit::SliceIterator& Circuit::SliceIterator::operator++() { - if (this->finished()) { - *this = circ_->slice_end(); - return *this; - } - prev_b_frontier_ = cut_.b_frontier; - cut_ = circ_->next_cut(cut_.u_frontier, cut_.b_frontier); - return *this; -} - -bool Circuit::SliceIterator::finished() const { - for (const std::pair& pair : - this->cut_.u_frontier->get()) { - if (!circ_->detect_final_Op(circ_->target(pair.second))) return false; - } - for (const std::pair& pair : - this->cut_.b_frontier->get()) { - if (!pair.second.empty()) return false; - } - return true; -} - Circuit::CommandIterator::CommandIterator(const Circuit& circ) : current_slice_iterator_(circ.slice_begin()), current_index_(0), diff --git a/tket/test/CMakeLists.txt b/tket/test/CMakeLists.txt index 4e020c4d16..44437e6df2 100644 --- a/tket/test/CMakeLists.txt +++ b/tket/test/CMakeLists.txt @@ -66,6 +66,7 @@ add_executable(test-tket src/Circuit/test_DummyBox.cpp src/Circuit/test_Multiplexor.cpp src/Circuit/test_PauliExpBoxes.cpp + src/Circuit/test_Slices.cpp src/Circuit/test_StatePreparation.cpp src/Circuit/test_Symbolic.cpp src/Circuit/test_ThreeQubitConversion.cpp diff --git a/tket/test/src/Circuit/test_Circ.cpp b/tket/test/src/Circuit/test_Circ.cpp index 2f47486862..24dce6424b 100644 --- a/tket/test/src/Circuit/test_Circ.cpp +++ b/tket/test/src/Circuit/test_Circ.cpp @@ -24,6 +24,7 @@ #include "tket/Circuit/DAGDefs.hpp" #include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Circuit/Simulation/CircuitSimulator.hpp" +#include "tket/Circuit/Slices.hpp" #include "tket/Gate/GatePtr.hpp" #include "tket/Gate/OpPtrFunctions.hpp" #include "tket/OpType/EdgeType.hpp" @@ -1596,7 +1597,7 @@ SCENARIO("Test circuit.dagger() method") { Circuit daggered = circ.dagger(); REQUIRE(daggered == circ); SliceVec slices1, slices2; - for (Circuit::SliceIterator sliceit = daggered.slice_begin(); + for (SliceIterator sliceit = daggered.slice_begin(); sliceit != daggered.slice_end(); sliceit++) { slices1.push_back(*sliceit); } diff --git a/tket/test/src/Circuit/test_Slices.cpp b/tket/test/src/Circuit/test_Slices.cpp new file mode 100644 index 0000000000..88883e2aad --- /dev/null +++ b/tket/test/src/Circuit/test_Slices.cpp @@ -0,0 +1,35 @@ +// Copyright 2019-2024 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "tket/Circuit/Circuit.hpp" + +namespace tket { +namespace test_ClassicalOps { + +SCENARIO("Circuit iteration") { + GIVEN("Successive conditionals on the same unit") { + // https://github.com/CQCL/tket/issues/1673 + Circuit circ(2, 2); + circ.add_conditional_gate(OpType::X, {}, {1}, {0, 1}, 3); + circ.add_conditional_gate(OpType::Y, {}, {1}, {0}, 1); + circ.add_measure(0, 0); + unsigned d = circ.depth(); + REQUIRE(d == 3); + } +} + +} // namespace test_ClassicalOps +} // namespace tket diff --git a/tket/test/src/Ops/test_ClassicalOps.cpp b/tket/test/src/Ops/test_ClassicalOps.cpp index 9e4ba89e79..a418b394d6 100644 --- a/tket/test/src/Ops/test_ClassicalOps.cpp +++ b/tket/test/src/Ops/test_ClassicalOps.cpp @@ -16,6 +16,7 @@ #include "../testutil.hpp" #include "tket/Circuit/Circuit.hpp" +#include "tket/Circuit/Slices.hpp" #include "tket/Ops/ClassicalOps.hpp" #include "tket/Transformations/BasicOptimisation.hpp" #include "tket/Transformations/CliffordOptimisation.hpp" @@ -105,8 +106,7 @@ SCENARIO("Check that classical bundles work as expected") { circ.add_conditional_gate(OpType::Z, {}, uvec{2}, {0}, 1); circ.assert_valid(); std::vector> types; - for (Circuit::SliceIterator si = circ.slice_begin(); si != circ.slice_end(); - ++si) { + for (SliceIterator si = circ.slice_begin(); si != circ.slice_end(); ++si) { Slice sl = *si; std::vector slice_types; for (const Vertex& v : sl) { @@ -139,8 +139,7 @@ SCENARIO("Check that classical bundles work as expected") { circ.add_measure(3, 1); circ.assert_valid(); std::vector> types; - for (Circuit::SliceIterator si = circ.slice_begin(); si != circ.slice_end(); - ++si) { + for (SliceIterator si = circ.slice_begin(); si != circ.slice_end(); ++si) { Slice sl = *si; std::vector slice_types; for (const Vertex& v : sl) {