diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index a2ee3c4663..c7da315a6b 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -496,6 +496,13 @@ PYBIND11_MODULE(passes, m) { ":py:class:`Architecture` Nodes", py::arg("placer")); + m.def( + "NaivePlacementPass", &gen_naive_placement_pass, + ":param architecture: The Architecture used for relabelling." + "\n:return: a pass to relabel :py:class:`Circuit` Qubits to " + ":py:class:`Architecture` Nodes", + py::arg("arc")); + m.def( "RenameQubitsPass", &gen_rename_qubits_pass, "Rename some or all qubits.", "\n\n:param qubit_map: map from old to new qubit names", diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index cd2c5079da..3f760707fa 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -31,6 +31,8 @@ Minor new features: * New ``pytket.passes.auto_rebase_pass`` and ``pytket.passes.auto_squash_pass`` which attempt to construct rebase and squash passess given a target gate set from known decompositions. +* New ``pytket.passes.NaivePlacementPass`` which completes a basic relabelling of all Circuit Qubit + not labelled as some Architecture Node to any available Architecture Node 0.19.2 (February 2022) ---------------------- diff --git a/pytket/tests/predicates_test.py b/pytket/tests/predicates_test.py index 293994cbfc..a864d91b49 100644 --- a/pytket/tests/predicates_test.py +++ b/pytket/tests/predicates_test.py @@ -32,6 +32,7 @@ RoutingPass, CXMappingPass, PlacementPass, + NaivePlacementPass, RenameQubitsPass, FullMappingPass, DefaultMappingPass, @@ -202,14 +203,29 @@ def test_routing_and_placement_pass() -> None: pl = Placement(arc) routing = RoutingPass(arc) placement = PlacementPass(pl) + nplacement = NaivePlacementPass(arc) cu = CompilationUnit(circ.copy()) assert placement.apply(cu) assert routing.apply(cu) + assert nplacement.apply(cu) expected_map = {q[0]: n1, q[1]: n0, q[2]: n2, q[3]: n5, q[4]: n3} assert cu.initial_map == expected_map + cu1 = CompilationUnit(circ.copy()) + assert nplacement.apply(cu1) + arcnodes = arc.nodes + expected_nmap = { + q[0]: arcnodes[0], + q[1]: arcnodes[1], + q[2]: arcnodes[2], + q[3]: arcnodes[3], + q[4]: arcnodes[4], + } + assert cu1.initial_map == expected_nmap # check composition works ok - seq_pass = SequencePass([SynthesiseTket(), placement, routing, SynthesiseUMD()]) + seq_pass = SequencePass( + [SynthesiseTket(), placement, routing, nplacement, SynthesiseUMD()] + ) cu2 = CompilationUnit(circ.copy()) assert seq_pass.apply(cu2) assert cu2.initial_map == expected_map @@ -223,7 +239,7 @@ def test_routing_and_placement_pass() -> None: def test_default_mapping_pass() -> None: circ = Circuit() - q = circ.add_q_register("q", 5) + q = circ.add_q_register("q", 6) circ.CX(0, 1) circ.H(0) circ.Z(1) @@ -233,14 +249,17 @@ def test_default_mapping_pass() -> None: circ.X(2) circ.CX(1, 4) circ.CX(0, 4) + circ.H(5) n0 = Node("b", 0) n1 = Node("b", 1) n2 = Node("b", 2) n3 = Node("a", 0) n4 = Node("f", 0) - arc = Architecture([[n0, n1], [n1, n2], [n2, n3], [n3, n4]]) + n5 = Node("g", 7) + arc = Architecture([[n0, n1], [n1, n2], [n2, n3], [n3, n4], [n4, n5]]) pl = GraphPlacement(arc) + nplacement = NaivePlacementPass(arc) routing = RoutingPass(arc) placement = PlacementPass(pl) default = DefaultMappingPass(arc) @@ -249,6 +268,7 @@ def test_default_mapping_pass() -> None: assert placement.apply(cu_rp) assert routing.apply(cu_rp) + assert nplacement.apply(cu_rp) assert default.apply(cu_def) assert cu_rp.circuit == cu_def.circuit @@ -650,6 +670,10 @@ def sq(a: float, b: float, c: float) -> Circuit: assert p_pass.to_dict()["StandardPass"]["name"] == "PlacementPass" assert p_pass.to_dict()["StandardPass"]["placement"]["type"] == "GraphPlacement" assert p_pass.to_dict()["StandardPass"]["placement"]["config"]["depth_limit"] == 5 + # NaivePlacementPass + np_pass = NaivePlacementPass(arc) + assert np_pass.to_dict()["StandardPass"]["name"] == "NaivePlacementPass" + assert check_arc_dict(arc, np_pass.to_dict()["StandardPass"]["architecture"]) # RenameQubitsPass qm = {Qubit("a", 0): Qubit("b", 1), Qubit("a", 1): Qubit("b", 0)} rn_pass = RenameQubitsPass(qm) @@ -662,8 +686,10 @@ def sq(a: float, b: float, c: float) -> Circuit: assert fm_pass.to_dict()["pass_class"] == "SequencePass" p_pass = fm_pass.get_sequence()[0] r_pass = fm_pass.get_sequence()[1] - assert p_pass.to_dict()["StandardPass"]["name"] == "PlacementPass" + np_pass = fm_pass.get_sequence()[2] + assert np_pass.to_dict()["StandardPass"]["name"] == "NaivePlacementPass" assert r_pass.to_dict()["StandardPass"]["name"] == "RoutingPass" + assert p_pass.to_dict()["StandardPass"]["name"] == "PlacementPass" assert check_arc_dict(arc, r_pass.to_dict()["StandardPass"]["architecture"]) assert p_pass.to_dict()["StandardPass"]["placement"]["type"] == "GraphPlacement" # DefaultMappingPass @@ -671,20 +697,24 @@ def sq(a: float, b: float, c: float) -> Circuit: assert dm_pass.to_dict()["pass_class"] == "SequencePass" p_pass = dm_pass.get_sequence()[0].get_sequence()[0] r_pass = dm_pass.get_sequence()[0].get_sequence()[1] + np_pass = dm_pass.get_sequence()[0].get_sequence()[2] d_pass = dm_pass.get_sequence()[1] assert d_pass.to_dict()["StandardPass"]["name"] == "DelayMeasures" assert p_pass.to_dict()["StandardPass"]["name"] == "PlacementPass" + assert np_pass.to_dict()["StandardPass"]["name"] == "NaivePlacementPass" assert r_pass.to_dict()["StandardPass"]["name"] == "RoutingPass" assert check_arc_dict(arc, r_pass.to_dict()["StandardPass"]["architecture"]) assert p_pass.to_dict()["StandardPass"]["placement"]["type"] == "GraphPlacement" # DefaultMappingPass with delay_measures=False dm_pass = DefaultMappingPass(arc, False) assert dm_pass.to_dict()["pass_class"] == "SequencePass" - assert len(dm_pass.get_sequence()) == 2 + assert len(dm_pass.get_sequence()) == 3 p_pass = dm_pass.get_sequence()[0] r_pass = dm_pass.get_sequence()[1] + np_pass = dm_pass.get_sequence()[2] assert p_pass.to_dict()["StandardPass"]["name"] == "PlacementPass" assert r_pass.to_dict()["StandardPass"]["name"] == "RoutingPass" + assert np_pass.to_dict()["StandardPass"]["name"] == "NaivePlacementPass" assert check_arc_dict(arc, r_pass.to_dict()["StandardPass"]["architecture"]) assert p_pass.to_dict()["StandardPass"]["placement"]["type"] == "GraphPlacement" # AASRouting diff --git a/schemas/compiler_pass_v1.json b/schemas/compiler_pass_v1.json index fa74d46b26..0a473e7129 100644 --- a/schemas/compiler_pass_v1.json +++ b/schemas/compiler_pass_v1.json @@ -403,6 +403,20 @@ ] } }, + { + "if": { + "properties": { + "name": { + "const": "NaivePlacementPass" + } + } + }, + "then": { + "required": [ + "architecture" + ] + } + }, { "if": { "properties": { diff --git a/tket/src/Placement/Placement.cpp b/tket/src/Placement/Placement.cpp index adb248ed43..e5d2184593 100644 --- a/tket/src/Placement/Placement.cpp +++ b/tket/src/Placement/Placement.cpp @@ -154,6 +154,45 @@ std::vector Placement::get_all_placement_maps( return {get_placement_map(circ_)}; } +qubit_mapping_t NaivePlacement::get_placement_map(const Circuit &circ_) const { + return get_all_placement_maps(circ_).at(0); +} + +std::vector NaivePlacement::get_all_placement_maps( + const Circuit &circ_) const { + qubit_mapping_t placement; + qubit_vector_t to_place; + std::vector placed; + + // Find which/if any qubits need placing + for (const Qubit &q : circ_.all_qubits()) { + Node n(q); + if (!this->arc_.node_exists(n)) { + to_place.push_back(n); + } else { + placed.push_back(n); + // if already placed, make sure qubit retains placement + placement.insert({n, n}); + } + } + // avoid doing std::set_difference unless qubits need to be placed + unsigned n_placed = to_place.size(); + if (n_placed > 0) { + std::vector difference, + architecture_nodes = this->arc_.get_all_nodes_vec(); + std::set_difference( + architecture_nodes.begin(), architecture_nodes.end(), placed.begin(), + placed.end(), std::inserter(difference, difference.begin())); + // should always be enough remaining qubits to assign unplaced qubits to + TKET_ASSERT(difference.size() >= n_placed); + for (unsigned i = 0; i < n_placed; i++) { + // naively assign each qubit to some free node + placement.insert({to_place[i], difference[i]}); + } + } + return {placement}; +} + qubit_mapping_t LinePlacement::get_placement_map(const Circuit &circ_) const { return get_all_placement_maps(circ_).at(0); } diff --git a/tket/src/Placement/include/Placement/Placement.hpp b/tket/src/Placement/include/Placement/Placement.hpp index e567c0042b..ff2f2dd60c 100644 --- a/tket/src/Placement/include/Placement/Placement.hpp +++ b/tket/src/Placement/include/Placement/Placement.hpp @@ -269,6 +269,42 @@ class Placement { Architecture arc_; }; +/** + * NaivePlacement class provides methods for relabelling any + * Qubit objects in some Circuit to Node objects in some Architecture + * given the constraint that only Qubit that are not already labelled + * as some Node can be relabelled, and only to Architecture Node + * that are not already in the Circuit. + */ +class NaivePlacement : public Placement { + public: + /** + * @param _arc Architecture object later relabellings are produced for + */ + explicit NaivePlacement(const Architecture& _arc) { arc_ = _arc; } + /** + * Given some circuit, returns a map between Qubit which defines some + * relabelling of some Circuit qubits to Architecture qubits + * + * @param circ_ Circuit map relabelling is defined for + * + * @return Map defining relabelling for circuit Qubit objects + */ + qubit_mapping_t get_placement_map(const Circuit& circ_) const override; + + /** + * Given some circuit, returns a single map for relabelling + * in a vector. + * + * @param circ_ Circuit map relabelling is defined for + * + * @return Vector of a single Map defining relabelling for Circuit + * Qubit objects. + */ + std::vector get_all_placement_maps( + const Circuit& circ_) const override; +}; + class LinePlacement : public Placement { public: explicit LinePlacement(const Architecture& _arc) { arc_ = _arc; } diff --git a/tket/src/Predicates/CompilerPass.cpp b/tket/src/Predicates/CompilerPass.cpp index 654ca3ea0f..50218aeb84 100644 --- a/tket/src/Predicates/CompilerPass.cpp +++ b/tket/src/Predicates/CompilerPass.cpp @@ -438,6 +438,9 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { } else if (passname == "PlacementPass") { pp = gen_placement_pass(content.at("placement").get()); + } else if (passname == "NaivePlacementPass") { + pp = gen_naive_placement_pass( + content.at("architecture").get()); } else if (passname == "RenameQubitsPass") { pp = gen_rename_qubits_pass( content.at("qubit_map").get>()); diff --git a/tket/src/Predicates/PassGenerators.cpp b/tket/src/Predicates/PassGenerators.cpp index 5d15c88784..95af0feb2e 100644 --- a/tket/src/Predicates/PassGenerators.cpp +++ b/tket/src/Predicates/PassGenerators.cpp @@ -182,10 +182,34 @@ PassPtr gen_placement_pass(const PlacementPtr& placement_ptr) { return std::make_shared(precons, t, pc, j); } +PassPtr gen_naive_placement_pass(const Architecture& arc) { + Transform::Transformation trans = [=](Circuit& circ, + std::shared_ptr maps) { + NaivePlacement np(arc); + return np.place(circ, maps); + }; + Transform t = Transform(trans); + PredicatePtr n_qubit_pred = + std::make_shared(arc.n_nodes()); + + PredicatePtrMap precons{CompilationUnit::make_type_pair(n_qubit_pred)}; + PredicatePtr placement_pred = std::make_shared(arc); + PredicatePtrMap s_postcons{CompilationUnit::make_type_pair(placement_pred)}; + PostConditions pc{s_postcons, {}, Guarantee::Preserve}; + // record pass config + nlohmann::json j; + j["name"] = "NaivePlacementPass"; + j["architecture"] = arc; + return std::make_shared(precons, t, pc, j); +} + PassPtr gen_full_mapping_pass( const Architecture& arc, const PlacementPtr& placement_ptr, const std::vector& config) { - return gen_placement_pass(placement_ptr) >> gen_routing_pass(arc, config); + std::vector vpp = { + gen_placement_pass(placement_ptr), gen_routing_pass(arc, config), + gen_naive_placement_pass(arc)}; + return std::make_shared(vpp); } PassPtr gen_default_mapping_pass(const Architecture& arc, bool delay_measures) { diff --git a/tket/src/Predicates/include/Predicates/PassGenerators.hpp b/tket/src/Predicates/include/Predicates/PassGenerators.hpp index bc204b4217..3bccaa3ea0 100644 --- a/tket/src/Predicates/include/Predicates/PassGenerators.hpp +++ b/tket/src/Predicates/include/Predicates/PassGenerators.hpp @@ -45,6 +45,8 @@ PassPtr gen_clifford_simp_pass(bool allow_swaps = true); PassPtr gen_rename_qubits_pass(const std::map& qm); PassPtr gen_placement_pass(const PlacementPtr& placement_ptr); + +PassPtr gen_naive_placement_pass(const Architecture& arc); /* This higher order function generates a Routing pass using the std::vector object */ PassPtr gen_full_mapping_pass( diff --git a/tket/tests/test_CompilerPass.cpp b/tket/tests/test_CompilerPass.cpp index 3008e04d47..15855ae98b 100644 --- a/tket/tests/test_CompilerPass.cpp +++ b/tket/tests/test_CompilerPass.cpp @@ -234,12 +234,13 @@ SCENARIO("Test making (mostly routing) passes using PassGenerators") { } } GIVEN("Synthesise Passes in a row then routing") { - Circuit circ(4); + Circuit circ(5); circ.add_op(OpType::H, {0}); circ.add_op(OpType::CZ, {0, 1}); circ.add_op(OpType::CH, {0, 2}); circ.add_op(OpType::CnX, {0, 1, 2, 3}); circ.add_op(OpType::CZ, {0, 1}); + circ.add_op(OpType::X, {4}); OpTypeSet ots = {OpType::CX, OpType::TK1, OpType::SWAP}; PredicatePtr gsp = std::make_shared(ots); SquareGrid grid(2, 3); diff --git a/tket/tests/test_Placement.cpp b/tket/tests/test_Placement.cpp index b03036e832..45c1976ba6 100644 --- a/tket/tests/test_Placement.cpp +++ b/tket/tests/test_Placement.cpp @@ -434,16 +434,6 @@ SCENARIO("Check Monomorpher satisfies correct placement conditions") { } Monomorpher morph(test_circ, arc, {}, {10, arc.n_connections()}); - /*std::vector results = morph.place(1); - THEN("The circuit is placed in the highly connected region.") { - std::set middle_nodes = {5, 6, 9, 10}; - for (auto map : results) { - for (auto mapping : map.map) { - REQUIRE(middle_nodes.find(arc.map_node( - mapping.second)) != middle_nodes.end()); - } - } - }*/ } } } @@ -497,9 +487,68 @@ SCENARIO( REQUIRE(potential_maps.size() > 0); } } +SCENARIO("Test NaivePlacement class") { + Architecture test_arc({{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 6}}); + GIVEN( + "No Qubits placed in Circuit, same number of qubits and architecture " + "nodes.") { + Circuit test_circ(7); + NaivePlacement np(test_arc); + qubit_mapping_t p = np.get_placement_map(test_circ); + REQUIRE(p[Qubit(0)] == Node(0)); + REQUIRE(p[Qubit(1)] == Node(1)); + REQUIRE(p[Qubit(2)] == Node(2)); + REQUIRE(p[Qubit(3)] == Node(3)); + REQUIRE(p[Qubit(4)] == Node(4)); + REQUIRE(p[Qubit(5)] == Node(5)); + REQUIRE(p[Qubit(6)] == Node(6)); + } + GIVEN("No Qubits placed in Circuit, less qubits than architecture nodes.") { + Circuit test_circ(6); + NaivePlacement np(test_arc); + qubit_mapping_t p = np.get_placement_map(test_circ); + REQUIRE(p[Qubit(0)] == Node(0)); + REQUIRE(p[Qubit(1)] == Node(1)); + REQUIRE(p[Qubit(2)] == Node(2)); + REQUIRE(p[Qubit(3)] == Node(3)); + REQUIRE(p[Qubit(4)] == Node(4)); + REQUIRE(p[Qubit(5)] == Node(5)); + } + GIVEN( + "Some Qubits placed in Circuit, same number of qubits and architecture " + "nodes.") { + Circuit test_circ(4); + test_circ.add_qubit(Node(0)); + test_circ.add_qubit(Node(1)); + test_circ.add_qubit(Node(2)); + NaivePlacement np(test_arc); + qubit_mapping_t p = np.get_placement_map(test_circ); + + REQUIRE(p[Qubit(0)] == Node(3)); + REQUIRE(p[Qubit(1)] == Node(4)); + REQUIRE(p[Qubit(2)] == Node(5)); + REQUIRE(p[Qubit(3)] == Node(6)); + REQUIRE(p[Node(0)] == Node(0)); + REQUIRE(p[Node(1)] == Node(1)); + REQUIRE(p[Node(2)] == Node(2)); + } + GIVEN("Some Qubits placed in Circuit, less qubits than architecture nodes.") { + Circuit test_circ(2); + test_circ.add_qubit(Node(0)); + test_circ.add_qubit(Node(1)); + test_circ.add_qubit(Node(2)); + NaivePlacement np(test_arc); + qubit_mapping_t p = np.get_placement_map(test_circ); + + REQUIRE(p[Qubit(0)] == Node(3)); + REQUIRE(p[Qubit(1)] == Node(4)); + REQUIRE(p[Node(0)] == Node(0)); + REQUIRE(p[Node(1)] == Node(1)); + REQUIRE(p[Node(2)] == Node(2)); + } +} // Tests for new placement method wrappers - SCENARIO( "Does the base Placement class correctly modify Circuits and return " "maps?") { diff --git a/tket/tests/test_json.cpp b/tket/tests/test_json.cpp index bbdec1822b..68d087e347 100644 --- a/tket/tests/test_json.cpp +++ b/tket/tests/test_json.cpp @@ -619,6 +619,7 @@ SCENARIO("Test compiler pass serializations") { COMPPASSJSONTEST(PlacementPass, gen_placement_pass(place)) // TKET-1419 COMPPASSJSONTEST(NoiseAwarePlacement, gen_placement_pass(na_place)) + COMPPASSJSONTEST(NaivePlacementPass, gen_naive_placement_pass(arc)) #undef COMPPASSJSONTEST GIVEN("RoutingPass") { // Can only be applied to placed circuits