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

📝 Added in-code documentation to Mapper and HeuristicMapper #148

Merged
merged 3 commits into from
Oct 18, 2022
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/cmake-build-*
.idea/
.cache/
.vscode/

__pycache__/
*.so
Expand Down
87 changes: 83 additions & 4 deletions include/Mapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ constexpr unsigned short MAX_DEVICE_QUBITS = 128;
class Mapper {
protected:
// internal structures

/**
* @brief Structure to store an operation on 1 or 2 logical qubits.
*
* For a single-qubit operation `control` is set to `-1`
*/
struct Gate {
short control = -1;
unsigned short target = 0;
Expand All @@ -42,41 +48,114 @@ class Mapper {
}
};

/**
* @brief The quantum circuit to be mapped
*/
qc::QuantumComputation qc;
Architecture& architecture;

qc::QuantumComputation qcMapped;
/**
* @brief The quantum architecture on which to map the circuit
*/
Architecture& architecture;

/**
* @brief The resulting quantum circuit after mapping
*/
qc::QuantumComputation qcMapped;
/**
* @brief The gates of the circuit split into layers
*
* Each entry in the outer vector corresponds to 1 layer, containing all its gates in an inner vector
*/
std::vector<std::vector<Gate>> layers{};

std::array<short, MAX_DEVICE_QUBITS> qubits{};
/**
* @brief containing the logical qubit currently mapped to each physical qubit.
* `qubits[physical_qubit] = logical_qubit`
*
* The inverse of `locations`
*/
std::array<short, MAX_DEVICE_QUBITS> qubits{};
/**
* @brief containing the logical qubit currently mapped to each physical qubit.
* `locations[logical_qubit] = physical_qubit`
*
* The inverse of `qubits`
*/
std::array<short, MAX_DEVICE_QUBITS> locations{};
std::array<double, MAX_DEVICE_QUBITS> fidelities{};

std::unordered_set<unsigned short> usedDeviceQubits{};

MappingResults results{};

/**
* @brief Initialize the results structure with circuit names, registers in the output circuit, gate counts, etc.
*/
virtual void initResults();

/**
* @brief Splits the circuit into layers according to the method set in `config.layering` and saves the result in `layers`
*
* methods of layering described in https://iic.jku.at/files/eda/2019_dac_mapping_quantum_circuits_ibm_architectures_using_minimal_number_swap_h_gates.pdf
*
* Layering::IndividualGates/Layering::None -> each gate on separate layer
* Layering::DisjointQubits -> each layer contains gates only acting on a disjoint set of qubits
* Layering::OddGates -> always 2 gates per layer (assigned by order of original gate index in the circuit)
* Layering::QubitTriangle -> intended for architectures which contain triangles of physical qubits, each layer only contains gates acting on 3 distinct qubits
*/
virtual void createLayers();

/**
* @brief Get the index of the next layer after the given index containing a gate acting on more than one qubit
*/
virtual std::size_t getNextLayer(std::size_t idx);

/**
* @brief adding additional qubits to the result circuit if architecture has more physical qubits than the original
* circuit has logical qubits
*/
virtual void placeRemainingArchitectureQubits();

/**
* @brief finalizes the circuit after mapping
* (e.g. adding unused qubits if architecture has more physical qubits than mapped circuit has logical qubits)
*/
virtual void finalizeMappedCircuit();

/**
* @brief count number of elementary gates and cnots in circuit and save the results in `info.gates` and `info.cnots`
*/
virtual void countGates(const qc::QuantumComputation& circuit, MappingResults::CircuitInfo& info) {
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`
*/
virtual void countGates(decltype(qcMapped.cbegin()) it, const decltype(qcMapped.cend())& end, MappingResults::CircuitInfo& info);

/**
* @brief performs optimizations on the circuit before mapping
*
* @param config contains settings of the current mapping run (e.g. `config.preMappingOptimizations` controls if pre-mapping optimizations are performed)
*/
virtual void preMappingOptimizations(const Configuration& config);

/**
* @brief performs optimizations on the circuit before mapping
*
* @param config contains settings of the current mapping run (e.g. `config.postMappingOptimizations` controls if post-mapping optimizations are performed)
*/
virtual void postMappingOptimizations(const Configuration& config);

public:
Mapper(const qc::QuantumComputation& qc, Architecture& architecture);
virtual ~Mapper() = default;

/**
* @brief map the circuit passed at initialization to the architecture
*
* @param config the settings for this mapping run (controls e.g. layering methods, pre- and post-optimizations, etc.)
*/
virtual void map(const Configuration& config) = 0;

virtual void dumpResult(const std::string& outputFilename) {
Expand Down
119 changes: 110 additions & 9 deletions include/heuristic/HeuristicMapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,45 @@ class HeuristicMapper: public Mapper {
public:
using Mapper::Mapper; // import constructors from parent class

/**
* @brief map the circuit passed at initialization to the architecture
*
* @param config the settings for this mapping run (controls e.g. layering methods, pre- and post-optimizations, etc.)
*/
void map(const Configuration& config) override;

/**
* @brief struct representing one node in the A* search containing info about swaps, mappings and costs
*/
struct Node {
unsigned long costFixed = 0;
double costHeur = 0.;
double lookaheadPenalty = 0.;
double costTotal = 0.;
std::array<short, MAX_DEVICE_QUBITS> qubits{}; // get qubit at specific location
std::array<short, MAX_DEVICE_QUBITS> locations{}; // get location of specific qubit
bool done = true;
std::vector<std::vector<Exchange>> swaps = {};
unsigned long nswaps = 0;
/** cost of all swaps in the node */
unsigned long costFixed = 0;
/** heuristic cost expected for future swaps needed in current circuit layer */
double costHeur = 0.;
/** heuristic cost expected for future swaps needed in later circuit layers (further layers contribute less) */
double lookaheadPenalty = 0.;
double costTotal = 0.;
/**
* containing the logical qubit currently mapped to each physical qubit.
* `qubits[physical_qubit] = logical_qubit`
*
* The inverse of `locations`
*/
std::array<short, MAX_DEVICE_QUBITS> qubits{};
/**
* containing the logical qubit currently mapped to each physical qubit.
* `locations[logical_qubit] = physical_qubit`
*
* The inverse of `qubits`
*/
std::array<short, MAX_DEVICE_QUBITS> locations{};
/** true if all qubit pairs are mapped next to each other on the architecture */
bool done = true;
/** swaps used to get from mapping after last layer to the current mapping;
* each search node begins a new entry in the outer vector */
std::vector<std::vector<Exchange>> swaps = {};
/** number of swaps used to get from mapping after last layer to the current mapping */
unsigned long nswaps = 0;

Node() = default;
Node(const std::array<short, MAX_DEVICE_QUBITS>& q, const std::array<short, MAX_DEVICE_QUBITS>& loc, const std::vector<std::vector<Exchange>>& sw = {}) {
Expand All @@ -33,6 +60,9 @@ class HeuristicMapper: public Mapper {
std::copy(sw.begin(), sw.end(), std::back_inserter(swaps));
}

/**
* @brief applies an in-place swap of 2 qubits in `qubits` and `locations` of the node
*/
void applySWAP(const Edge& swap, Architecture& arch) {
short q1 = qubits.at(swap.first);
short q2 = qubits.at(swap.second);
Expand All @@ -54,6 +84,9 @@ class HeuristicMapper: public Mapper {
}
}

/**
* @brief applies an in-place teleportation of 2 qubits in `qubits` and `locations` of the node
*/
void applyTeleportation(const Edge& swap, Architecture& arch) {
short q1 = qubits.at(swap.first);
short q2 = qubits.at(swap.second);
Expand Down Expand Up @@ -108,6 +141,9 @@ class HeuristicMapper: public Mapper {
}
}

/**
* @brief checks if the qubits of the given gate are mapped next to each other. If not, set `done` to false
*/
void checkUnfinished(const Architecture& arch, const Gate& gate) {
if (arch.distance(locations.at(gate.control), locations.at(gate.target)) > COST_DIRECTION_REVERSE) {
done = false;
Expand All @@ -131,28 +167,93 @@ class HeuristicMapper: public Mapper {
protected:
unique_priority_queue<Node> nodes{};

/**
* @brief creates an initial mapping of logical qubits to physical qubits with different methods depending on
* `Mapper::results.config.initialLayout`
*/
virtual void createInitialMapping();

/**
* @brief statically creates an initial mapping of logical qubits to physical qubits by considering qubits that
* share a gate in the first layer and mapping those to any free connected qubit pair in the architecture.
* The remaining qubits are then just mapped by order of index.
*/
virtual void staticInitialMapping();

/**
* @brief returns distance of the given logical qubit pair according to the current mapping
*/
double distanceOnArchitectureOfLogicalQubits(unsigned short control, unsigned short target) {
return architecture.distance(locations.at(control), locations.at(target));
}

/**
* @brief returns distance of the given physical qubit pair on the architecture
*/
double distanceOnArchitectureOfPhysicalQubits(unsigned short control, unsigned short target) {
return architecture.distance(control, target);
}

/**
* @brief map the logical qubit `target` to a free physical qubit, that is nearest to the physical qubit `source` is mapped to
*
* @param source an already mapped logical qubit, which should be mapped near to `target`
* @param target an unmapped logical qubit
*/
virtual void mapToMinDistance(unsigned short source, unsigned short target);

/**
* @brief gathers all qubits that are acted on by a 2-qubit-gate in the given layer in `consideredQubits`,
* and maps any of them that are not yet mapped to a physical qubit.
*
* All gates are mapped in order of their index in the layer. The qubits are mapped to any 2 qubits with minimal distance on the architecture.
*
* Additionally sets the fields `costHeur` (maximum distance between any 2 qubits which share a gate) and `done` (all qubit considered pairs
* are mapped next to each other) in the current search node.
*
* @param layer index of the circuit layer to consider
* @param node current AStar search node
* @param consideredQubits vector in which to gather all relevant qubits of this layer
*/
virtual void mapUnmappedGates(long layer, Node& node, std::vector<unsigned short>& consideredQubits);

/**
* @brief search for an optimal mapping/set of swaps using A*-search and the heuristic specified in `HeuristicMapper::Node::updateHeuristicCost`
*
* uses `HeuristicMapper::nodes` as a priority queue for the A*-search, assumed to be empty (or at least containing only nodes
* compliant with the current layer in their fields `costHeur` and `done`)
*
* @param layer index of the current circuit layer
*/
virtual Node AstarMap(long layer);

/**
* @brief expand the given node by calling `expand_node_add_one_swap` for all possible swaps, which creates new search nodes and adds them to `HeuristicMapper::nodes`
*
* @param consideredQubits set of all qubits that are acted on by a 2-qubit-gate in the respective layer
* @param node current search node
* @param layer index of current circuit layer
*/
void expandNode(const std::vector<unsigned short>& consideredQubits, Node& node, long layer);

/**
* @brief creates a new node with a swap on the given edge and adds it to `HeuristicMapper::nodes`
*
* @param swap edge on which to perform a swap
* @param node current search node
* @param layer index of current circuit layer
*/
void expand_node_add_one_swap(const Edge& swap, Node& node, long layer);

/**
* @brief calculates the heuristic cost for the following layers and saves it in the node as `lookaheadPenalty`
*
* @param layer index of current circuit layer
* @param node search node for which to calculate lookahead penalty
*/
void lookahead(long layer, Node& node);

// TODO: also use in `HeuristicMapper::mapUnmappedGates` and `HeuristicMapper::Node::updateHeuristicCost`
double heuristicCost(double currentCost, double newCost) {
if (results.config.admissibleHeuristic) {
return std::max(currentCost, newCost);
Expand Down
5 changes: 5 additions & 0 deletions src/Mapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,17 @@ void Mapper::createLayers() {
unsigned short target = qc.initialLayout.at(gate->getTargets().at(0));
size_t layer = 0;

// methods of layering described in https://iic.jku.at/files/eda/2019_dac_mapping_quantum_circuits_ibm_architectures_using_minimal_number_swap_h_gates.pdf
switch (config.layering) {
case Layering::IndividualGates:
case Layering::None:
// each gate is put in a new layer
layers.emplace_back();
layers.back().emplace_back(control, target, gate.get());
break;
case Layering::DisjointQubits:
// gates are put in the last layer (from the back of the circuit) in which all of its qubits are not yet used by another gate
// in a circuit diagram this can be thought of shifting all gates as far left as possible and defining each column of gates as one layer
if (singleQubit) {
layer = lastLayer.at(target) + 1;
lastLayer.at(target) = layer;
Expand All @@ -81,6 +85,7 @@ void Mapper::createLayers() {
layers.at(layer).emplace_back(control, target, gate.get());
break;
case Layering::OddGates:
// every other gate is put in a new layer
if (even) {
layers.emplace_back();
layers.back().emplace_back(control, target, gate.get());
Expand Down
7 changes: 7 additions & 0 deletions src/heuristic/HeuristicMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ void HeuristicMapper::mapUnmappedGates(long layer, HeuristicMapper::Node& node,

if (controlLocation == DEFAULT_POSITION && targetLocation == DEFAULT_POSITION) {
std::set<Edge> possibleEdges{};
// gather all edges in the architecture for which both qubits are unmapped
for (const auto& edge: architecture.getCouplingMap()) {
if (qubits.at(edge.first) == DEFAULT_POSITION && qubits.at(edge.second) == DEFAULT_POSITION) {
possibleEdges.emplace(edge);
Expand All @@ -292,6 +293,7 @@ void HeuristicMapper::mapUnmappedGates(long layer, HeuristicMapper::Node& node,
std::pair<unsigned short, unsigned short> chosenEdge;

if (possibleEdges.empty()) {
// map to 2 qubits with minimal distance
double bestScore = std::numeric_limits<int>::max();

for (int i = 0; i < architecture.getNqubits(); i++) {
Expand Down Expand Up @@ -322,9 +324,12 @@ void HeuristicMapper::mapUnmappedGates(long layer, HeuristicMapper::Node& node,
} else if (targetLocation == DEFAULT_POSITION) {
mapToMinDistance(gate.control, gate.target);
}
// TODO: use HeuristicMapper::Node::updateHeuristicCost instead to have heuristic function implementation isolated to one place
node.costHeur = std::max(node.costHeur, distanceOnArchitectureOfLogicalQubits(gate.control, gate.target));
}

// if all considered qubit pairs are mapped next to each other, the mapping is done
// (at most separated by 1 edge possibly directed in the wrong direction requiring 1 reversing gate)
node.done = node.costHeur <= COST_DIRECTION_REVERSE;
}

Expand Down Expand Up @@ -367,6 +372,7 @@ HeuristicMapper::Node HeuristicMapper::AstarMap(long layer) {
Node result = nodes.top();
nodes.pop();

// clear nodes
while (!nodes.empty()) {
nodes.pop();
}
Expand All @@ -381,6 +387,7 @@ void HeuristicMapper::expandNode(const std::vector<unsigned short>& consideredQu
used_swaps.emplace_back(architecture.getNqubits());
}

// set up new teleportation qubits
std::set<Edge> perms = architecture.getCouplingMap();
architecture.getCurrentTeleportations().clear();
architecture.getTeleportationQubits().clear();
Expand Down