diff --git a/include/Architecture.hpp b/include/Architecture.hpp index 2487063fd..800ffb220 100644 --- a/include/Architecture.hpp +++ b/include/Architecture.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include constexpr std::uint8_t GATES_OF_BIDIRECTIONAL_SWAP = 3U; @@ -32,6 +33,8 @@ constexpr std::uint32_t COST_TELEPORTATION = 2 * COST_CNOT_GATE + COST_MEASUREMENT + 4 * COST_SINGLE_QUBIT_GATE; constexpr std::uint32_t COST_DIRECTION_REVERSE = 4 * COST_SINGLE_QUBIT_GATE; +enum class DirectionReversalStrategy { Identity, Hadamard, NotApplicable }; + class Architecture { public: class Properties { @@ -242,7 +245,9 @@ class Architecture { return teleportationQubits; } - [[nodiscard]] const Matrix& getDistanceTable() const { return distanceTable; } + [[nodiscard]] const DistanceTable& getDistanceTable() const { + return distanceTable; + } [[nodiscard]] const Properties& getProperties() const { return properties; } @@ -279,10 +284,19 @@ class Architecture { singleQubitFidelities.clear(); } - [[nodiscard]] double distance(std::uint16_t control, - std::uint16_t target) const { + [[nodiscard]] double distance(std::uint16_t control, std::uint16_t target, + const qc::OpType opType) const { if (currentTeleportations.empty()) { - return distanceTable.at(control).at(target); + const auto& distanceEntry = distanceTable.at(control).at(target); + auto dist = distanceEntry.first; + if (distanceEntry.second && opType != qc::None) { + if (supportsDirectionReversal(opType)) { + dist += computeCostDirectionReverse(opType); + } else { + dist += 10000; + } + } + return dist; } return static_cast(bfs(control, target, currentTeleportations)); } @@ -348,13 +362,19 @@ class Architecture { static void printCouplingMap(const CouplingMap& cm, std::ostream& os); + static DirectionReversalStrategy + getDirectionReversalStrategy(qc::OpType opType); + static std::uint32_t computeCostDirectionReverse(qc::OpType opType); + static std::uint32_t computeGatesDirectionReverse(qc::OpType opType); + static bool supportsDirectionReversal(qc::OpType opType); + protected: std::string name; std::uint16_t nqubits = 0; CouplingMap couplingMap = {}; CouplingMap currentTeleportations = {}; bool isBidirectional = true; - Matrix distanceTable = {}; + DistanceTable distanceTable = {}; std::vector> teleportationQubits{}; Properties properties = {}; Matrix fidelityTable = {}; @@ -363,21 +383,23 @@ class Architecture { void createDistanceTable(); void createFidelityTable(); - static double costHeuristicBidirectional(const Dijkstra::Node& node) { + static std::pair + costHeuristicBidirectional(const Dijkstra::Node& node) { auto length = node.cost - 1; if (node.containsCorrectEdge) { - return length * COST_BIDIRECTIONAL_SWAP; + return {length * COST_BIDIRECTIONAL_SWAP, false}; } throw QMAPException("In a bidrectional architecture it should not happen " "that a node does not contain the right edge."); } - static double costHeuristicUnidirectional(const Dijkstra::Node& node) { + static std::pair + costHeuristicUnidirectional(const Dijkstra::Node& node) { auto length = node.cost - 1; if (node.containsCorrectEdge) { - return length * COST_UNIDIRECTIONAL_SWAP; + return {length * COST_UNIDIRECTIONAL_SWAP, false}; } - return length * COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE; + return {length * COST_UNIDIRECTIONAL_SWAP, true}; } // added for teleportation @@ -390,10 +412,10 @@ class Architecture { static std::size_t findCouplingLimit(const CouplingMap& cm, std::uint16_t nQubits); static std::size_t - findCouplingLimit(const CouplingMap& cm, std::uint16_t nQubits, - const std::set& qubitChoice); - static void - findCouplingLimit(std::uint16_t node, std::uint16_t curSum, - const std::vector>& connections, - std::vector& d, std::vector& visited); + findCouplingLimit(const CouplingMap& cm, std::uint16_t nQubits, + const std::set& qubitChoice); + static void findCouplingLimit( + std::uint16_t node, std::uint16_t curSum, + const std::vector>& connections, + std::vector& d, std::vector& visited); }; diff --git a/include/Mapper.hpp b/include/Mapper.hpp index 107f30dc1..a0b24c78d 100644 --- a/include/Mapper.hpp +++ b/include/Mapper.hpp @@ -157,13 +157,33 @@ class Mapper { countGates(circuit.cbegin(), circuit.cend(), info); } /** - * @brief count number of elementary gates and cnots in circuit and save the - * results in `info.gates` and `info.cnots` + * @brief count number of elementary gates and two-qubit gates in circuit and + * save the results in `info.gates` and `info.twoQubitGates` */ virtual void countGates(decltype(qcMapped.cbegin()) it, const decltype(qcMapped.cend())& end, MappingResults::CircuitInfo& info); + /** + * @brief inserts a two qubit operation op along edge. + * @param edge the edge in the coupling graph that controls on which physical + * qubits the operation will act. The logical and physical direction are the + * same. + * @param op the operation which is inserted + * @return total number of inserted operations + */ + std::size_t insertGate(const Edge& edge, const qc::Operation& op); + + /** + * @brief inserts a two qubit operation op along a direction reversed edge. + * @param edge the edge in the coupling graph that controls on which physical + * qubits the operation will act. The logical direction of the operation is + * the reverse of the physical direction. + * @param op the operation which is inserted + * @return total number of inserted operations + */ + std::size_t insertReversedGate(const Edge& edge, const qc::Operation& op); + /** * @brief performs optimizations on the circuit before mapping * @@ -234,6 +254,9 @@ class Mapper { virtual MappingResults& getResults() { return results; } + const qc::QuantumComputation& getMappedCircuit() const { return qcMapped; } + qc::QuantumComputation& getMappedCircuit() { return qcMapped; } + virtual nlohmann::json json() { return results.json(); } virtual std::string csv() { return results.csv(); } diff --git a/include/MappingResults.hpp b/include/MappingResults.hpp index c869f2461..24e87d849 100644 --- a/include/MappingResults.hpp +++ b/include/MappingResults.hpp @@ -19,7 +19,7 @@ struct MappingResults { std::uint16_t qubits = 0; std::size_t gates = 0; std::size_t singleQubitGates = 0; - std::size_t cnots = 0; + std::size_t twoQubitGates = 0; std::size_t layers = 0; // info in output circuit @@ -62,14 +62,14 @@ struct MappingResults { circuit["qubits"] = input.qubits; circuit["gates"] = input.gates; circuit["single_qubit_gates"] = input.singleQubitGates; - circuit["cnots"] = input.cnots; + circuit["cnots"] = input.twoQubitGates; auto& mappedCirc = resultJSON["mapped_circuit"]; mappedCirc["name"] = output.name; mappedCirc["qubits"] = output.qubits; mappedCirc["gates"] = output.gates; mappedCirc["single_qubit_gates"] = output.singleQubitGates; - mappedCirc["cnots"] = output.cnots; + mappedCirc["cnots"] = output.twoQubitGates; if (!mappedCircuit.empty()) { mappedCirc["qasm"] = mappedCircuit; } @@ -100,11 +100,11 @@ struct MappingResults { virtual std::string csv() { std::stringstream ss{}; ss << input.name << ";" << input.qubits << ";" << input.gates << ";" - << input.singleQubitGates << ";" << input.cnots << ";" << architecture - << ";" << output.name << ";" << output.qubits << ";" << output.gates - << ";" << output.singleQubitGates << ";" << output.cnots << ";" - << output.swaps << ";" << output.directionReverse << ";" - << output.teleportations << ";"; + << input.singleQubitGates << ";" << input.twoQubitGates << ";" + << architecture << ";" << output.name << ";" << output.qubits << ";" + << output.gates << ";" << output.singleQubitGates << ";" + << output.twoQubitGates << ";" << output.swaps << ";" + << output.directionReverse << ";" << output.teleportations << ";"; if (timeout) { ss << "TO"; } else { diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index da3ce8aff..b6a45ac2a 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -179,19 +179,28 @@ class HeuristicMapper : public Mapper { continue; } - auto cost = arch.distance( - static_cast( - locations.at(static_cast(gate.control))), - static_cast(locations.at(gate.target))); + auto cost = + arch.distance(static_cast(locations.at( + static_cast(gate.control))), + static_cast(locations.at(gate.target)), + gate.op->getType()); auto fidelityCost = cost; if (admissibleHeuristic) { costHeur = std::max(costHeur, fidelityCost); } else { costHeur += fidelityCost; } - if (cost > COST_DIRECTION_REVERSE) { - done = false; - return; + if (Architecture::supportsDirectionReversal(gate.op->getType())) { + if (cost > + Architecture::computeCostDirectionReverse(gate.op->getType())) { + done = false; + return; + } + } else { + if (cost > COST_UNIDIRECTIONAL_SWAP * 2) { + done = false; + return; + } } } } @@ -230,20 +239,22 @@ class HeuristicMapper : public Mapper { * @brief returns distance of the given logical qubit pair according to the * current mapping */ - double distanceOnArchitectureOfLogicalQubits(std::uint16_t control, - std::uint16_t target) { + double distanceOnArchitectureOfLogicalQubits(std::uint16_t control, + std::uint16_t target, + const qc::OpType opType) { return architecture.distance( static_cast(locations.at(control)), - static_cast(locations.at(target))); + static_cast(locations.at(target)), opType); } /** * @brief returns distance of the given physical qubit pair on the * architecture */ - double distanceOnArchitectureOfPhysicalQubits(std::uint16_t control, - std::uint16_t target) { - return architecture.distance(control, target); + double distanceOnArchitectureOfPhysicalQubits(std::uint16_t control, + std::uint16_t target, + const qc::OpType opType) { + return architecture.distance(control, target, opType); } /** @@ -254,7 +265,8 @@ class HeuristicMapper : public Mapper { * to `target` * @param target an unmapped logical qubit */ - virtual void mapToMinDistance(std::uint16_t source, std::uint16_t target); + virtual void mapToMinDistance(std::uint16_t source, std::uint16_t target, + const qc::OpType opType); /** * @brief gathers all qubits that are acted on by a 2-qubit-gate in the given diff --git a/include/utils.hpp b/include/utils.hpp index 780e937db..526b2d01c 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -20,10 +20,11 @@ #include #include -using Matrix = std::vector>; -using Edge = std::pair; -using CouplingMap = std::set; -using QubitSubset = std::set; +using Matrix = std::vector>; +using DistanceTable = std::vector>>; +using Edge = std::pair; +using CouplingMap = std::set; +using QubitSubset = std::set; struct Exchange { Exchange(const std::uint16_t f, const std::uint16_t s, const qc::OpType type) @@ -60,9 +61,10 @@ class Dijkstra { double cost = -1.; }; - static void buildTable(std::uint16_t n, const CouplingMap& couplingMap, - Matrix& distanceTable, - const std::function& cost); + static void + buildTable(std::uint16_t n, const CouplingMap& couplingMap, + DistanceTable& distanceTable, + const std::function(const Node&)>& cost); protected: static void dijkstra(const CouplingMap& couplingMap, std::vector& nodes, diff --git a/mqt/qmap/bindings.cpp b/mqt/qmap/bindings.cpp index d2cb80cf0..7f552dc22 100644 --- a/mqt/qmap/bindings.cpp +++ b/mqt/qmap/bindings.cpp @@ -241,7 +241,8 @@ PYBIND11_MODULE(pyqmap, m) { .def_readwrite("gates", &MappingResults::CircuitInfo::gates) .def_readwrite("single_qubit_gates", &MappingResults::CircuitInfo::singleQubitGates) - .def_readwrite("cnots", &MappingResults::CircuitInfo::cnots) + .def_readwrite("two_qubit_gates", + &MappingResults::CircuitInfo::twoQubitGates) .def_readwrite("layers", &MappingResults::CircuitInfo::layers) .def_readwrite("swaps", &MappingResults::CircuitInfo::swaps) .def_readwrite("direction_reverse", diff --git a/pyproject.toml b/pyproject.toml index df236f7e0..590a1a16c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,8 +107,7 @@ log_cli_level = "INFO" xfail_strict = true filterwarnings = [ "error", - # See https://github.com/Qiskit/rustworkx/pull/728 - 'ignore:RetworkxLoader.exec_module\(\) not found; falling back to load_module\(\):ImportWarning', + 'ignore:__package__ != __spec__.parent:ImportWarning', ] [tool.coverage.run] source = ["mqt.qmap"] diff --git a/src/Architecture.cpp b/src/Architecture.cpp index 4b352ba6d..2c60344ec 100644 --- a/src/Architecture.cpp +++ b/src/Architecture.cpp @@ -486,13 +486,15 @@ std::uint64_t Architecture::bfs(const std::uint16_t start, std::size_t Architecture::findCouplingLimit(const CouplingMap& cm, const std::uint16_t nQubits) { - std::vector> connections; - std::vector d; - std::vector visited; + std::vector> connections; + std::vector d; + std::vector visited; connections.resize(nQubits); std::uint16_t maxSum = 0; for (const auto& edge : cm) { - connections.at(edge.first).emplace_back(edge.second); + connections.at(edge.first).emplace(edge.second); + // make sure that the connections are bidirectional + connections.at(edge.second).emplace(edge.first); } for (std::uint16_t q = 0; q < nQubits; ++q) { d.clear(); @@ -513,15 +515,17 @@ std::size_t Architecture::findCouplingLimit(const CouplingMap& cm, std::size_t Architecture::findCouplingLimit(const CouplingMap& cm, const std::uint16_t nQubits, const QubitSubset& qubitChoice) { - std::vector> connections; - std::vector d; - std::vector visited; + std::vector> connections; + std::vector d; + std::vector visited; connections.resize(nQubits); std::uint16_t maxSum = 0; for (const auto& edge : cm) { if ((qubitChoice.count(edge.first) != 0U) && (qubitChoice.count(edge.second) != 0U)) { - connections.at(edge.first).emplace_back(edge.second); + connections.at(edge.first).emplace(edge.second); + // make sure that the connections are bidirectional + connections.at(edge.second).emplace(edge.first); } } for (std::uint16_t q = 0; q < nQubits; ++q) { @@ -545,7 +549,7 @@ std::size_t Architecture::findCouplingLimit(const CouplingMap& cm, void Architecture::findCouplingLimit( const std::uint16_t node, const std::uint16_t curSum, - const std::vector>& connections, + const std::vector>& connections, std::vector& d, std::vector& visited) { if (visited.at(node)) { return; @@ -687,3 +691,57 @@ void Architecture::printCouplingMap(const CouplingMap& cm, std::ostream& os) { } os << "}" << std::endl; } + +DirectionReversalStrategy +Architecture::getDirectionReversalStrategy(const qc::OpType opType) { + switch (opType) { + case qc::X: + case qc::SX: + case qc::SXdag: + return DirectionReversalStrategy::Hadamard; + case qc::Z: + case qc::S: + case qc::Sdag: + case qc::T: + case qc::Tdag: + case qc::Phase: + case qc::RZ: + case qc::SWAP: + return DirectionReversalStrategy::Identity; + default: + return DirectionReversalStrategy::NotApplicable; + } +} + +std::uint32_t +Architecture::computeCostDirectionReverse(const qc::OpType opType) { + switch (Architecture::getDirectionReversalStrategy(opType)) { + case DirectionReversalStrategy::Identity: + return 0; + case DirectionReversalStrategy::Hadamard: + return 4 * COST_SINGLE_QUBIT_GATE; + default: + throw QMAPException( + "Cannot compute cost for direction reversal, because gate " + + toString(opType) + " does not support reversal!"); + } +} +std::uint32_t +Architecture::computeGatesDirectionReverse(const qc::OpType opType) { + switch (Architecture::getDirectionReversalStrategy(opType)) { + case DirectionReversalStrategy::Identity: + return 0; + case DirectionReversalStrategy::Hadamard: + return 4; + default: + throw QMAPException( + "Cannot compute gates for direction reversal, because gate " + + toString(opType) + " does not support reversal!"); + } +} + +bool Architecture::supportsDirectionReversal(const qc::OpType opType) { + const auto strategy = getDirectionReversalStrategy(opType); + return strategy == DirectionReversalStrategy::Identity || + strategy == DirectionReversalStrategy::Hadamard; +} diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 37495b53e..dc88edf84 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -301,18 +301,18 @@ void Mapper::countGates(decltype(qcMapped.cbegin()) it, if (g->getType() == qc::SWAP) { if (architecture.bidirectional()) { info.gates += GATES_OF_BIDIRECTIONAL_SWAP; - info.cnots += GATES_OF_BIDIRECTIONAL_SWAP; + info.twoQubitGates += GATES_OF_BIDIRECTIONAL_SWAP; } else { info.gates += GATES_OF_UNIDIRECTIONAL_SWAP; - info.cnots += GATES_OF_BIDIRECTIONAL_SWAP; - info.singleQubitGates += GATES_OF_DIRECTION_REVERSE; + info.twoQubitGates += GATES_OF_BIDIRECTIONAL_SWAP; + info.singleQubitGates += + Architecture::computeGatesDirectionReverse(g->getType()); } } else if (g->getControls().empty()) { ++info.singleQubitGates; ++info.gates; } else { - assert(g->getType() == qc::X); - ++info.cnots; + ++info.twoQubitGates; ++info.gates; } continue; @@ -324,3 +324,31 @@ void Mapper::countGates(decltype(qcMapped.cbegin()) it, } } } +std::size_t Mapper::insertGate(const Edge& edge, const qc::Operation& op) { + auto newOp = op.clone(); + newOp->setControls({qc::Control{edge.first}}); + newOp->setTargets({edge.second}); + qcMapped.emplace_back(newOp); + return 1; +} + +std::size_t Mapper::insertReversedGate(const Edge& edge, + const qc::Operation& op) { + switch (Architecture::getDirectionReversalStrategy(op.getType())) { + case DirectionReversalStrategy::Identity: + // no additional gates required to reverse direction + return insertGate(edge, op); + case DirectionReversalStrategy::Hadamard: { + // four hadamard gates to reverse direction + qcMapped.h(edge.first); + qcMapped.h(edge.second); + const auto nGates = insertGate(edge, op); + qcMapped.h(edge.first); + qcMapped.h(edge.second); + return nGates + 4; + } + default: + throw QMAPException("Cannot insert reversed gate, because gate " + + toString(op.getType()) + " does not support reversal!"); + } +} diff --git a/src/exact/ExactMapper.cpp b/src/exact/ExactMapper.cpp index 3896e6705..717ea54e3 100644 --- a/src/exact/ExactMapper.cpp +++ b/src/exact/ExactMapper.cpp @@ -103,6 +103,12 @@ void ExactMapper::map(const Configuration& settings) { } else { maxLimit = architecture.getCouplingLimit() - 1U; } + if (!architecture.bidirectional()) { + // on a directed architecture, one more SWAP might be needed overall + // due to the directionality of the edges and direction reversal not + // being possible for every gate. + maxLimit += 1U; + } if (config.swapReduction == SwapReduction::CouplingLimit) { limit = maxLimit; } else if (config.swapReduction == SwapReduction::Increasing) { @@ -275,38 +281,39 @@ void ExactMapper::map(const Configuration& settings) { op->getParameter().at(0), op->getParameter().at(1), op->getParameter().at(2)); } else { - const Edge cnot = {locations.at(static_cast(gate.control)), - locations.at(gate.target)}; + const Edge controlledGate = { + locations.at(static_cast(gate.control)), + locations.at(gate.target)}; - if (architecture.getCouplingMap().find(cnot) == + if (architecture.getCouplingMap().find(controlledGate) == architecture.getCouplingMap().end()) { - const Edge reverse = {cnot.second, cnot.first}; + const Edge reverse = {controlledGate.second, controlledGate.first}; if (architecture.getCouplingMap().find(reverse) == architecture.getCouplingMap().end()) { - throw QMAPException( - "Invalid CNOT: " + std::to_string(reverse.first) + "-" + - std::to_string(reverse.second)); + throw QMAPException("Invalid controlled gate " + op->getName() + + ": " + std::to_string(reverse.first) + "-" + + std::to_string(reverse.second)); + } + if (!Architecture::supportsDirectionReversal(op->getType())) { + throw QMAPException("Invalid controlled gate " + op->getName() + + ": " + std::to_string(reverse.first) + "-" + + std::to_string(reverse.second)); } + if (settings.verbose) { - std::cout - << i - << ": Added (direction-reversed) cnot with control and target: " - << cnot.first << " " << cnot.second << std::endl; + std::cout << i << ": Added (direction-reversed) controlled gate " + << op->getName() + << " with control and target: " << controlledGate.first + << " " << controlledGate.second << std::endl; } - qcMapped.h(reverse.first); - qcMapped.h(reverse.second); - qcMapped.x(reverse.second, - qc::Control{static_cast(reverse.first)}); - qcMapped.h(reverse.second); - qcMapped.h(reverse.first); + insertReversedGate(reverse, *op); } else { if (settings.verbose) { - std::cout << i - << ": Added cnot with control and target: " << cnot.first - << " " << cnot.second << std::endl; + std::cout << i << ": Added controlled gate " << op->getName() + << " with control and target: " << controlledGate.first + << " " << controlledGate.second << std::endl; } - qcMapped.x(cnot.second, - qc::Control{static_cast(cnot.first)}); + insertGate(controlledGate, *op); } } } @@ -354,7 +361,7 @@ void ExactMapper::map(const Configuration& settings) { // 10) re-count gates results.output.singleQubitGates = 0U; - results.output.cnots = 0U; + results.output.twoQubitGates = 0U; results.output.gates = 0U; countGates(qcMapped, results.output); @@ -588,7 +595,8 @@ number of variables: (|L|-1) * m! } auto coupling = LogicTerm(false); - if (architecture.bidirectional()) { + if (architecture.bidirectional() || + !Architecture::supportsDirectionReversal(gate.op->getType())) { for (const auto& edge : rcm) { auto indexFC = x[k][physicalQubitIndex[edge.first]] [static_cast(gate.control)]; @@ -603,7 +611,6 @@ number of variables: (|L|-1) * m! auto indexFT = x[k][physicalQubitIndex[edge.first]][gate.target]; auto indexSC = x[k][physicalQubitIndex[edge.second]] [static_cast(gate.control)]; - coupling = coupling || ((indexFC && indexST) || (indexFT && indexSC)); } } @@ -731,6 +738,9 @@ number of variables: (|L|-1) * m! if (gate.singleQubit()) { continue; } + if (!Architecture::supportsDirectionReversal(gate.op->getType())) { + continue; + } auto reverse = LogicTerm(false); for (const auto& [q0, q1] : rcm) { @@ -739,9 +749,12 @@ number of variables: (|L|-1) * m! [static_cast(gate.control)]; reverse = reverse || (indexFT && indexSC); } - cost = cost + LogicTerm::ite(reverse, - LogicTerm(::GATES_OF_DIRECTION_REVERSE), - LogicTerm(0)); + cost = cost + + LogicTerm::ite(reverse, + LogicTerm(static_cast( + Architecture::computeGatesDirectionReverse( + gate.op->getType()))), + LogicTerm(0)); } } lb->minimize(cost); @@ -763,9 +776,9 @@ number of variables: (|L|-1) * m! // quickly determine cost choiceResults.output.singleQubitGates = choiceResults.input.singleQubitGates; - choiceResults.output.cnots = choiceResults.input.cnots; - choiceResults.output.gates = - choiceResults.output.singleQubitGates + choiceResults.output.cnots; + choiceResults.output.twoQubitGates = choiceResults.input.twoQubitGates; + choiceResults.output.gates = choiceResults.output.singleQubitGates + + choiceResults.output.twoQubitGates; assert(choiceResults.output.swaps == 0U); assert(choiceResults.output.directionReverse == 0U); // swaps @@ -835,6 +848,9 @@ number of variables: (|L|-1) * m! if (gate.singleQubit()) { continue; } + if (!Architecture::supportsDirectionReversal(gate.op->getType())) { + continue; + } for (const auto& edge : rcm) { auto indexFT = x[k][physicalQubitIndex[edge.first]][gate.target]; auto indexSC = x[k][physicalQubitIndex[edge.second]] @@ -842,7 +858,9 @@ number of variables: (|L|-1) * m! if (m->getBoolValue(indexFT, lb.get()) && m->getBoolValue(indexSC, lb.get())) { choiceResults.output.directionReverse++; - choiceResults.output.gates += GATES_OF_DIRECTION_REVERSE; + choiceResults.output.gates += + Architecture::computeGatesDirectionReverse( + gate.op->getType()); } } } diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 4a107a577..e9009a371 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -94,31 +94,22 @@ void HeuristicMapper::map(const Configuration& configuration) { gateidx++; } } else { - const Edge cnot = { + const Edge controlledGate = { locations.at(static_cast(gate.control)), locations.at(gate.target)}; - if (architecture.getCouplingMap().find(cnot) == + if (architecture.getCouplingMap().find(controlledGate) == architecture.getCouplingMap().end()) { - const Edge reverse = {cnot.second, cnot.first}; + const Edge reverse = {controlledGate.second, controlledGate.first}; if (architecture.getCouplingMap().find(reverse) == architecture.getCouplingMap().end()) { - throw QMAPException( - "Invalid CNOT: " + std::to_string(reverse.first) + "-" + - std::to_string(reverse.second)); + throw QMAPException("Invalid controlled gate " + op->getName() + + ": " + std::to_string(reverse.first) + "-" + + std::to_string(reverse.second)); } - qcMapped.h(reverse.first); - qcMapped.h(reverse.second); - qcMapped.x(reverse.second, - qc::Control{static_cast(reverse.first)}); - qcMapped.h(reverse.second); - qcMapped.h(reverse.first); - + gateidx += insertReversedGate(reverse, *op); results.output.directionReverse++; - gateidx += 5; } else { - qcMapped.x(cnot.second, - qc::Control{static_cast(cnot.first)}); - gateidx++; + gateidx += insertGate(controlledGate, *op); } } } @@ -336,7 +327,8 @@ void HeuristicMapper::mapUnmappedGates( for (std::uint16_t j = i + 1; j < architecture.getNqubits(); j++) { if (qubits.at(i) == DEFAULT_POSITION && qubits.at(j) == DEFAULT_POSITION) { - const double dist = architecture.distance(i, j); + const double dist = + architecture.distance(i, j, gate.op->getType()); if (dist < bestScore) { bestScore = dist; chosenEdge = std::make_pair(i, j); @@ -367,22 +359,25 @@ void HeuristicMapper::mapUnmappedGates( chosenEdge.second, qcMapped.outputPermutation); } else if (controlLocation == DEFAULT_POSITION) { - mapToMinDistance(gate.target, static_cast(gate.control)); + mapToMinDistance(gate.target, static_cast(gate.control), + gate.op->getType()); } else if (targetLocation == DEFAULT_POSITION) { - mapToMinDistance(static_cast(gate.control), gate.target); + mapToMinDistance(static_cast(gate.control), gate.target, + gate.op->getType()); } } } void HeuristicMapper::mapToMinDistance(const std::uint16_t source, - const std::uint16_t target) { + const std::uint16_t target, + const qc::OpType opType) { auto min = std::numeric_limits::max(); std::optional pos = std::nullopt; for (std::uint16_t i = 0; i < architecture.getNqubits(); ++i) { if (qubits.at(i) == DEFAULT_POSITION) { // TODO: Consider fidelity here if available auto distance = distanceOnArchitectureOfPhysicalQubits( - static_cast(locations.at(source)), i); + static_cast(locations.at(source)), i, opType); if (distance < min) { min = distance; pos = i; @@ -566,8 +561,10 @@ void HeuristicMapper::lookahead(const std::size_t layer, if (node.qubits.at(j) == DEFAULT_POSITION) { // TODO: Consider fidelity here if available min = std::min(min, distanceOnArchitectureOfPhysicalQubits( - j, static_cast( - node.locations.at(gate.target)))); + j, + static_cast( + node.locations.at(gate.target)), + gate.op->getType())); } } penalty = heuristicAddition(penalty, min); @@ -580,7 +577,7 @@ void HeuristicMapper::lookahead(const std::size_t layer, distanceOnArchitectureOfPhysicalQubits( static_cast(node.locations.at( static_cast(gate.control))), - j)); + j, gate.op->getType())); } } penalty = heuristicAddition(penalty, min); @@ -588,7 +585,8 @@ void HeuristicMapper::lookahead(const std::size_t layer, auto cost = architecture.distance( static_cast( node.locations.at(static_cast(gate.control))), - static_cast(node.locations.at(gate.target))); + static_cast(node.locations.at(gate.target)), + gate.op->getType()); penalty = heuristicAddition(penalty, cost); } } diff --git a/src/utils.cpp b/src/utils.cpp index d881d3fb6..90b20895b 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -7,11 +7,13 @@ #include -void Dijkstra::buildTable(const std::uint16_t n, const CouplingMap& couplingMap, - Matrix& distanceTable, - const std::function& cost) { +void Dijkstra::buildTable( + const std::uint16_t n, const CouplingMap& couplingMap, + DistanceTable& distanceTable, + const std::function(const Node&)>& cost) { distanceTable.clear(); - distanceTable.resize(n, std::vector(n, -1.)); + distanceTable.resize(n, std::vector>( + n, std::pair(-1., false))); for (std::uint16_t i = 0; i < n; ++i) { std::vector nodes(n); @@ -28,7 +30,7 @@ void Dijkstra::buildTable(const std::uint16_t n, const CouplingMap& couplingMap, for (std::uint16_t j = 0; j < n; ++j) { if (i == j) { - distanceTable.at(i).at(j) = 0; + distanceTable.at(i).at(j) = std::pair(0, false); } else { distanceTable.at(i).at(j) = cost(nodes.at(j)); } diff --git a/test/python/test_exact_mapper.py b/test/python/test_exact_mapper.py index da9a8901e..52a3d7cb5 100644 --- a/test/python/test_exact_mapper.py +++ b/test/python/test_exact_mapper.py @@ -1,8 +1,17 @@ """Test the exact mapper.""" +from __future__ import annotations +from typing import TYPE_CHECKING + +import numpy as np +import pytest +import qiskit.circuit.library as qcl from qiskit import QuantumCircuit from qiskit.providers.fake_provider import FakeLondon +if TYPE_CHECKING: + from qiskit.circuit import ControlledGate + from mqt import qmap from mqt.qcec import verify @@ -60,3 +69,86 @@ def test_exact_non_trivial_swaps() -> None: result = verify(qc, qc_mapped) assert result.considered_equivalent() is True + + +@pytest.fixture() +def one_way_arch() -> qmap.Architecture: + """Return a simple one way architecture to test direction reversal.""" + return qmap.Architecture( + 2, + { + (0, 1), + }, + ) + + +@pytest.mark.parametrize( + "gate", + [qcl.CXGate(), qcl.CSXGate()], +) +def test_direction_reverse_hadamard(one_way_arch: qmap.Architecture, gate: ControlledGate) -> None: + """Verify that control and target are flipped using four hadamard gates for some gates where this is possible.""" + qc = QuantumCircuit(2) + qc.append(gate, [0, 1]) + qc.append(gate, [1, 0]) + qc.measure_all() + + qc_mapped, results = qmap.compile(qc, arch=one_way_arch, method="exact") + assert qc_mapped.count_ops()["h"] == 4 + assert "swap" not in qc_mapped.count_ops() + + result = verify(qc, qc_mapped) + assert result.considered_equivalent() is True + + +@pytest.mark.parametrize( + "gate", + [ + qcl.CYGate(), + qcl.CRXGate(0.5), + qcl.CRYGate(0.5), + qcl.CHGate(), + qcl.CU3Gate(0.25, 0.5, 0.75), + ], +) +def test_direction_reverse_swap(one_way_arch: qmap.Architecture, gate: ControlledGate) -> None: + """Verify that control and target are flipped using a swap permutation.""" + qc = QuantumCircuit(2) + qc.append(gate, [0, 1]) + qc.append(gate, [1, 0]) + qc.measure_all() + + qc_mapped, results = qmap.compile(qc, arch=one_way_arch, method="exact") + assert "h" not in qc_mapped.count_ops() + assert qc_mapped.count_ops()["swap"] == 1 + + result = verify(qc, qc_mapped) + assert result.considered_equivalent() is True + + +@pytest.mark.parametrize( + "gate", + [ + qcl.CZGate(), + qcl.CPhaseGate(0.5), + qcl.CRZGate(0.5), + qcl.CPhaseGate(np.pi / 2), + qcl.CPhaseGate(-np.pi / 2), + qcl.CPhaseGate(np.pi / 4), + qcl.CPhaseGate(-np.pi / 4), + qcl.CU1Gate(0.25), + ], +) +def test_direction_reverse_identity(one_way_arch: qmap.Architecture, gate: ControlledGate) -> None: + """Verify that control and target are flipped without adding gates for some gates where this is possible.""" + qc = QuantumCircuit(2) + qc.append(gate, [0, 1]) + qc.append(gate, [1, 0]) + qc.measure_all() + + qc_mapped, results = qmap.compile(qc, arch=one_way_arch, method="exact") + assert "h" not in qc_mapped.count_ops() + assert "swap" not in qc_mapped.count_ops() + + result = verify(qc, qc_mapped) + assert result.considered_equivalent() is True diff --git a/test/test_exact.cpp b/test/test_exact.cpp index 45133f408..79f4f5c90 100644 --- a/test/test_exact.cpp +++ b/test/test_exact.cpp @@ -556,3 +556,75 @@ TEST_F(ExactTest, Test) { EXPECT_EQ(mapper.getResults().output.swaps, 0); EXPECT_EQ(mapper.getResults().output.directionReverse, 2); } + +TEST_F(ExactTest, TestDirectionReverseHadamard) { + using namespace qc::literals; + + Architecture arch; + const CouplingMap cm = {{0, 1}}; + arch.loadCouplingMap(2, cm); + + Architecture::printCouplingMap(cm, std::cout); + + qc = qc::QuantumComputation(2); + qc.sx(0, 1_pc); + qc.sx(1, 0_pc); + + auto mapper = ExactMapper(qc, arch); + mapper.map(settings); + + const auto& results = mapper.getResults(); + EXPECT_EQ(results.output.swaps, 0); + EXPECT_EQ(results.output.directionReverse, 1); + const auto& qcMapped = mapper.getMappedCircuit(); + EXPECT_EQ(qcMapped.getNops(), 6U + 1U + 2U); + std::cout << qcMapped << std::endl; +} + +TEST_F(ExactTest, TestDirectionReverseIdentity) { + using namespace qc::literals; + + Architecture arch; + const CouplingMap cm = {{0, 1}}; + arch.loadCouplingMap(2, cm); + + Architecture::printCouplingMap(cm, std::cout); + + qc = qc::QuantumComputation(2); + qc.rz(0, 1_pc, 0.25); + qc.rz(1, 0_pc, 0.75); + + auto mapper = ExactMapper(qc, arch); + mapper.map(settings); + + const auto& results = mapper.getResults(); + EXPECT_EQ(results.output.swaps, 0); + EXPECT_EQ(results.output.directionReverse, 1); + const auto& qcMapped = mapper.getMappedCircuit(); + EXPECT_EQ(qcMapped.getNops(), 2U + 1U + 2U); + std::cout << qcMapped << std::endl; +} + +TEST_F(ExactTest, TestDirectionReverseNotApplicable) { + using namespace qc::literals; + + Architecture arch; + const CouplingMap cm = {{0, 1}}; + arch.loadCouplingMap(2, cm); + + Architecture::printCouplingMap(cm, std::cout); + + qc = qc::QuantumComputation(2); + qc.y(0, 1_pc); + qc.y(1, 0_pc); + + auto mapper = ExactMapper(qc, arch); + mapper.map(settings); + + const auto& results = mapper.getResults(); + EXPECT_EQ(results.output.swaps, 1); + EXPECT_EQ(results.output.directionReverse, 0); + const auto& qcMapped = mapper.getMappedCircuit(); + EXPECT_EQ(qcMapped.getNops(), 3U + 1U + 2U); + std::cout << qcMapped << std::endl; +} diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 84ece675e..04d7de82d 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -250,3 +250,87 @@ TEST_P(HeuristicTest20QTeleport, Teleportation) { tokyoMapper->printResult(std::cout); SUCCEED() << "Mapping successful"; } + +class DirectionReverseTest : public testing::TestWithParam { +protected: + qc::QuantumComputation qc{}; + Configuration settings{}; + + void SetUp() override { + using namespace qc::literals; + settings.verbose = true; + settings.method = Method::Heuristic; + } +}; + +TEST_F(DirectionReverseTest, TestDirectionReverseHadamard) { + using namespace qc::literals; + + Architecture arch; + const CouplingMap cm = {{0, 1}}; + arch.loadCouplingMap(2, cm); + + Architecture::printCouplingMap(cm, std::cout); + + qc = qc::QuantumComputation(2); + qc.sx(0, 1_pc); + qc.sx(1, 0_pc); + + auto mapper = HeuristicMapper(qc, arch); + mapper.map(settings); + + const auto& results = mapper.getResults(); + EXPECT_EQ(results.output.swaps, 0); + EXPECT_EQ(results.output.directionReverse, 1); + const auto& qcMapped = mapper.getMappedCircuit(); + EXPECT_EQ(qcMapped.getNops(), 6U + 1U + 2U); + std::cout << qcMapped << std::endl; +} + +TEST_F(DirectionReverseTest, TestDirectionReverseIdentity) { + using namespace qc::literals; + + Architecture arch; + const CouplingMap cm = {{0, 1}}; + arch.loadCouplingMap(2, cm); + + Architecture::printCouplingMap(cm, std::cout); + + qc = qc::QuantumComputation(2); + qc.rz(0, 1_pc, 0.25); + qc.rz(1, 0_pc, 0.75); + + auto mapper = HeuristicMapper(qc, arch); + mapper.map(settings); + + const auto& results = mapper.getResults(); + EXPECT_EQ(results.output.swaps, 0); + EXPECT_EQ(results.output.directionReverse, 1); + const auto& qcMapped = mapper.getMappedCircuit(); + EXPECT_EQ(qcMapped.getNops(), 2U + 1U + 2U); + std::cout << qcMapped << std::endl; +} + +TEST_F(DirectionReverseTest, TestDirectionReverseNotApplicable) { + using namespace qc::literals; + + Architecture arch; + const CouplingMap cm = {{0, 1}}; + arch.loadCouplingMap(2, cm); + + Architecture::printCouplingMap(cm, std::cout); + + qc = qc::QuantumComputation(2); + qc.y(0, 1_pc); + qc.y(1, 0_pc); + + auto mapper = HeuristicMapper(qc, arch); + mapper.map(settings); + + const auto& results = mapper.getResults(); + EXPECT_EQ(results.output.swaps, 1); + EXPECT_EQ(results.output.directionReverse, 0); + const auto& qcMapped = mapper.getMappedCircuit(); + EXPECT_EQ(qcMapped.getNops(), 3U + 1U + 2U); + std::cout << qcMapped << std::endl; +}