Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fidelity-aware lookahead, lookahead-based initial layout, DisjointSameOpType layering, upper limits for active qubits per layer #416

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 40 additions & 8 deletions include/Mapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ class Mapper {
*/
std::vector<std::set<std::uint16_t>> 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`
Expand Down Expand Up @@ -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<std::optional<std::size_t>, MAX_DEVICE_QUBITS>& lastLayer,
const std::optional<std::uint16_t>& control, std::uint16_t target,
qc::Operation* gate);
const std::optional<std::uint16_t>& control, std::uint16_t target);

/**
* Similar to processDisjointQubitLayer, but instead of treating each gate
Expand All @@ -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<std::optional<std::size_t>, MAX_DEVICE_QUBITS>& lastLayer,
const std::optional<std::uint16_t>& 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<std::optional<std::size_t>, MAX_DEVICE_QUBITS>& lastLayer,
std::array<qc::OpType, MAX_DEVICE_QUBITS>& lastLayerOpType,
const std::optional<std::uint16_t>& control, std::uint16_t target,
qc::Operation* gate);

Expand Down
5 changes: 4 additions & 1 deletion include/configuration/Configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ struct Configuration {
std::set<std::uint16_t> 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;
Expand Down
15 changes: 15 additions & 0 deletions include/configuration/Heuristic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,21 @@
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;
}
Fixed Show fixed Hide fixed
}

[[maybe_unused]] static inline std::string toString(const Heuristic heuristic) {
switch (heuristic) {
case Heuristic::GateCountMaxDistance:
Expand Down
26 changes: 23 additions & 3 deletions include/configuration/InitialLayout.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,25 @@

#include <iostream>

/// 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) {
Expand All @@ -21,6 +36,8 @@ toString(const InitialLayout strategy) {
return "static";
case InitialLayout::Dynamic:
return "dynamic";
case InitialLayout::DynamicGreedyLookahead:
return "dynamic_greedy_lookahead";
}
return " ";
}
Expand All @@ -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);
}
8 changes: 7 additions & 1 deletion include/configuration/Layering.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ enum class Layering {
DisjointQubits,
OddGates,
QubitTriangle,
Disjoint2qBlocks
Disjoint2qBlocks,
DisjointSameOpTypeBlocks
};

[[maybe_unused]] static inline std::string toString(const Layering strategy) {
Expand All @@ -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 " ";
}
Expand All @@ -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);
}
39 changes: 38 additions & 1 deletion include/configuration/LookaheadHeuristic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,16 @@
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
};

/**
Expand All @@ -29,10 +38,28 @@
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;
}
Fixed Show fixed Hide fixed
}

[[maybe_unused]] static inline std::string
toString(const LookaheadHeuristic heuristic) {
switch (heuristic) {
Expand All @@ -42,6 +69,10 @@
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 " ";
}
Expand All @@ -57,6 +88,12 @@
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);
}
68 changes: 62 additions & 6 deletions include/heuristic/HeuristicMapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ class HeuristicMapper : public Mapper {
* The inverse of `qubits`
*/
std::array<std::int16_t, MAX_DEVICE_QUBITS> 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<std::int16_t, MAX_DEVICE_QUBITS> targetLocations{};
/** current fixed cost
*
* non-fidelity-aware: cost of all swaps used in the node
Expand Down Expand Up @@ -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<std::int16_t, MAX_DEVICE_QUBITS>& q,
Expand All @@ -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
Expand All @@ -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
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading
Loading