Skip to content

Commit

Permalink
Fidelity-aware heuristic and search graph visualization (#384)
Browse files Browse the repository at this point in the history
## Description

Includes:
- a new fidelity-aware heuristic (+some changes to the current heuristic
mapping system making it compatible with the new approach)
- a data logger (logging all search nodes and metadata in the heuristic
mapper)
- a Python visualization module for visualizing search graphs after
mapping
- the option to automatically split layers if the search takes too long
- iterative bidirectional routing (similar to Qiskit's SabreLayout but
using QMAP's routing pass)

Currently, the fidelity-aware heuristic mapping is not yet compatible
with dynamic initial layouting, lookahead or qubit teleportation.
Therefore, I did not yet add an example for fidelity-aware mapping in
Mapping.ipynb in the documentation. (I think advertising this feature
outside the API reference only makes sense once it is more fleshed out)

## Checklist:

<!---
This checklist serves as a reminder of a couple of things that ensure
your pull request will be merged swiftly.
-->

- [x] The pull request only contains commits that are related to it.
- [x] I have added appropriate tests and documentation.
- [x] I have made sure that all CI jobs on GitHub pass.
- [x] The pull request introduces no new warnings and follows the
project's style guidelines.

---------

Co-authored-by: Lukas Burgholzer <[email protected]>
  • Loading branch information
EliasLF and burgholzer authored Dec 22, 2023
1 parent 07642b8 commit b20ddfa
Show file tree
Hide file tree
Showing 33 changed files with 6,069 additions and 448 deletions.
2 changes: 2 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ build:
os: ubuntu-22.04
tools:
python: "3.11"
apt_packages:
- graphviz
jobs:
post_checkout:
# Skip docs build if the commit message contains "skip ci"
Expand Down
31 changes: 31 additions & 0 deletions docs/source/Mapping.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"<style>.widget-subarea{display:none;} /*hide widgets as they do not work with sphinx*/</style>\n",
"\n",
"# Quantum Circuit Mapping\n",
"\n",
"Many quantum computing architectures limit the pairs of qubits that two-qubit operations can be applied to.\n",
Expand Down Expand Up @@ -263,6 +265,35 @@
"\n",
"qc_mapped_w_teleport.draw(output=\"mpl\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Visualizing search graphs\n",
"\n",
"For deeper insight into a specific mapping process one can visualize the search graphs generated during heuristic mapping."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from mqt import qmap\n",
"\n",
"visualizer = qmap.visualization.SearchVisualizer()\n",
"qc_mapped, res = qmap.compile(qc, arch, method=\"heuristic\", visualizer=visualizer)\n",
"visualizer.visualize_search_graph()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There exist a wide range of customizations for these visualizations. Please refer to the [reference documentation](library/Visualization.rst) for more information."
]
}
],
"metadata": {
Expand Down
1 change: 1 addition & 0 deletions docs/source/library/Library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Library
Subarchitectures
Synthesis
SynthesisResults
Visualization
27 changes: 27 additions & 0 deletions docs/source/library/Visualization.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Visualization
=============

.. currentmodule:: mqt.qmap

All visualization functionality is bundled in the sub-module :mod:`mqt.qmap.visualization`.

Search graph visualization
##########################

.. currentmodule:: mqt.qmap.visualization

The recommended way to visualize search graphs of a mapping process is via a :class:`SearchVisualizer` object, which can be passed to the :func:`mqt.qmap.compile` function to enable data logging during mapping, after which this data can be visualized by calling the method :meth:`SearchVisualizer.visualize_search_graph`.

.. note::
:meth:`SearchVisualizer.visualize_search_graph` returns an IPython display object and therefore requires to be executed in a jupyter notebook or jupyter lab environment.

.. note::
Automatic layouting of architecture or search nodes requires `Graphviz <https://graphviz.org/>`_ to be installed (except for the layouting method :code:`walker`). If Graphviz is called without it being installed, it will ensue in an error such as:

:code:`FileNotFoundError: [Errno 2] "sfdp" not found in path.`

Consequently, the only way to use :meth:`SearchVisualizer.visualize_search_graph` without Graphviz is by passing explicit architecture node positions or hiding the architecture graph by passing :code:`show_layout=None`.

.. autoclass:: SearchVisualizer
:special-members: __init__
:members:
137 changes: 134 additions & 3 deletions include/Architecture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,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;

constexpr std::uint16_t MAX_DEVICE_QUBITS = 128;

class Architecture {
public:
class Properties {
Expand Down Expand Up @@ -238,6 +240,10 @@ class Architecture {
createDistanceTable();
}

[[nodiscard]] bool isEdgeConnected(const Edge& edge) const {
return couplingMap.find(edge) != couplingMap.end();
}

CouplingMap& getCurrentTeleportations() { return currentTeleportations; }
std::vector<std::pair<std::int16_t, std::int16_t>>& getTeleportationQubits() {
return teleportationQubits;
Expand All @@ -254,12 +260,127 @@ class Architecture {
createFidelityTable();
}

[[nodiscard]] const Matrix& getFidelityTable() const { return fidelityTable; }
[[nodiscard]] bool isFidelityAvailable() const { return fidelityAvailable; }

[[nodiscard]] const std::vector<Matrix>& getFidelityDistanceTables() const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
return fidelityDistanceTables;
}

[[nodiscard]] const Matrix&
getFidelityDistanceTable(std::size_t skipEdges) const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
if (skipEdges >= fidelityDistanceTables.size()) {
const static Matrix DEFAULT_MATRIX(nqubits,
std::vector<double>(nqubits, 0.0));
return DEFAULT_MATRIX;
}
return fidelityDistanceTables.at(skipEdges);
}

[[nodiscard]] const Matrix& getFidelityDistanceTable() const {
return getFidelityDistanceTable(0);
}

[[nodiscard]] double fidelityDistance(std::uint16_t q1, std::uint16_t q2,
std::size_t skipEdges) const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
if (q1 >= nqubits) {
throw QMAPException("Qubit out of range.");
}
if (q2 >= nqubits) {
throw QMAPException("Qubit out of range.");
}
if (skipEdges >= fidelityDistanceTables.size()) {
return 0.;
}
return fidelityDistanceTables.at(skipEdges).at(q1).at(q2);
}

[[nodiscard]] double fidelityDistance(std::uint16_t q1,
std::uint16_t q2) const {
return fidelityDistance(q1, q2, 0);
}

[[nodiscard]] const Matrix& getFidelityTable() const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
return fidelityTable;
}

[[nodiscard]] const std::vector<double>& getSingleQubitFidelities() const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
return singleQubitFidelities;
}

[[nodiscard]] const std::vector<double>& getSingleQubitFidelityCosts() const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
return singleQubitFidelityCosts;
}

[[nodiscard]] double getSingleQubitFidelityCost(std::uint16_t qbit) const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
if (qbit >= nqubits) {
throw QMAPException("Qubit out of range.");
}
return singleQubitFidelityCosts.at(qbit);
}

[[nodiscard]] const Matrix& getTwoQubitFidelityCosts() const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
return twoQubitFidelityCosts;
}

[[nodiscard]] double getTwoQubitFidelityCost(std::uint16_t q1,
std::uint16_t q2) const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
if (q1 >= nqubits) {
throw QMAPException("Qubit out of range.");
}
if (q2 >= nqubits) {
throw QMAPException("Qubit out of range.");
}
return twoQubitFidelityCosts.at(q1).at(q2);
}

[[nodiscard]] const Matrix& getSwapFidelityCosts() const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
return swapFidelityCosts;
}

[[nodiscard]] double getSwapFidelityCost(std::uint16_t q1,
std::uint16_t q2) const {
if (!fidelityAvailable) {
throw QMAPException("No fidelity data available.");
}
if (q1 >= nqubits) {
throw QMAPException("Qubit out of range.");
}
if (q2 >= nqubits) {
throw QMAPException("Qubit out of range.");
}
return swapFidelityCosts.at(q1).at(q2);
}

[[nodiscard]] bool bidirectional() const { return isBidirectional; }

[[nodiscard]] bool isArchitectureAvailable() const {
Expand All @@ -276,8 +397,13 @@ class Architecture {
distanceTable.clear();
isBidirectional = true;
properties.clear();
fidelityAvailable = false;
fidelityTable.clear();
singleQubitFidelities.clear();
singleQubitFidelityCosts.clear();
twoQubitFidelityCosts.clear();
swapFidelityCosts.clear();
fidelityDistanceTables.clear();
}

[[nodiscard]] double distance(std::uint16_t control,
Expand Down Expand Up @@ -357,9 +483,14 @@ class Architecture {
bool isBidirectional = true;
Matrix distanceTable = {};
std::vector<std::pair<std::int16_t, std::int16_t>> teleportationQubits{};
Properties properties = {};
Matrix fidelityTable = {};
Properties properties = {};
bool fidelityAvailable = false;
Matrix fidelityTable = {};
std::vector<double> singleQubitFidelities = {};
std::vector<double> singleQubitFidelityCosts = {};
Matrix twoQubitFidelityCosts = {};
Matrix swapFidelityCosts = {};
std::vector<Matrix> fidelityDistanceTables = {};

void createDistanceTable();
void createFidelityTable();
Expand Down
81 changes: 81 additions & 0 deletions include/DataLogger.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// This file is part of the MQT QMAP library released under the MIT license.
// See README.md or go to https://github.com/cda-tum/qmap for more information.
//

#pragma once

#include "Architecture.hpp"
#include "Mapper.hpp"
#include "MappingResults.hpp"
#include "QuantumComputation.hpp"

#include <fstream>
#include <string>

class DataLogger {
public:
DataLogger(std::string path, Architecture& arch, qc::QuantumComputation qc)
: dataLoggingPath(std::move(path)), architecture(&arch),
nqubits(arch.getNqubits()), inputCircuit(std::move(qc)) {
initLog();
logArchitecture();
logInputCircuit(inputCircuit);
for (std::size_t i = 0; i < inputCircuit.getNqubits(); ++i) {
qregs.emplace_back("q", "q[" + std::to_string(i) + "]");
}
for (std::size_t i = 0; i < inputCircuit.getNcbits(); ++i) {
cregs.emplace_back("c", "c[" + std::to_string(i) + "]");
}
}

void initLog();
void clearLog();
void logArchitecture();
void logSearchNode(std::size_t layer, std::size_t nodeId,
std::size_t parentId, double costFixed, double costHeur,
double lookaheadPenalty,
const std::array<std::int16_t, MAX_DEVICE_QUBITS>& qubits,
bool validMapping,
const std::vector<std::vector<Exchange>>& swaps,
std::size_t depth);
void logFinalizeLayer(
std::size_t layer, const qc::CompoundOperation& ops,
const std::vector<std::uint16_t>& singleQubitMultiplicity,
const std::map<std::pair<std::uint16_t, std::uint16_t>,
std::pair<std::uint16_t, std::uint16_t>>&
twoQubitMultiplicity,
const std::array<std::int16_t, MAX_DEVICE_QUBITS>& initialLayout,
std::size_t finalNodeId, double finalCostFixed, double finalCostHeur,
double finalLookaheadPenalty,
const std::array<std::int16_t, MAX_DEVICE_QUBITS>& finalLayout,
const std::vector<std::vector<Exchange>>& finalSwaps,
std::size_t finalSearchDepth);
void splitLayer();
void logMappingResult(MappingResults& result);
void logInputCircuit(qc::QuantumComputation& qc) {
if (deactivated) {
return;
}
qc.dump(dataLoggingPath + "/input.qasm", qc::Format::OpenQASM);
};
void logOutputCircuit(qc::QuantumComputation& qc) {
if (deactivated) {
return;
}
qc.dump(dataLoggingPath + "/output.qasm", qc::Format::OpenQASM);
}
void close();

protected:
std::string dataLoggingPath;
Architecture* architecture;
std::uint16_t nqubits;
qc::QuantumComputation inputCircuit;
qc::RegisterNames qregs{};
qc::RegisterNames cregs{};
std::vector<std::ofstream> searchNodesLogFiles; // 1 per layer
bool deactivated = false;

void openNewLayer(std::size_t layer);
};
Loading

0 comments on commit b20ddfa

Please sign in to comment.