diff --git a/include/Mapper.hpp b/include/Mapper.hpp index 109c7781a..9b0867c32 100644 --- a/include/Mapper.hpp +++ b/include/Mapper.hpp @@ -115,6 +115,21 @@ class Mapper { */ std::vector> activeQubits2QGates{}; + /** + * @brief upper limit for activeQubits in each layer; 0 is equal to no limit + */ + std::size_t maximumActiveQubits = 0; + /** + * @brief upper limit for activeQubits1QGates in each layer; 0 is equal to no + * limit + */ + std::size_t maximumActiveQubits1QGates = 0; + /** + * @brief upper limit for activeQubits2QGates in each layer; 0 is equal to no + * limit + */ + std::size_t maximumActiveQubits2QGates = 0; + /** * @brief containing the logical qubit currently mapped to each physical * qubit. `qubits[physical_qubit] = logical_qubit` @@ -187,14 +202,12 @@ class Mapper { * @param lastLayer the array storing the last layer each qubit is used in * @param control the (potential) control qubit of the gate * @param target the target qubit of the gate - * @param gate the gate to be added to the layer - * @param collect2qBlocks if true, gates are collected in 2Q-blocks, and - * layering is performed on these blocks + * + * @return the index of the first layer the gate can be added to */ - void processDisjointQubitLayer( + std::size_t processDisjointQubitLayer( std::array, MAX_DEVICE_QUBITS>& lastLayer, - const std::optional& control, std::uint16_t target, - qc::Operation* gate); + const std::optional& control, std::uint16_t target); /** * Similar to processDisjointQubitLayer, but instead of treating each gate @@ -204,10 +217,29 @@ class Mapper { * @param lastLayer the array storing the last layer each qubit is used in * @param control the (potential) control qubit of the gate * @param target the target qubit of the gate - * @param gate the gate to be added to the layer + * + * @return the index of the first layer the gate can be added to + */ + std::size_t processDisjoint2qBlockLayer( + std::array, MAX_DEVICE_QUBITS>& lastLayer, + const std::optional& control, std::uint16_t target); + + /** + * Equivalent to processDisjoint2qBlockLayer, but only collecting blocks of + * the same gate type (e.g. only CNOTs or only X gates), i.e. different gate + * types on the same qubits are separated to different layers + * + * @param lastLayer the array storing the last layer each qubit is used in + * @param lastLayerOpType the array storing the last gate type of each qubit + * @param control the (potential) control qubit of the gate + * @param target the target qubit of the gate + * @param gate the operation of the gate to be added to the circuit + * + * @return the index of the first layer the gate can be added to */ - void processDisjoint2qBlockLayer( + std::size_t processDisjointSameOpTypeBlockLayer( std::array, MAX_DEVICE_QUBITS>& lastLayer, + std::array& lastLayerOpType, const std::optional& control, std::uint16_t target, qc::Operation* gate); diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 3c7908c2b..525c6e185 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -41,7 +41,10 @@ struct Configuration { std::set subgraph{}; // how to cluster the gates into layers - Layering layering = Layering::IndividualGates; + Layering layering = Layering::IndividualGates; + std::size_t maximumActiveQubits = 0; + std::size_t maximumActiveQubits1QGates = 0; + std::size_t maximumActiveQubits2QGates = 0; // initial layout to use for heuristic approach InitialLayout initialLayout = InitialLayout::Dynamic; diff --git a/include/configuration/Heuristic.hpp b/include/configuration/Heuristic.hpp index 7d6f54c60..c1b4f9f0e 100644 --- a/include/configuration/Heuristic.hpp +++ b/include/configuration/Heuristic.hpp @@ -118,6 +118,21 @@ isPrincipallyAdmissible(const Heuristic heuristic) { return false; } +/** + * If the heuristic tracks its currently intended target location for each + * logical qubit in `HeuristicMapper::Node::targetLocations`, which is then + * used by some lookahead heuristics + */ +[[maybe_unused]] static inline bool +tracksTargetLocations(const Heuristic heuristic) { + switch (heuristic) { + case Heuristic::FidelityBestLocation: + return true; + default: + return false; + } +} + [[maybe_unused]] static inline std::string toString(const Heuristic heuristic) { switch (heuristic) { case Heuristic::GateCountMaxDistance: diff --git a/include/configuration/InitialLayout.hpp b/include/configuration/InitialLayout.hpp index 4778a8359..527482a74 100644 --- a/include/configuration/InitialLayout.hpp +++ b/include/configuration/InitialLayout.hpp @@ -7,10 +7,25 @@ #include -/// Identity: q_i -> Q_i -/// Static: first layer is mapped q_c -> Q_c and q_t -> Q_t +/// +/// Static: +/// Dynamic: /// Dynamic: Layout is generated on demand upon encountering a specific gate -enum class InitialLayout { Identity, Static, Dynamic }; +enum class InitialLayout { + /** q_i -> Q_i */ + Identity, + /** first layer is mapped q_c -> Q_c and q_t -> Q_t */ + Static, + /** Layout is generated on demand upon encountering a specific gate using a + * bespoke greedy heuristic strategy minimizing the distance between 2Q gates + * in the current layer */ + Dynamic, + /** Layout is generated on demand upon encountering a specific gate mapping + * each logical qubit greedily to the one free physical qubit, that results + * in the lowest lookahead penalty (using the same lookahead settings as for + * the routing search)*/ + DynamicGreedyLookahead +}; [[maybe_unused]] static inline std::string toString(const InitialLayout strategy) { @@ -21,6 +36,8 @@ toString(const InitialLayout strategy) { return "static"; case InitialLayout::Dynamic: return "dynamic"; + case InitialLayout::DynamicGreedyLookahead: + return "dynamic_greedy_lookahead"; } return " "; } @@ -36,5 +53,8 @@ initialLayoutFromString(const std::string& initialLayout) { if (initialLayout == "dynamic" || initialLayout == "2") { return InitialLayout::Dynamic; } + if (initialLayout == "dynamic_greedy_lookahead" || initialLayout == "3") { + return InitialLayout::DynamicGreedyLookahead; + } throw std::invalid_argument("Invalid initial layout value: " + initialLayout); } diff --git a/include/configuration/Layering.hpp b/include/configuration/Layering.hpp index e9e19adef..5d723b5e5 100644 --- a/include/configuration/Layering.hpp +++ b/include/configuration/Layering.hpp @@ -12,7 +12,8 @@ enum class Layering { DisjointQubits, OddGates, QubitTriangle, - Disjoint2qBlocks + Disjoint2qBlocks, + DisjointSameOpTypeBlocks }; [[maybe_unused]] static inline std::string toString(const Layering strategy) { @@ -27,6 +28,8 @@ enum class Layering { return "qubit_triangle"; case Layering::Disjoint2qBlocks: return "disjoint_2q_blocks"; + case Layering::DisjointSameOpTypeBlocks: + return "disjoint_same_op_type_blocks"; } return " "; } @@ -48,5 +51,8 @@ layeringFromString(const std::string& layering) { if (layering == "disjoint_2q_blocks" || layering == "4") { return Layering::Disjoint2qBlocks; } + if (layering == "disjoint_same_op_type_blocks" || layering == "5") { + return Layering::DisjointSameOpTypeBlocks; + } throw std::invalid_argument("Invalid layering value: " + layering); } diff --git a/include/configuration/LookaheadHeuristic.hpp b/include/configuration/LookaheadHeuristic.hpp index 9a2a373db..87e6362d1 100644 --- a/include/configuration/LookaheadHeuristic.hpp +++ b/include/configuration/LookaheadHeuristic.hpp @@ -15,7 +15,16 @@ enum class LookaheadHeuristic { GateCountMaxDistance, /** sum over all distances between any virtual qubit pair in the given layer; optimizing gate-count */ - GateCountSumDistance + GateCountSumDistance, + /** sums the fidelity costs of any mapped 1Q gates in the given layer and + * fidelity distances between mapped qubit pairs of 2Q gates from their + * current location; optimizing fidelity */ + FidelityCurrentLocation, + /** sums the fidelity costs of any mapped 1Q gates in the given layer and + * fidelity distances between mapped qubit pairs of 2Q gates from the + * currently intended target location (if applicable, otherwise from their + * current location); optimizing fidelity */ + FidelityCurrentTargetLocation }; /** @@ -29,10 +38,28 @@ isFidelityAware(const LookaheadHeuristic heuristic) { case LookaheadHeuristic::GateCountMaxDistance: case LookaheadHeuristic::GateCountSumDistance: return false; + case LookaheadHeuristic::FidelityCurrentLocation: + case LookaheadHeuristic::FidelityCurrentTargetLocation: + return true; } return false; } +/** + * Uses the currently intended target location for each logical qubit in + * `HeuristicMapper::Node::targetLocations` and therefore requires a heuristic, + * which tracks those. + */ +[[maybe_unused]] static inline bool +usesTargetLocations(const LookaheadHeuristic heuristic) { + switch (heuristic) { + case LookaheadHeuristic::FidelityCurrentTargetLocation: + return true; + default: + return false; + } +} + [[maybe_unused]] static inline std::string toString(const LookaheadHeuristic heuristic) { switch (heuristic) { @@ -42,6 +69,10 @@ toString(const LookaheadHeuristic heuristic) { return "gate_count_max_distance"; case LookaheadHeuristic::GateCountSumDistance: return "gate_count_sum_distance"; + case LookaheadHeuristic::FidelityCurrentLocation: + return "fidelity_current_location"; + case LookaheadHeuristic::FidelityCurrentTargetLocation: + return "fidelity_current_target_location"; } return " "; } @@ -57,6 +88,12 @@ lookaheadHeuristicFromString(const std::string& heuristic) { if (heuristic == "gate_count_sum_distance" || heuristic == "2") { return LookaheadHeuristic::GateCountSumDistance; } + if (heuristic == "fidelity_current_location" || heuristic == "3") { + return LookaheadHeuristic::FidelityCurrentLocation; + } + if (heuristic == "fidelity_current_target_location" || heuristic == "4") { + return LookaheadHeuristic::FidelityCurrentTargetLocation; + } throw std::invalid_argument("Invalid lookahead heuristic value: " + heuristic); } diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index 74d282dc9..4bc8d56c2 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -49,6 +49,16 @@ class HeuristicMapper : public Mapper { * The inverse of `qubits` */ std::array locations{}; + /** + * containing the currently intended target location (i.e. the physical + * qubit) of each logical qubit. + * `targetLocations[logical_qubit] = target_physical_qubit` + * + * multiple logical qubits can have the same target location + * + * only tracked by some heuristics (see `tracksTargetLocations()`) + */ + std::array targetLocations{}; /** current fixed cost * * non-fidelity-aware: cost of all swaps used in the node @@ -81,10 +91,12 @@ class HeuristicMapper : public Mapper { explicit Node() { qubits.fill(DEFAULT_POSITION); locations.fill(DEFAULT_POSITION); + targetLocations.fill(DEFAULT_POSITION); }; explicit Node(std::size_t nodeId) : id(nodeId) { qubits.fill(DEFAULT_POSITION); locations.fill(DEFAULT_POSITION); + targetLocations.fill(DEFAULT_POSITION); }; Node(std::size_t nodeId, std::size_t parentId, const std::array& q, @@ -99,7 +111,9 @@ class HeuristicMapper : public Mapper { locations(loc), costFixed(initCostFixed), costFixedReversals(initCostFixedReversals), sharedSwaps(initSharedSwaps), depth(searchDepth), parent(parentId), - id(nodeId) {} + id(nodeId) { + targetLocations.fill(DEFAULT_POSITION); + } /** * @brief returns costFixed + costHeur + lookaheadPenalty @@ -108,6 +122,13 @@ class HeuristicMapper : public Mapper { return costFixed + costFixedReversals + costHeur + lookaheadPenalty; } + /** + * @brief returns costFixed + costHeur + lookaheadPenalty + */ + [[nodiscard]] double getTotalCostNoLookahead() const { + return costFixed + costFixedReversals + costHeur; + } + /** * @brief returns costFixed + lookaheadPenalty */ @@ -171,13 +192,23 @@ class HeuristicMapper : public Mapper { /** * @brief maps any yet unmapped qubits, which are acted on in a given layer, - * to a physical qubit. + * to a physical qubit using a greedy heuristic strategy minimizing the + * distance between 2Q gates in the current layer. * - * @param twoQubitGateMultiplicity number of two qubit gates acting on pairs - * of logical qubits in the current layer + * @param layer the layer for which to map the qubits */ virtual void mapUnmappedGates(std::size_t layer); + /** + * @brief maps any yet unmapped logical qubits, which are acted on in a given + * layer, greedily to the one free physical qubit, that results in the lowest + * lookahead penalty (using the same lookahead settings as for the routing + * search) + * + * @param layer the layer for which to map the qubits + */ + virtual void mapUnmappedGatesLookahead(std::size_t layer); + /** * @brief Routes the input circuit, i.e. inserts SWAPs to meet topology * constraints and optimize fidelity if activated @@ -379,10 +410,11 @@ class HeuristicMapper : public Mapper { * layers (depreciated by a constant factor growing with each layer) and * saves it in the node as `Node::lookaheadPenalty` * - * @param layer index of current circuit layer + * @param nextLayer index of the layer after the current circuit layer, i.e. + * the first layer considered in the lookahead * @param node search node for which to calculate lookahead penalty */ - void updateLookaheadPenalty(std::size_t layer, Node& node); + void updateLookaheadPenalty(std::size_t nextLayer, Node& node); /** * @brief calculates the lookahead penalty for one layer using @@ -408,6 +440,30 @@ class HeuristicMapper : public Mapper { */ double lookaheadGateCountSumDistance(std::size_t layer, Node& node); + /** + * @brief calculates the lookahead penalty for one layer using + * `LookaheadHeuristic::FidelityCurrentLocation` + * + * @param layer index of the circuit layer for which to calculate the + * lookahead penalty + * @param node search node for which to calculate the heuristic cost + * + * @return lookahead penalty + */ + double lookaheadFidelityCurrentLocation(std::size_t layer, Node& node); + + /** + * @brief calculates the lookahead penalty for one layer using + * `LookaheadHeuristic::FidelityCurrentTargetLocation` + * + * @param layer index of the circuit layer for which to calculate the + * lookahead penalty + * @param node search node for which to calculate the heuristic cost + * + * @return lookahead penalty + */ + double lookaheadFidelityCurrentTargetLocation(std::size_t layer, Node& node); + static double computeEffectiveBranchingRate(std::size_t nodesProcessed, const std::size_t solutionDepth) { // N = (b*)^d + (b*)^(d-1) + ... + (b*)^2 + b* + 1 diff --git a/src/Mapper.cpp b/src/Mapper.cpp index c6026ee40..d37e3728e 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -31,10 +31,9 @@ Mapper::Mapper(qc::QuantumComputation quantumComputation, Architecture& arch) qc::CircuitOptimizer::removeFinalMeasurements(qc); } -void Mapper::processDisjointQubitLayer( +std::size_t Mapper::processDisjointQubitLayer( std::array, MAX_DEVICE_QUBITS>& lastLayer, - const std::optional& control, const std::uint16_t target, - qc::Operation* gate) { + const std::optional& control, const std::uint16_t target) { std::size_t layer = 0; if (!control.has_value()) { if (lastLayer.at(target).has_value()) { @@ -55,21 +54,12 @@ void Mapper::processDisjointQubitLayer( lastLayer.at(*control) = layer; lastLayer.at(target) = layer; } - - if (layers.size() <= layer) { - layers.emplace_back(); - } - if (control.has_value()) { - layers.at(layer).emplace_back(*control, target, gate); - } else { - layers.at(layer).emplace_back(-1, target, gate); - } + return layer; } -void Mapper::processDisjoint2qBlockLayer( +std::size_t Mapper::processDisjoint2qBlockLayer( std::array, MAX_DEVICE_QUBITS>& lastLayer, - const std::optional& control, const std::uint16_t target, - qc::Operation* gate) { + const std::optional& control, const std::uint16_t target) { std::size_t layer = 0; if (!control.has_value()) { // single qubit gates can always be added to the last 2Q block and should @@ -103,22 +93,85 @@ void Mapper::processDisjoint2qBlockLayer( lastLayer.at(*control) = layer; lastLayer.at(target) = layer; } + return layer; +} - if (layers.size() <= layer) { - layers.emplace_back(); - } - if (control.has_value()) { - layers.at(layer).emplace_back(*control, target, gate); +std::size_t Mapper::processDisjointSameOpTypeBlockLayer( + std::array, MAX_DEVICE_QUBITS>& lastLayer, + std::array& lastLayerOpType, + const std::optional& control, std::uint16_t target, + qc::Operation* gate) { + std::size_t layer = 0; + if (!control.has_value()) { + if (lastLayer.at(target).has_value()) { + layer = *lastLayer.at(target); + if (lastLayerOpType.at(target) != gate->getType()) { + ++layer; + } + lastLayer.at(target) = layer; + } } else { - layers.at(layer).emplace_back(-1, target, gate); + if (!lastLayer.at(*control).has_value() && + !lastLayer.at(target).has_value()) { + layer = 0; + } else if (!lastLayer.at(*control).has_value()) { + layer = *lastLayer.at(target) + 1; + } else if (!lastLayer.at(target).has_value()) { + layer = *lastLayer.at(*control) + 1; + } else { + layer = std::max(*lastLayer.at(*control), *lastLayer.at(target)) + 1; + + if (*lastLayer.at(*control) == *lastLayer.at(target) && + lastLayerOpType.at(*control) == gate->getType() && + lastLayerOpType.at(target) == gate->getType()) { + for (auto& g : layers.at(layer - 1)) { + if ((g.control == *control && g.target == target) || + (g.control == target && g.target == *control)) { + // if last layer contained gate with equivalent qubit set, use that + // layer + --layer; + break; + } + } + } + } + lastLayer.at(*control) = layer; + lastLayer.at(target) = layer; } + return layer; } void Mapper::createLayers() { const auto& config = results.config; std::array, MAX_DEVICE_QUBITS> lastLayer{}; + std::array lastLayerOpType{}; + lastLayerOpType.fill(qc::OpType::None); + + singleQubitMultiplicities.clear(); + twoQubitMultiplicities.clear(); + activeQubits.clear(); + activeQubits1QGates.clear(); + activeQubits2QGates.clear(); + layers.clear(); + + maximumActiveQubits = config.maximumActiveQubits; + maximumActiveQubits1QGates = config.maximumActiveQubits1QGates; + maximumActiveQubits2QGates = config.maximumActiveQubits2QGates; + // TODO: find maximum matching in architecture to prevent overfilling + // maximumActiveQubits2QGates = std::max(config.maximumActiveQubits2QGates, + // 2*architecture->getMaxMatchingSize()); + + if (maximumActiveQubits == 1) { + throw QMAPException("maximumActiveQubits cannot be 1, since this prevents " + "placing any 2q gates!"); + } + if (maximumActiveQubits2QGates == 1) { + throw QMAPException("maximumActiveQubits2QGates cannot be 1, since this " + "prevents placing any 2q gates!"); + } - auto qubitsInLayer = std::set{}; + auto qubitsInLayer = std::set{}; + std::size_t layer = 0; bool even = true; for (auto& gate : qc) { @@ -153,54 +206,39 @@ void Mapper::createLayers() { switch (config.layering) { case Layering::IndividualGates: // each gate is put in a new layer - layers.emplace_back(); - if (control.has_value()) { - layers.back().emplace_back(*control, target, gate.get()); - } else { - layers.back().emplace_back(-1, target, gate.get()); - } + layer = layers.size(); break; case Layering::DisjointQubits: - processDisjointQubitLayer(lastLayer, control, target, gate.get()); + layer = processDisjointQubitLayer(lastLayer, control, target); break; case Layering::Disjoint2qBlocks: - processDisjoint2qBlockLayer(lastLayer, control, target, gate.get()); + layer = processDisjoint2qBlockLayer(lastLayer, control, target); + break; + case Layering::DisjointSameOpTypeBlocks: + layer = processDisjointSameOpTypeBlockLayer(lastLayer, lastLayerOpType, + control, target, gate.get()); break; case Layering::OddGates: // every other gate is put in a new layer if (even) { - layers.emplace_back(); - if (control.has_value()) { - layers.back().emplace_back(*control, target, gate.get()); - } else { - layers.back().emplace_back(-1, target, gate.get()); - } + layer = layers.size(); } else { - if (control.has_value()) { - layers.back().emplace_back(*control, target, gate.get()); - } else { - layers.back().emplace_back(-1, target, gate.get()); - } + assert(!layers.empty()); + layer = layers.size() - 1; } even = !even; break; case Layering::QubitTriangle: - if (layers.empty()) { - layers.emplace_back(); - } - if (singleQubit) { // single qubit gates can be added in any layer - layers.back().emplace_back(-1, target, gate.get()); + layer = layers.empty() ? 0 : layers.size() - 1; } else { qubitsInLayer.insert(*control); qubitsInLayer.insert(target); - if (qubitsInLayer.size() <= 3) { - layers.back().emplace_back(*control, target, gate.get()); + layer = layers.size() - 1; } else { - layers.emplace_back(); - layers.back().emplace_back(*control, target, gate.get()); + layer = layers.size(); qubitsInLayer.clear(); qubitsInLayer.insert(*control); qubitsInLayer.insert(target); @@ -208,54 +246,113 @@ void Mapper::createLayers() { } break; } - } - results.input.layers = layers.size(); - // compute qubit gate multiplicities - singleQubitMultiplicities = std::vector( - layers.size(), SingleQubitMultiplicity(architecture->getNqubits(), 0)); - twoQubitMultiplicities = - std::vector(layers.size(), TwoQubitMultiplicity{}); - activeQubits = std::vector>( - layers.size(), std::set{}); - activeQubits1QGates = std::vector>( - layers.size(), std::set{}); - activeQubits2QGates = std::vector>( - layers.size(), std::set{}); - - for (std::size_t i = 0; i < layers.size(); ++i) { - for (const auto& gate : layers[i]) { - if (gate.singleQubit()) { - activeQubits[i].emplace(gate.target); - activeQubits1QGates[i].emplace(gate.target); - ++singleQubitMultiplicities[i][gate.target]; + // check if layer is full, if so step to the next layer + bool layerChanged = false; + while (layer < layers.size()) { + if (maximumActiveQubits > 0 && + maximumActiveQubits < + activeQubits.at(layer).size() + + static_cast( + activeQubits.at(layer).find(target) == + activeQubits.at(layer).end()) + + (singleQubit ? 0 + : static_cast( + activeQubits.at(layer).find(*control) == + activeQubits.at(layer).end()))) { + // if activeQubits.size() would grow above maximumActiveQubits + ++layer; + layerChanged = true; + continue; + } + if (control.has_value()) { + // 2q gate + if (maximumActiveQubits2QGates > 0 && + maximumActiveQubits2QGates < + activeQubits2QGates.at(layer).size() + + static_cast( + activeQubits2QGates.at(layer).find(target) == + activeQubits2QGates.at(layer).end()) + + static_cast( + activeQubits2QGates.at(layer).find(*control) == + activeQubits2QGates.at(layer).end())) { + // if activeQubits2QGates.size() would grow above + // maximumActiveQubits2QGates + ++layer; + layerChanged = true; + continue; + } } else { - activeQubits[i].emplace(gate.control); - activeQubits[i].emplace(gate.target); - activeQubits2QGates[i].emplace(gate.control); - activeQubits2QGates[i].emplace(gate.target); - if (gate.control >= gate.target) { - const auto edge = - std::pair(gate.target, static_cast(gate.control)); - if (twoQubitMultiplicities[i].find(edge) == - twoQubitMultiplicities[i].end()) { - twoQubitMultiplicities[i][edge] = {0, 1}; - } else { - twoQubitMultiplicities[i][edge].second++; - } - } else { - const auto edge = - std::pair(static_cast(gate.control), gate.target); - if (twoQubitMultiplicities[i].find(edge) == - twoQubitMultiplicities[i].end()) { - twoQubitMultiplicities[i][edge] = {1, 0}; - } else { - twoQubitMultiplicities[i][edge].first++; - } + // 1q gate + if (maximumActiveQubits1QGates > 0 && + maximumActiveQubits1QGates < + activeQubits1QGates.at(layer).size() + + static_cast( + activeQubits1QGates.at(layer).find(target) == + activeQubits1QGates.at(layer).end())) { + // if activeQubits1QGates.size() would grow above + // maximumActiveQubits1QGates + ++layer; + layerChanged = true; + continue; + } + } + break; + } + + if (layerChanged) { + even = true; + lastLayer.at(target) = layer; + lastLayerOpType.at(target) = gate->getType(); + if (!singleQubit) { + lastLayer.at(*control) = layer; + lastLayerOpType.at(*control) = gate->getType(); + qubitsInLayer.clear(); + qubitsInLayer.insert(*control); + qubitsInLayer.insert(target); + } + } + + while (layers.size() <= layer) { + singleQubitMultiplicities.emplace_back(architecture->getNqubits(), 0); + twoQubitMultiplicities.emplace_back(); + activeQubits.emplace_back(); + activeQubits1QGates.emplace_back(); + activeQubits2QGates.emplace_back(); + layers.emplace_back(); + } + + if (singleQubit) { + layers.at(layer).emplace_back(-1, target, gate.get()); + activeQubits.at(layer).emplace(target); + activeQubits1QGates.at(layer).emplace(target); + ++singleQubitMultiplicities.at(layer).at(target); + } else { + layers.at(layer).emplace_back(*control, target, gate.get()); + activeQubits.at(layer).emplace(*control); + activeQubits.at(layer).emplace(target); + activeQubits2QGates.at(layer).emplace(*control); + activeQubits2QGates.at(layer).emplace(target); + if (*control >= target) { + auto insertResult = twoQubitMultiplicities.at(layer).insert( + {{target, *control}, {0, 1}}); + if (insertResult.second) { + // if there is already an entry for this edge, the multiplicity in + // the backward direction is increased + insertResult.first->second.second++; + } + } else { + auto insertResult = twoQubitMultiplicities.at(layer).insert( + {{*control, target}, {1, 0}}); + if (insertResult.second) { + // if there is already an entry for this edge, the multiplicity in + // the forward direction is increased + insertResult.first->second.first++; } } } } + results.input.layers = layers.size(); } bool Mapper::isLayerSplittable(std::size_t index) { diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 5d78a0c66..caaad5f77 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -140,6 +140,18 @@ void HeuristicMapper::checkParameters() { throw QMAPException("Teleportation is not yet supported for heuristic " "mapper using fidelity-aware mapping!"); } + if (usesTargetLocations(config.lookaheadHeuristic) && + !tracksTargetLocations(config.heuristic)) { + throw QMAPException("Lookahead heuristic " + + toString(config.lookaheadHeuristic) + + " requires heuristic that tracks target locations!"); + } + if (config.initialLayout == InitialLayout::DynamicGreedyLookahead && + config.lookaheadHeuristic == LookaheadHeuristic::None) { + throw QMAPException("Initial layout strategy " + + toString(config.initialLayout) + + " requires lookahead heuristic!"); + } } void HeuristicMapper::createInitialMapping() { @@ -200,6 +212,7 @@ void HeuristicMapper::createInitialMapping() { staticInitialMapping(); break; case InitialLayout::Dynamic: + case InitialLayout::DynamicGreedyLookahead: // nothing to be done here break; @@ -210,11 +223,7 @@ void HeuristicMapper::createInitialMapping() { void HeuristicMapper::mapUnmappedGates(std::size_t layer) { if (fidelityAwareHeur) { - for (std::size_t q = 0; q < singleQubitMultiplicities.at(layer).size(); - ++q) { - if (singleQubitMultiplicities.at(layer).at(q) == 0) { - continue; - } + for (const auto& q : activeQubits1QGates.at(layer)) { if (locations.at(q) == DEFAULT_POSITION) { // TODO: consider fidelity // map to first free physical qubit @@ -223,6 +232,10 @@ void HeuristicMapper::mapUnmappedGates(std::size_t layer) { if (qubits.at(physQbit) == -1) { locations.at(q) = static_cast(physQbit); qubits.at(physQbit) = static_cast(q); + qc::QuantumComputation::findAndSWAP(q, physQbit, + qcMapped.initialLayout); + qc::QuantumComputation::findAndSWAP(q, physQbit, + qcMapped.outputPermutation); break; } } @@ -251,15 +264,18 @@ void HeuristicMapper::mapUnmappedGates(std::size_t layer) { // map to 2 qubits with minimal distance double bestScore = std::numeric_limits::max(); - for (std::uint16_t i = 0; i < architecture->getNqubits(); i++) { - 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); - if (dist < bestScore) { - bestScore = dist; - chosenEdge = std::make_pair(i, j); - } + for (std::uint16_t i = 0; i < architecture->getNqubits(); ++i) { + if (qubits.at(i) != DEFAULT_POSITION) { + continue; + } + for (std::uint16_t j = i + 1; j < architecture->getNqubits(); ++j) { + if (qubits.at(j) != DEFAULT_POSITION) { + continue; + } + const double dist = architecture->distance(i, j); + if (dist < bestScore) { + bestScore = dist; + chosenEdge = std::make_pair(i, j); } } } @@ -288,6 +304,171 @@ void HeuristicMapper::mapUnmappedGates(std::size_t layer) { } } +void HeuristicMapper::mapUnmappedGatesLookahead(std::size_t layer) { + Node node(0, 0, qubits, locations); + if (fidelityAwareHeur) { + for (const auto& q : activeQubits1QGates.at(layer)) { + if (locations.at(q) == DEFAULT_POSITION) { + double bestLookaheadPenalty = std::numeric_limits::max(); + std::int16_t bestPhysQbit = DEFAULT_POSITION; + for (std::uint16_t physQbit = 0; physQbit < architecture->getNqubits(); + ++physQbit) { + if (qubits.at(physQbit) != DEFAULT_POSITION) { + continue; + } + node.locations.at(q) = static_cast(physQbit); + node.qubits.at(physQbit) = static_cast(q); + updateLookaheadPenalty(layer, node); + if (node.lookaheadPenalty < bestLookaheadPenalty) { + bestLookaheadPenalty = node.lookaheadPenalty; + bestPhysQbit = static_cast(physQbit); + } + node.locations.at(q) = DEFAULT_POSITION; + node.qubits.at(physQbit) = DEFAULT_POSITION; + } + // if there are enough qubits in the architecture (which should be + // checked in advance), there should always be a free qubit + assert(bestPhysQbit != DEFAULT_POSITION); + node.locations.at(q) = bestPhysQbit; + node.qubits.at(static_cast(bestPhysQbit)) = + static_cast(q); + locations.at(q) = bestPhysQbit; + qubits.at(static_cast(bestPhysQbit)) = + static_cast(q); + qc::QuantumComputation::findAndSWAP( + q, static_cast(bestPhysQbit), qcMapped.initialLayout); + qc::QuantumComputation::findAndSWAP( + q, static_cast(bestPhysQbit), + qcMapped.outputPermutation); + } + } + } + + for (const auto& [logEdge, _] : twoQubitMultiplicities.at(layer)) { + const auto& [q1, q2] = logEdge; + + const auto q1Location = locations.at(q1); + const auto q2Location = locations.at(q2); + + if (q1Location == DEFAULT_POSITION && q2Location == DEFAULT_POSITION) { + double bestLookaheadPenalty = std::numeric_limits::max(); + std::int16_t bestPhysQ1 = DEFAULT_POSITION; + std::int16_t bestPhysQ2 = DEFAULT_POSITION; + for (std::uint16_t physQ1 = 0; physQ1 < architecture->getNqubits(); + ++physQ1) { + if (qubits.at(physQ1) != DEFAULT_POSITION) { + continue; + } + for (std::uint16_t physQ2 = 0; physQ2 < architecture->getNqubits(); + ++physQ2) { + if (physQ1 == physQ2 || qubits.at(physQ2) != DEFAULT_POSITION) { + continue; + } + node.locations.at(q1) = static_cast(physQ1); + node.qubits.at(physQ1) = static_cast(q1); + node.locations.at(q2) = static_cast(physQ2); + node.qubits.at(physQ2) = static_cast(q2); + updateLookaheadPenalty(layer, node); + if (node.lookaheadPenalty < bestLookaheadPenalty) { + bestLookaheadPenalty = node.lookaheadPenalty; + bestPhysQ1 = static_cast(physQ1); + bestPhysQ2 = static_cast(physQ2); + } + node.locations.at(q1) = DEFAULT_POSITION; + node.qubits.at(physQ1) = DEFAULT_POSITION; + node.locations.at(q2) = DEFAULT_POSITION; + node.qubits.at(physQ2) = DEFAULT_POSITION; + } + } + // if there are enough qubits in the architecture (which should be + // checked in advance), there should always be a free qubit + assert(bestPhysQ1 != DEFAULT_POSITION && bestPhysQ2 != DEFAULT_POSITION); + node.locations.at(q1) = bestPhysQ1; + node.locations.at(q2) = bestPhysQ2; + node.qubits.at(static_cast(bestPhysQ1)) = + static_cast(q1); + node.qubits.at(static_cast(bestPhysQ2)) = + static_cast(q2); + locations.at(q1) = bestPhysQ1; + locations.at(q2) = bestPhysQ2; + qubits.at(static_cast(bestPhysQ1)) = + static_cast(q1); + qubits.at(static_cast(bestPhysQ2)) = + static_cast(q2); + qc::QuantumComputation::findAndSWAP( + q1, static_cast(bestPhysQ1), qcMapped.initialLayout); + qc::QuantumComputation::findAndSWAP( + q2, static_cast(bestPhysQ2), qcMapped.initialLayout); + qc::QuantumComputation::findAndSWAP( + q1, static_cast(bestPhysQ1), qcMapped.outputPermutation); + qc::QuantumComputation::findAndSWAP( + q2, static_cast(bestPhysQ2), qcMapped.outputPermutation); + } else if (q1Location == DEFAULT_POSITION) { + double bestLookaheadPenalty = std::numeric_limits::max(); + std::int16_t bestPhysQbit = DEFAULT_POSITION; + for (std::uint16_t physQbit = 0; physQbit < architecture->getNqubits(); + ++physQbit) { + if (qubits.at(physQbit) != DEFAULT_POSITION) { + continue; + } + node.locations.at(q1) = static_cast(physQbit); + node.qubits.at(physQbit) = static_cast(q1); + updateLookaheadPenalty(layer, node); + if (node.lookaheadPenalty < bestLookaheadPenalty) { + bestLookaheadPenalty = node.lookaheadPenalty; + bestPhysQbit = static_cast(physQbit); + } + node.locations.at(q1) = DEFAULT_POSITION; + node.qubits.at(physQbit) = DEFAULT_POSITION; + } + // if there are enough qubits in the architecture (which should be + // checked in advance), there should always be a free qubit + assert(bestPhysQbit != DEFAULT_POSITION); + node.locations.at(q1) = bestPhysQbit; + node.qubits.at(static_cast(bestPhysQbit)) = + static_cast(q1); + locations.at(q1) = bestPhysQbit; + qubits.at(static_cast(bestPhysQbit)) = + static_cast(q1); + qc::QuantumComputation::findAndSWAP( + q1, static_cast(bestPhysQbit), qcMapped.initialLayout); + qc::QuantumComputation::findAndSWAP( + q1, static_cast(bestPhysQbit), qcMapped.outputPermutation); + } else if (q2Location == DEFAULT_POSITION) { + double bestLookaheadPenalty = std::numeric_limits::max(); + std::int16_t bestPhysQbit = DEFAULT_POSITION; + for (std::uint16_t physQbit = 0; physQbit < architecture->getNqubits(); + ++physQbit) { + if (qubits.at(physQbit) != DEFAULT_POSITION) { + continue; + } + node.locations.at(q2) = static_cast(physQbit); + node.qubits.at(physQbit) = static_cast(q2); + updateLookaheadPenalty(layer, node); + if (node.lookaheadPenalty < bestLookaheadPenalty) { + bestLookaheadPenalty = node.lookaheadPenalty; + bestPhysQbit = static_cast(physQbit); + } + node.locations.at(q2) = DEFAULT_POSITION; + node.qubits.at(physQbit) = DEFAULT_POSITION; + } + // if there are enough qubits in the architecture (which should be + // checked in advance), there should always be a free qubit + assert(bestPhysQbit != DEFAULT_POSITION); + node.locations.at(q2) = bestPhysQbit; + node.qubits.at(static_cast(bestPhysQbit)) = + static_cast(q2); + locations.at(q2) = bestPhysQbit; + qubits.at(static_cast(bestPhysQbit)) = + static_cast(q2); + qc::QuantumComputation::findAndSWAP( + q2, static_cast(bestPhysQbit), qcMapped.initialLayout); + qc::QuantumComputation::findAndSWAP( + q2, static_cast(bestPhysQbit), qcMapped.outputPermutation); + } + } +} + void HeuristicMapper::mapToMinDistance(const std::uint16_t source, const std::uint16_t target) { auto min = std::numeric_limits::max(); @@ -549,13 +730,17 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { Node bestDoneNode(0); bool validMapping = false; - mapUnmappedGates(layer); + if (config.initialLayout == InitialLayout::DynamicGreedyLookahead) { + mapUnmappedGatesLookahead(layer); + } else { + mapUnmappedGates(layer); + } node.locations = locations; node.qubits = qubits; recalculateFixedCost(layer, node); updateHeuristicCost(layer, node); - updateLookaheadPenalty(layer, node); + updateLookaheadPenalty(getNextLayer(layer), node); if (config.dataLoggingEnabled()) { dataLogger->logSearchNode(layer, node.id, node.parent, @@ -578,7 +763,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { while (!nodes.empty() && (!validMapping || - nodes.top().getTotalCost() < bestDoneNode.getTotalFixedCost())) { + nodes.top().getTotalCostNoLookahead() < bestDoneNode.costFixed)) { if (splittable && expandedNodes >= config.automaticLayerSplitsNodeLimit) { if (config.dataLoggingEnabled()) { qc::CompoundOperation compOp(architecture->getNqubits()); @@ -1037,7 +1222,7 @@ void HeuristicMapper::applySWAP(const Edge& swap, std::size_t layer, recalculateFixedCostReversals(layer, node); updateHeuristicCost(layer, node); if (results.config.lookaheadHeuristic != LookaheadHeuristic::None) { - updateLookaheadPenalty(layer, node); + updateLookaheadPenalty(getNextLayer(layer), node); } } @@ -1112,7 +1297,7 @@ void HeuristicMapper::applyTeleportation(const Edge& swap, std::size_t layer, recalculateFixedCostReversals(layer, node); updateHeuristicCost(layer, node); if (results.config.lookaheadHeuristic != LookaheadHeuristic::None) { - updateLookaheadPenalty(layer, node); + updateLookaheadPenalty(getNextLayer(layer), node); } } @@ -1406,6 +1591,7 @@ HeuristicMapper::heuristicGateCountMaxDistanceOrSumDistanceMinusSharedSwaps( double HeuristicMapper::heuristicFidelityBestLocation(std::size_t layer, Node& node) { const auto& consideredQubits = getConsideredQubits(layer); + const auto& activeQubits1Q = activeQubits1QGates.at(layer); const auto& singleQubitGateMultiplicity = singleQubitMultiplicities.at(layer); const auto& twoQubitGateMultiplicity = twoQubitMultiplicities.at(layer); @@ -1414,14 +1600,16 @@ double HeuristicMapper::heuristicFidelityBestLocation(std::size_t layer, // single qubit gate savings potential by moving them to different physical // qubits with higher fidelity double savingsPotential = 0.; - for (std::uint16_t logQbit = 0U; logQbit < architecture->getNqubits(); - ++logQbit) { + for (const auto& logQbit : activeQubits1Q) { if (singleQubitGateMultiplicity.at(logQbit) == 0) { continue; } - double qbitSavings = 0; - const double currFidelity = architecture->getSingleQubitFidelityCost( - static_cast(node.locations.at(logQbit))); + double qbitSavings = 0; + const auto currentLocaction = + static_cast(node.locations.at(logQbit)); + auto targetLocation = currentLocaction; + const double currFidelity = + architecture->getSingleQubitFidelityCost(currentLocaction); for (std::uint16_t physQbit = 0U; physQbit < architecture->getNqubits(); ++physQbit) { if (architecture->getSingleQubitFidelityCost(physQbit) >= currFidelity) { @@ -1434,8 +1622,13 @@ double HeuristicMapper::heuristicFidelityBestLocation(std::size_t layer, architecture->fidelityDistance( static_cast(node.locations.at(logQbit)), physQbit, consideredQubits.size() - 1); - qbitSavings = std::max(qbitSavings, curSavings); + if (curSavings > qbitSavings) { + qbitSavings = curSavings; + targetLocation = physQbit; + } } + node.targetLocations.at(logQbit) = + static_cast(targetLocation); savingsPotential += qbitSavings; } @@ -1454,29 +1647,41 @@ double HeuristicMapper::heuristicFidelityBestLocation(std::size_t layer, // pair and take the cost of moving it there via swaps plus the // fidelity cost of executing all their shared gates on that edge // as the qubit pairs cost - double swapCost = std::numeric_limits::max(); - for (const auto& [q3, q4] : architecture->getCouplingMap()) { - swapCost = std::min( - swapCost, - forwardMult * architecture->getTwoQubitFidelityCost(q3, q4) + - reverseMult * architecture->getTwoQubitFidelityCost(q4, q3) + + double swapCost = std::numeric_limits::max(); + const auto currentLocation1 = + static_cast(node.locations.at(q1)); + const auto currentLocation2 = + static_cast(node.locations.at(q2)); + auto targetLocation1 = currentLocation1; + auto targetLocation2 = currentLocation2; + for (const auto& [physQ1, physQ2] : architecture->getCouplingMap()) { + const auto currSwapCost = std::min( + forwardMult * architecture->getTwoQubitFidelityCost(physQ1, physQ2) + + reverseMult * + architecture->getTwoQubitFidelityCost(physQ2, physQ1) + architecture->fidelityDistance( - static_cast(node.locations.at(q1)), q3, + static_cast(node.locations.at(q1)), physQ1, consideredQubits.size() - 1) + architecture->fidelityDistance( - static_cast(node.locations.at(q2)), q4, - consideredQubits.size() - 1)); - swapCost = std::min( - swapCost, - forwardMult * architecture->getTwoQubitFidelityCost(q4, q3) + - reverseMult * architecture->getTwoQubitFidelityCost(q3, q4) + + static_cast(node.locations.at(q2)), physQ2, + consideredQubits.size() - 1), + forwardMult * architecture->getTwoQubitFidelityCost(physQ2, physQ1) + + reverseMult * + architecture->getTwoQubitFidelityCost(physQ1, physQ2) + architecture->fidelityDistance( - static_cast(node.locations.at(q2)), q3, + static_cast(node.locations.at(q2)), physQ1, consideredQubits.size() - 1) + architecture->fidelityDistance( - static_cast(node.locations.at(q1)), q4, + static_cast(node.locations.at(q1)), physQ2, consideredQubits.size() - 1)); + if (swapCost > currSwapCost) { + swapCost = currSwapCost; + targetLocation1 = physQ1; + targetLocation2 = physQ2; + } } + node.targetLocations.at(q1) = static_cast(targetLocation1); + node.targetLocations.at(q2) = static_cast(targetLocation2); if (edgeDone) { const double currEdgeCost = @@ -1497,11 +1702,10 @@ double HeuristicMapper::heuristicFidelityBestLocation(std::size_t layer, return costHeur - savingsPotential; } -void HeuristicMapper::updateLookaheadPenalty(const std::size_t layer, +void HeuristicMapper::updateLookaheadPenalty(std::size_t nextLayer, HeuristicMapper::Node& node) { const auto& config = results.config; node.lookaheadPenalty = 0.; - auto nextLayer = getNextLayer(layer); double factor = config.firstLookaheadFactor; for (std::size_t i = 0; i < config.nrLookaheads; ++i) { @@ -1517,14 +1721,17 @@ void HeuristicMapper::updateLookaheadPenalty(const std::size_t layer, case LookaheadHeuristic::GateCountSumDistance: penalty = lookaheadGateCountSumDistance(nextLayer, node); break; - default: + case LookaheadHeuristic::FidelityCurrentLocation: + penalty = lookaheadFidelityCurrentLocation(nextLayer, node); + break; + case LookaheadHeuristic::FidelityCurrentTargetLocation: + penalty = lookaheadFidelityCurrentTargetLocation(nextLayer, node); break; } node.lookaheadPenalty += factor * penalty; factor *= config.lookaheadFactor; - nextLayer = getNextLayer(nextLayer); // TODO: consider single qubits here - // for better fidelity lookahead + nextLayer = getNextLayer(nextLayer); } } @@ -1545,7 +1752,6 @@ HeuristicMapper::lookaheadGateCountMaxDistance(const std::size_t layer, auto min = std::numeric_limits::max(); for (std::uint16_t j = 0; j < architecture->getNqubits(); ++j) { if (node.qubits.at(j) == DEFAULT_POSITION) { - // TODO: Consider fidelity here if available if (forwardMult > 0) { min = std::min(min, architecture->distance( j, static_cast(loc2))); @@ -1561,7 +1767,6 @@ HeuristicMapper::lookaheadGateCountMaxDistance(const std::size_t layer, auto min = std::numeric_limits::max(); for (std::uint16_t j = 0; j < architecture->getNqubits(); ++j) { if (node.qubits.at(j) == DEFAULT_POSITION) { - // TODO: Consider fidelity here if available if (forwardMult > 0) { min = std::min(min, architecture->distance( static_cast(loc1), j)); @@ -1609,7 +1814,6 @@ HeuristicMapper::lookaheadGateCountSumDistance(const std::size_t layer, auto min = std::numeric_limits::max(); for (std::uint16_t j = 0; j < architecture->getNqubits(); ++j) { if (node.qubits.at(j) == DEFAULT_POSITION) { - // TODO: Consider fidelity here if available if (forwardMult > 0) { min = std::min(min, architecture->distance( j, static_cast(loc2))); @@ -1625,7 +1829,6 @@ HeuristicMapper::lookaheadGateCountSumDistance(const std::size_t layer, auto min = std::numeric_limits::max(); for (std::uint16_t j = 0; j < architecture->getNqubits(); ++j) { if (node.qubits.at(j) == DEFAULT_POSITION) { - // TODO: Consider fidelity here if available if (forwardMult > 0) { min = std::min(min, architecture->distance( static_cast(loc1), j)); @@ -1655,3 +1858,133 @@ HeuristicMapper::lookaheadGateCountSumDistance(const std::size_t layer, return penalty; } + +double +HeuristicMapper::lookaheadFidelityCurrentLocation(const std::size_t layer, + HeuristicMapper::Node& node) { + const auto& consideredQubits = getConsideredQubits(layer); + const auto& activeQubits1Q = activeQubits1QGates.at(layer); + const auto& singleQubitGateMultiplicity = singleQubitMultiplicities.at(layer); + const auto& twoQubitGateMultiplicity = twoQubitMultiplicities.at(layer); + + double penalty = 0.; + for (const auto& logQbit : activeQubits1Q) { + if (singleQubitGateMultiplicity.at(logQbit) == 0) { + continue; + } + const auto physQbit = node.locations.at(logQbit); + if (physQbit == DEFAULT_POSITION) { + continue; + } + penalty += singleQubitGateMultiplicity.at(logQbit) * + architecture->getSingleQubitFidelityCost( + static_cast(physQbit)); + } + + for (const auto& [edge, mult] : twoQubitGateMultiplicity) { + const auto [q1, q2] = edge; + + const auto physQ1 = node.locations.at(q1); + const auto physQ2 = node.locations.at(q2); + + if (physQ1 == DEFAULT_POSITION && physQ2 == DEFAULT_POSITION) { + // no penalty + } else if (physQ1 == DEFAULT_POSITION) { + double min = std::numeric_limits::max(); + for (std::uint16_t j = 0; j < architecture->getNqubits(); ++j) { + if (node.qubits.at(j) == DEFAULT_POSITION) { + // 1 swap could potentially be saved by sharing with any other + // considered qubit + 1 edge is left to execute the gates on + min = std::min(min, architecture->fidelityDistance( + j, physQ2, consideredQubits.size())); + } + } + penalty += min; + } else if (physQ2 == DEFAULT_POSITION) { + double min = std::numeric_limits::max(); + for (std::uint16_t j = 0; j < architecture->getNqubits(); ++j) { + if (node.qubits.at(j) == DEFAULT_POSITION) { + // 1 swap could potentially be saved by sharing with any other + // considered qubit + 1 edge is left to execute the gates on + min = std::min(min, architecture->fidelityDistance( + j, physQ1, consideredQubits.size())); + } + } + penalty += min; + } else { + penalty += architecture->fidelityDistance(physQ1, physQ2, + consideredQubits.size()); + } + } + + return penalty; +} + +double HeuristicMapper::lookaheadFidelityCurrentTargetLocation( + const std::size_t layer, HeuristicMapper::Node& node) { + const auto& consideredQubits = getConsideredQubits(layer); + const auto& activeQubits1Q = activeQubits1QGates.at(layer); + const auto& singleQubitGateMultiplicity = singleQubitMultiplicities.at(layer); + const auto& twoQubitGateMultiplicity = twoQubitMultiplicities.at(layer); + + double penalty = 0.; + for (const auto& logQbit : activeQubits1Q) { + if (singleQubitGateMultiplicity.at(logQbit) == 0) { + continue; + } + auto physQbit = node.targetLocations.at(logQbit); + if (physQbit == DEFAULT_POSITION) { + physQbit = node.locations.at(logQbit); + } + if (physQbit == DEFAULT_POSITION) { + continue; + } + penalty += singleQubitGateMultiplicity.at(logQbit) * + architecture->getSingleQubitFidelityCost( + static_cast(physQbit)); + } + + for (const auto& [edge, mult] : twoQubitGateMultiplicity) { + const auto [q1, q2] = edge; + + auto physQ1 = node.targetLocations.at(q1); + if (physQ1 == DEFAULT_POSITION) { + physQ1 = node.locations.at(q1); + } + auto physQ2 = node.targetLocations.at(q2); + if (physQ2 == DEFAULT_POSITION) { + physQ2 = node.locations.at(q2); + } + + if (physQ1 == DEFAULT_POSITION && physQ2 == DEFAULT_POSITION) { + // no penalty + } else if (physQ1 == DEFAULT_POSITION) { + double min = std::numeric_limits::max(); + for (std::uint16_t j = 0; j < architecture->getNqubits(); ++j) { + if (node.qubits.at(j) == DEFAULT_POSITION) { + // 1 swap could potentially be saved by sharing with any other + // considered qubit + 1 edge is left to execute the gates on + min = std::min(min, architecture->fidelityDistance( + j, physQ2, consideredQubits.size())); + } + } + penalty += min; + } else if (physQ2 == DEFAULT_POSITION) { + double min = std::numeric_limits::max(); + for (std::uint16_t j = 0; j < architecture->getNqubits(); ++j) { + if (node.qubits.at(j) == DEFAULT_POSITION) { + // 1 swap could potentially be saved by sharing with any other + // considered qubit + 1 edge is left to execute the gates on + min = std::min(min, architecture->fidelityDistance( + j, physQ1, consideredQubits.size())); + } + } + penalty += min; + } else { + penalty += architecture->fidelityDistance(physQ1, physQ2, + consideredQubits.size()); + } + } + + return penalty; +} diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 128c3c35d..f8968b87d 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -67,6 +67,9 @@ def compile( # noqa: A001 initial_layout: str | InitialLayout = "dynamic", iterative_bidirectional_routing_passes: int | None = None, layering: str | Layering = "individual_gates", + maximum_active_qubits: int = 0, + maximum_active_qubits_1q_gates: int = 0, + maximum_active_qubits_2q_gates: int = 0, automatic_layer_splits_node_limit: int | None = 5000, early_termination: str | EarlyTermination = "none", early_termination_limit: int = 0, @@ -103,6 +106,9 @@ def compile( # noqa: A001 initial_layout: The initial layout to use. Defaults to "dynamic". iterative_bidirectional_routing_passes: Number of iterative bidirectional routing passes to perform or None to disable. Defaults to None. layering: The layering strategy to use. Defaults to "individual_gates". + maximum_active_qubits: The maximum number of active qubits in a single layer or 0 for no limit. Defaults to 0. + maximum_active_qubits_1q_gates: The maximum number of active qubits in a single layer for 1-qubit gates or 0 for no limit. Defaults to 0. + maximum_active_qubits_2q_gates: The maximum number of active qubits in a single layer for 2-qubit gates or 0 for no limit. Defaults to 0. automatic_layer_splits_node_limit: The number of expanded nodes after which to split a layer or None to disable automatic layer splitting. Defaults to 5000. early_termination: The early termination strategy to use, i.e. terminating the search after a goal node has been found, but before it is guarantueed to be optimal. Defaults to "none". early_termination_limit: The number of nodes (counted according to the early termination strategy) after which to terminate the search early. Defaults to 0. @@ -151,6 +157,9 @@ def compile( # noqa: A001 config.iterative_bidirectional_routing = True config.iterative_bidirectional_routing_passes = iterative_bidirectional_routing_passes config.layering = Layering(layering) + config.maximum_active_qubits = maximum_active_qubits + config.maximum_active_qubits_1q_gates = maximum_active_qubits_1q_gates + config.maximum_active_qubits_2q_gates = maximum_active_qubits_2q_gates if automatic_layer_splits_node_limit is None: config.automatic_layer_splits = False else: diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index 15b2822d3..7e08093eb 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -121,6 +121,9 @@ class Configuration: iterative_bidirectional_routing: bool iterative_bidirectional_routing_passes: int layering: Layering + maximum_active_qubits: int + maximum_active_qubits_1q_gates: int + maximum_active_qubits_2q_gates: int automatic_layer_splits: bool automatic_layer_splits_node_limit: int early_termination: EarlyTermination @@ -198,6 +201,7 @@ class Encoding: class InitialLayout: __members__: ClassVar[dict[InitialLayout, int]] = ... # read-only + dynamic_greedy_lookahead: ClassVar[InitialLayout] = ... dynamic: ClassVar[InitialLayout] = ... identity: ClassVar[InitialLayout] = ... static: ClassVar[InitialLayout] = ... @@ -249,6 +253,8 @@ class LookaheadHeuristic: none: ClassVar[LookaheadHeuristic] = ... gate_count_max_distance: ClassVar[LookaheadHeuristic] = ... gate_count_sum_distance: ClassVar[LookaheadHeuristic] = ... + fidelity_current_location: ClassVar[LookaheadHeuristic] = ... + fidelity_current_target_location: ClassVar[LookaheadHeuristic] = ... @overload def __init__(self, value: int) -> None: ... @overload @@ -274,6 +280,7 @@ class Layering: odd_gates: ClassVar[Layering] = ... qubit_triangle: ClassVar[Layering] = ... disjoint_2q_blocks: ClassVar[Layering] = ... + disjoint_same_op_type_blocks: ClassVar[Layering] = ... @overload def __init__(self, value: int) -> None: ... @overload diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 38832e9b8..7eb8cf516 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -117,6 +117,7 @@ PYBIND11_MODULE(pyqmap, m) { .value("identity", InitialLayout::Identity) .value("static", InitialLayout::Static) .value("dynamic", InitialLayout::Dynamic) + .value("dynamic_greedy_lookahead", InitialLayout::DynamicGreedyLookahead) .export_values() // allow construction from string .def(py::init([](const std::string& str) -> InitialLayout { @@ -145,6 +146,10 @@ PYBIND11_MODULE(pyqmap, m) { LookaheadHeuristic::GateCountMaxDistance) .value("gate_count_sum_distance", LookaheadHeuristic::GateCountSumDistance) + .value("fidelity_current_location", + LookaheadHeuristic::FidelityCurrentLocation) + .value("fidelity_current_target_location", + LookaheadHeuristic::FidelityCurrentTargetLocation) .export_values() // allow construction from string .def(py::init([](const std::string& str) -> LookaheadHeuristic { @@ -158,6 +163,7 @@ PYBIND11_MODULE(pyqmap, m) { .value("odd_gates", Layering::OddGates) .value("qubit_triangle", Layering::QubitTriangle) .value("disjoint_2q_blocks", Layering::Disjoint2qBlocks) + .value("disjoint_same_op_type_blocks", Layering::DisjointSameOpTypeBlocks) .export_values() // allow construction from string .def(py::init([](const std::string& str) -> Layering { @@ -228,6 +234,12 @@ PYBIND11_MODULE(pyqmap, m) { .def_readwrite("debug", &Configuration::debug) .def_readwrite("data_logging_path", &Configuration::dataLoggingPath) .def_readwrite("layering", &Configuration::layering) + .def_readwrite("maximum_active_qubits", + &Configuration::maximumActiveQubits) + .def_readwrite("maximum_active_qubits_1q_gates", + &Configuration::maximumActiveQubits1QGates) + .def_readwrite("maximum_active_qubits_2q_gates", + &Configuration::maximumActiveQubits2QGates) .def_readwrite("automatic_layer_splits", &Configuration::automaticLayerSplits) .def_readwrite("automatic_layer_splits_node_limit", diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 2f64c10d0..f3cf306ca 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -336,24 +336,24 @@ TEST_F(InternalsTest, NodeLookaheadCalculation) { results.config.lookaheadHeuristic = LookaheadHeuristic::GateCountMaxDistance; results.config.nrLookaheads = 1; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (2 * COST_UNIDIRECTIONAL_SWAP), FLOAT_TOLERANCE); results.config.nrLookaheads = 2; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (2 * COST_UNIDIRECTIONAL_SWAP) + 0.75 * 0.5 * (2 * COST_UNIDIRECTIONAL_SWAP), FLOAT_TOLERANCE); results.config.nrLookaheads = 3; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (2 * COST_UNIDIRECTIONAL_SWAP) + 0.75 * 0.5 * (2 * COST_UNIDIRECTIONAL_SWAP) + 0.75 * 0.5 * 0.5 * (2 * COST_UNIDIRECTIONAL_SWAP), FLOAT_TOLERANCE); results.config.nrLookaheads = 4; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (2 * COST_UNIDIRECTIONAL_SWAP) + 0.75 * 0.5 * (2 * COST_UNIDIRECTIONAL_SWAP) + @@ -362,25 +362,25 @@ TEST_F(InternalsTest, NodeLookaheadCalculation) { results.config.lookaheadHeuristic = LookaheadHeuristic::GateCountSumDistance; results.config.nrLookaheads = 1; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (3 * COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE), FLOAT_TOLERANCE); results.config.nrLookaheads = 2; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (3 * COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE) + 0.75 * 0.5 * (2 * COST_UNIDIRECTIONAL_SWAP), FLOAT_TOLERANCE); results.config.nrLookaheads = 3; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (3 * COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE) + 0.75 * 0.5 * (2 * COST_UNIDIRECTIONAL_SWAP) + 0.75 * 0.5 * 0.5 * (2 * COST_UNIDIRECTIONAL_SWAP), FLOAT_TOLERANCE); results.config.nrLookaheads = 4; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (3 * COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE) + 0.75 * 0.5 * (2 * COST_UNIDIRECTIONAL_SWAP) + @@ -392,14 +392,14 @@ TEST_F(InternalsTest, NodeLookaheadCalculation) { results.config.lookaheadHeuristic = LookaheadHeuristic::GateCountMaxDistance; results.config.nrLookaheads = 1; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE), FLOAT_TOLERANCE); results.config.lookaheadHeuristic = LookaheadHeuristic::GateCountSumDistance; results.config.nrLookaheads = 1; - updateLookaheadPenalty(0, node); + updateLookaheadPenalty(1, node); EXPECT_NEAR(node.lookaheadPenalty, 0.75 * (2 * COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE), FLOAT_TOLERANCE);