From 7f00b847eeb5b77159ddbdcfedccf5b35c32d68c Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Mon, 23 Oct 2023 06:20:34 +0200 Subject: [PATCH 001/108] equivalent to squash commit of fidelityHeuristic2 up to 020c0176 --- docs/source/Mapping.ipynb | 29 + docs/source/library/Library.rst | 1 + docs/source/library/Visualization.rst | 16 + include/Architecture.hpp | 137 +- include/DataLogger.hpp | 76 + include/Mapper.hpp | 5 +- include/MappingResults.hpp | 15 +- include/configuration/Configuration.hpp | 10 +- include/heuristic/HeuristicMapper.hpp | 53 +- include/utils.hpp | 24 + pyproject.toml | 4 + src/Architecture.cpp | 80 +- src/CMakeLists.txt | 2 + src/DataLogger.cpp | 320 +++ src/Mapper.cpp | 23 +- src/heuristic/HeuristicMapper.cpp | 472 +++- src/mqt/qmap/compile.py | 13 + src/mqt/qmap/pyqmap.pyi | 1 + src/mqt/qmap/visualization/__init__.py | 9 + .../qmap/visualization/search_visualizer.py | 156 ++ src/mqt/qmap/visualization/treedraw.py | 341 +++ .../visualization/visualize_search_graph.py | 2003 +++++++++++++++++ src/python/bindings.cpp | 3 + src/utils.cpp | 60 +- test/test_architecture.cpp | 655 ++++++ test/test_general.cpp | 83 + test/test_heuristic.cpp | 879 +++++++- 27 files changed, 5300 insertions(+), 170 deletions(-) create mode 100644 docs/source/library/Visualization.rst create mode 100644 include/DataLogger.hpp create mode 100644 src/DataLogger.cpp create mode 100644 src/mqt/qmap/visualization/__init__.py create mode 100644 src/mqt/qmap/visualization/search_visualizer.py create mode 100644 src/mqt/qmap/visualization/treedraw.py create mode 100644 src/mqt/qmap/visualization/visualize_search_graph.py diff --git a/docs/source/Mapping.ipynb b/docs/source/Mapping.ipynb index a927a9ea8..a1d9c0852 100644 --- a/docs/source/Mapping.ipynb +++ b/docs/source/Mapping.ipynb @@ -263,6 +263,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 API reference for details." + ] } ], "metadata": { diff --git a/docs/source/library/Library.rst b/docs/source/library/Library.rst index 6cd1f4f69..c130a49b4 100644 --- a/docs/source/library/Library.rst +++ b/docs/source/library/Library.rst @@ -10,3 +10,4 @@ Library Subarchitectures Synthesis SynthesisResults + Visualization diff --git a/docs/source/library/Visualization.rst b/docs/source/library/Visualization.rst new file mode 100644 index 000000000..784cb7fad --- /dev/null +++ b/docs/source/library/Visualization.rst @@ -0,0 +1,16 @@ +Visualization +=========================== + +All visualization functionality is bundled in the sub-module :code:`mqt.qmap.visualization`. + +Search graph visualization +############################ + +The recommended way to visualize search graphs of a mapping process is via a :code:`SearchVisualizer` object, which can be passed to the :code:`compile` function when mapping a circuit to enable data logging during mapping, after which this data can be visualized by calling the method :code:`visualize_search_graph` of the :code:`SearchVisualizer` object. +Closing the :code:`SearchVisualizer` object will cleanup the data logged during mapping, which will however also be done automatically when the python process terminates (if not custom data logging path is used). + + .. note:: + :code:`visualize_search_graph` returns an IPython display object and therefore requires to be executed in a jupyter notebook or jupyter lab environment. + + .. currentmodule:: mqt.qmap.visualization + .. autoclass:: SearchVisualizer diff --git a/include/Architecture.hpp b/include/Architecture.hpp index 2ac403b71..e9ad70ad5 100644 --- a/include/Architecture.hpp +++ b/include/Architecture.hpp @@ -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 { @@ -238,6 +240,10 @@ class Architecture { createDistanceTable(); } + bool isEdgeConnected(const Edge& edge) const { + return couplingMap.find(edge) != couplingMap.end(); + } + CouplingMap& getCurrentTeleportations() { return currentTeleportations; } std::vector>& getTeleportationQubits() { return teleportationQubits; @@ -254,12 +260,125 @@ class Architecture { createFidelityTable(); } - [[nodiscard]] const Matrix& getFidelityTable() const { return fidelityTable; } + [[nodiscard]] bool isFidelityAvailable() const { return fidelityAvailable; } + + [[nodiscard]] const std::vector& 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()) { + return zeroMatrix; + } + 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& getSingleQubitFidelities() const { + if (!fidelityAvailable) { + throw QMAPException("No fidelity data available."); + } return singleQubitFidelities; } + [[nodiscard]] const std::vector& 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 { @@ -272,12 +391,18 @@ class Architecture { void reset() { name = ""; nqubits = 0; + zeroMatrix.clear(); couplingMap.clear(); 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, @@ -352,14 +477,20 @@ class Architecture { protected: std::string name; std::uint16_t nqubits = 0; + Matrix zeroMatrix = {}; CouplingMap couplingMap = {}; CouplingMap currentTeleportations = {}; bool isBidirectional = true; Matrix distanceTable = {}; std::vector> teleportationQubits{}; - Properties properties = {}; - Matrix fidelityTable = {}; + Properties properties = {}; + bool fidelityAvailable = false; + Matrix fidelityTable = {}; std::vector singleQubitFidelities = {}; + std::vector singleQubitFidelityCosts = {}; + Matrix twoQubitFidelityCosts = {}; + Matrix swapFidelityCosts = {}; + std::vector fidelityDistanceTables = {}; void createDistanceTable(); void createFidelityTable(); diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp new file mode 100644 index 000000000..115c818ca --- /dev/null +++ b/include/DataLogger.hpp @@ -0,0 +1,76 @@ +// +// 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 +#include + +class DataLogger { +public: + DataLogger(const std::string& path, Architecture& arch, + qc::QuantumComputation& qc) + : dataLoggingPath(path), architecture(arch), inputCircuit(qc) { + nqubits = architecture.getNqubits(); + initLog(); + logArchitecture(architecture); + logInputCircuit(inputCircuit); + for (std::size_t i = 0; i < qc.getNqubits(); ++i) { + qregs.push_back(std::make_pair("q", "q[" + std::to_string(i) + "]")); + } + for (std::size_t i = 0; i < qc.getNcbits(); ++i) { + cregs.push_back(std::make_pair("c", "c[" + std::to_string(i) + "]")); + } + } + + void initLog(); + void clearLog(); + void logArchitecture(Architecture& arch); + void logSearchNode(std::size_t layer, std::size_t nodeId, + std::size_t parentId, double costFixed, double costHeur, + double lookaheadPenalty, + const std::array& qubits, + bool validMapping, + const std::vector>& swaps, + std::size_t depth); + void logFinalizeLayer( + std::size_t layer, const qc::CompoundOperation& ops, + std::vector singleQubitMultiplicity, + std::map, + std::pair> + twoQubitMultiplicity, + const std::array& initialLayout, + std::size_t finalNodeId, double finalCostFixed, double finalCostHeur, + double finalLookaheadPenalty, + const std::array& finalLayout, + const std::vector>& finalSwaps, + std::size_t finalSearchDepth); + void logMappingResult(MappingResults& result); + void logInputCircuit(qc::QuantumComputation& qc) { + qc.dump(dataLoggingPath + "/input.qasm", qc::Format::OpenQASM); + }; + void logOutputCircuit(qc::QuantumComputation& qc) { + qc.dump(dataLoggingPath + "/output.qasm", qc::Format::OpenQASM); + } + // TODO: layering, initial layout + void close(); + +protected: + std::string dataLoggingPath; + Architecture& architecture; + std::uint16_t nqubits; + qc::QuantumComputation& inputCircuit; + qc::RegisterNames qregs{}; + qc::RegisterNames cregs{}; + std::vector searchNodesLogFiles; // 1 per layer + bool deactivated = false; + + void openNewLayer(std::size_t layer); +}; diff --git a/include/Mapper.hpp b/include/Mapper.hpp index 107f30dc1..f75771c50 100644 --- a/include/Mapper.hpp +++ b/include/Mapper.hpp @@ -18,9 +18,8 @@ #include #include -constexpr std::int16_t DEFAULT_POSITION = -1; -constexpr double INITIAL_FIDELITY = 1.0; -constexpr std::uint16_t MAX_DEVICE_QUBITS = 128; +constexpr std::int16_t DEFAULT_POSITION = -1; +constexpr double INITIAL_FIDELITY = 1.0; class Mapper { protected: diff --git a/include/MappingResults.hpp b/include/MappingResults.hpp index 7f4a8cf82..257ae92c0 100644 --- a/include/MappingResults.hpp +++ b/include/MappingResults.hpp @@ -21,6 +21,8 @@ struct MappingResults { std::size_t singleQubitGates = 0; std::size_t cnots = 0; std::size_t layers = 0; + double totalFidelity = 1.; + double totalLogFidelity = 0.; // info in output circuit std::size_t swaps = 0; @@ -90,12 +92,13 @@ struct MappingResults { resultJSON["config"] = config.json(); - auto& stats = resultJSON["statistics"]; - stats["timeout"] = timeout; - stats["mapping_time"] = time; - stats["arch"] = architecture; - stats["layers"] = input.layers; - stats["swaps"] = output.swaps; + auto& stats = resultJSON["statistics"]; + stats["timeout"] = timeout; + stats["mapping_time"] = time; + stats["arch"] = architecture; + stats["layers"] = input.layers; + stats["swaps"] = output.swaps; + stats["total_fidelity"] = output.totalFidelity; if (config.method == Method::Exact) { stats["direction_reverse"] = output.directionReverse; if (config.includeWCNF && !wcnf.empty()) { diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 365943a8c..abfdbea7b 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -25,9 +25,11 @@ struct Configuration { bool postMappingOptimizations = true; bool addMeasurementsToMappedCircuit = true; + bool swapOnFirstLayer = false; - bool verbose = false; - bool debug = false; + bool verbose = false; + bool debug = false; + std::string dataLoggingPath = ""; // map to particular subgraph of architecture (in exact mapper) std::set subgraph{}; @@ -41,7 +43,7 @@ struct Configuration { // lookahead scheme settings bool lookahead = true; bool admissibleHeuristic = true; - bool considerFidelity = true; + bool considerFidelity = false; std::size_t nrLookaheads = 15; double firstLookaheadFactor = 0.75; double lookaheadFactor = 0.5; @@ -74,6 +76,8 @@ struct Configuration { [[nodiscard]] nlohmann::json json() const; [[nodiscard]] std::string toString() const { return json().dump(2); } + bool dataLoggingEnabled() const { return !dataLoggingPath.empty(); } + void setTimeout(const std::size_t sec) { timeout = sec; } [[nodiscard]] bool swapLimitsEnabled() const { return (swapReduction != SwapReduction::None) && enableSwapLimits; diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index 35e0f6f28..d7e730e6e 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -3,6 +3,7 @@ // See README.md or go to https://github.com/cda-tum/qmap for more information. // +#include "DataLogger.hpp" #include "Mapper.hpp" #include "heuristic/UniquePriorityQueue.hpp" @@ -22,6 +23,7 @@ */ using TwoQubitMultiplicity = std::map>; +using SingleQubitMultiplicity = std::vector; class HeuristicMapper : public Mapper { public: @@ -76,13 +78,19 @@ class HeuristicMapper : public Mapper { std::size_t nswaps = 0; /** depth in search tree (starting with 0 at the root) */ std::size_t depth = 0; - - Node() = default; - Node(const std::array& q, + /** gates (pair of logical qubits) currently mapped next to each other */ + std::set validMappedTwoQubitGates = {}; + std::size_t parent = 0; + std::size_t id; + + Node(std::size_t nodeId) : id(nodeId){}; + Node(std::size_t nodeId, std::size_t parentId, + const std::array& q, const std::array& loc, const std::vector>& sw = {}, const double initCostFixed = 0, const std::size_t searchDepth = 0) - : costFixed(initCostFixed), depth(searchDepth) { + : costFixed(initCostFixed), depth(searchDepth), parent(parentId), + id(nodeId) { std::copy(q.begin(), q.end(), qubits.begin()); std::copy(loc.begin(), loc.end(), locations.begin()); std::copy(sw.begin(), sw.end(), std::back_inserter(swaps)); @@ -106,7 +114,10 @@ class HeuristicMapper : public Mapper { * @brief applies an in-place swap of 2 qubits in `qubits` and `locations` * of the node */ - void applySWAP(const Edge& swap, Architecture& arch); + void applySWAP(const Edge& swap, Architecture& arch, + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity, + bool considerFidelity); /** * @brief applies an in-place teleportation of 2 qubits in `qubits` and @@ -121,7 +132,11 @@ class HeuristicMapper : public Mapper { * @param arch the architecture for calculating distances between physical * qubits and supplying qubit information such as fidelity */ - void recalculateFixedCost(const Architecture& arch); + void recalculateFixedCost( + const Architecture& arch, + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity, + bool considerFidelity); /** * @brief calculates the heuristic cost of the current mapping in the node @@ -137,10 +152,12 @@ class HeuristicMapper : public Mapper { * such that it is admissible (i.e. A*-search should yield the optimal * solution using this heuristic) */ - void - updateHeuristicCost(const Architecture& arch, - const TwoQubitMultiplicity& twoQubitGateMultiplicity, - bool admissibleHeuristic); + void updateHeuristicCost( + const Architecture& arch, + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity, + const std::unordered_set& consideredQubits, + bool admissibleHeuristic, bool considerFidelity); std::ostream& print(std::ostream& out) const { out << "{\n"; @@ -157,6 +174,8 @@ class HeuristicMapper : public Mapper { protected: UniquePriorityQueue nodes{}; + DataLogger* dataLogger; + std::size_t nextNodeId = 0; /** * @brief creates an initial mapping of logical qubits to physical qubits with @@ -210,7 +229,8 @@ class HeuristicMapper : public Mapper { * of logical qubits in the current layer */ virtual void - mapUnmappedGates(const TwoQubitMultiplicity& twoQubitGateMultiplicity); + mapUnmappedGates(const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity); /** * @brief search for an optimal mapping/set of swaps using A*-search and the @@ -238,7 +258,8 @@ class HeuristicMapper : public Mapper { */ void expandNode(const std::unordered_set& consideredQubits, Node& node, std::size_t layer, - const TwoQubitMultiplicity& twoQubitGateMultiplicity); + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity); /** * @brief creates a new node with a swap on the given edge and adds it to @@ -250,9 +271,11 @@ class HeuristicMapper : public Mapper { * @param twoQubitGateMultiplicity number of two qubit gates acting on pairs * of logical qubits in the current layer */ - void - expandNodeAddOneSwap(const Edge& swap, Node& node, std::size_t layer, - const TwoQubitMultiplicity& twoQubitGateMultiplicity); + void expandNodeAddOneSwap( + const Edge& swap, Node& node, std::size_t layer, + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity, + const std::unordered_set& consideredQubits); /** * @brief calculates the heuristic cost for the following layers and saves it diff --git a/include/utils.hpp b/include/utils.hpp index bcdf4796c..3579d88fb 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -73,6 +73,9 @@ class Dijkstra { * logical qubits from one physical qubit to/next to another (along the * cheapest path) * + * e.g. cost of moving qubit q1 onto q2: + * distanceTable[q1][q2] + * * @param n size of the distance table (i.e. number of qubits) * @param couplingMap coupling map specifying all edges in the architecture * @param distanceTable target table @@ -89,6 +92,27 @@ class Dijkstra { static void buildTable(std::uint16_t n, const CouplingMap& couplingMap, Matrix& distanceTable, const Matrix& edgeWeights, double reversalCost, bool removeLastEdge); + /** + * @brief builds a 3d matrix containing the distance tables giving the minimal + * distances between 2 qubit when upto k edges can be skipped. + * + * e.g. cost of moving qubit q1 onto q2 skipping upto 3 edges: + * edgeSkipDistanceTable[3][q1][q2] + * + * if k > edgeSkipDistanceTable.size() a cost of 0 can be assumed + * + * this implementation does not work with distance tables containing CNOT + * reversal costs or the last edge removed + * (only pure distances between 2 qubits) + * + * @param distanceTable 2d matrix containing pure distances between any 2 + * qubits: distanceTable[source][target] + * @param couplingMap coupling map specifying all edges in the architecture + * @param edgeSkipDistanceTable 3d target table + */ + static void buildEdgeSkipTable(const Matrix& distanceTable, + const CouplingMap& couplingMap, + std::vector& edgeSkipDistanceTable); protected: static void dijkstra(const CouplingMap& couplingMap, std::vector& nodes, diff --git a/pyproject.toml b/pyproject.toml index e8956a824..a4f2a76bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,10 @@ dependencies = [ "rustworkx[all]>=0.13.0", "importlib_resources>=5.0; python_version < '3.10'", "typing_extensions>=4.0", + "distinctipy>=1.2.2", + "plotly>=5.15.0", + "networkx==2.5", + "ipywidgets>=7.6.5", ] dynamic = ["version"] diff --git a/src/Architecture.cpp b/src/Architecture.cpp index c0f11473d..1ec008bdd 100644 --- a/src/Architecture.cpp +++ b/src/Architecture.cpp @@ -44,6 +44,8 @@ void Architecture::loadCouplingMap(std::istream&& is) { if (std::getline(is, line)) { if (std::regex_search(line, m, rNqubits)) { nqubits = static_cast(std::stoul(m.str(1))); + zeroMatrix.clear(); + zeroMatrix.resize(nqubits, std::vector(nqubits, 0.0)); } else { throw QMAPException("No qubit count found in coupling map file: " + line); } @@ -65,7 +67,9 @@ void Architecture::loadCouplingMap(std::istream&& is) { } void Architecture::loadCouplingMap(std::uint16_t nQ, const CouplingMap& cm) { - nqubits = nQ; + nqubits = nQ; + zeroMatrix.clear(); + zeroMatrix.resize(nqubits, std::vector(nqubits, 0.0)); couplingMap = cm; properties.clear(); name = "generic_" + std::to_string(nQ); @@ -153,6 +157,8 @@ void Architecture::loadProperties(std::istream&& is) { properties.setNqubits(qubitNumber); if (!isArchitectureAvailable()) { nqubits = qubitNumber; + zeroMatrix.clear(); + zeroMatrix.resize(nqubits, std::vector(nqubits, 0.0)); createDistanceTable(); } @@ -167,7 +173,9 @@ void Architecture::loadProperties(const Properties& props) { } } nqubits = props.getNqubits(); - name = "generic_" + std::to_string(nqubits); + zeroMatrix.clear(); + zeroMatrix.resize(nqubits, std::vector(nqubits, 0.0)); + name = "generic_" + std::to_string(nqubits); createDistanceTable(); } properties = props; @@ -186,8 +194,9 @@ Architecture::Architecture(const std::uint16_t nQ, const CouplingMap& cm, void Architecture::createDistanceTable() { isBidirectional = true; - Matrix edgeWeights(nqubits, std::vector( - nqubits, std::numeric_limits::max())); + Matrix edgeWeights( + nqubits, + std::vector(nqubits, std::numeric_limits::infinity())); for (const auto& edge : couplingMap) { if (couplingMap.find({edge.second, edge.first}) == couplingMap.end()) { isBidirectional = false; @@ -203,22 +212,71 @@ void Architecture::createDistanceTable() { } void Architecture::createFidelityTable() { + fidelityAvailable = true; fidelityTable.clear(); fidelityTable.resize(nqubits, std::vector(nqubits, 0.0)); + twoQubitFidelityCosts.clear(); + twoQubitFidelityCosts.resize( + nqubits, + std::vector(nqubits, std::numeric_limits::infinity())); + swapFidelityCosts.clear(); + swapFidelityCosts.resize( + nqubits, + std::vector(nqubits, std::numeric_limits::infinity())); singleQubitFidelities.resize(nqubits, 1.0); - - for (const auto& [first, second] : couplingMap) { - if (properties.twoQubitErrorRateAvailable(first, second)) { - fidelityTable[first][second] = - 1.0 - properties.getTwoQubitErrorRate(first, second); - } - } + singleQubitFidelityCosts.resize(nqubits, 0.0); for (const auto& [qubit, operationProps] : properties.singleQubitErrorRate.get()) { singleQubitFidelities[qubit] = 1.0 - properties.getAverageSingleQubitErrorRate(qubit); + singleQubitFidelityCosts[qubit] = -std::log2(singleQubitFidelities[qubit]); + } + + for (const auto& [first, second] : couplingMap) { + if (properties.twoQubitErrorRateAvailable(first, second)) { + fidelityTable[first][second] = + 1.0 - properties.getTwoQubitErrorRate(first, second); + twoQubitFidelityCosts[first][second] = + -std::log2(fidelityTable[first][second]); + if (couplingMap.find({second, first}) == couplingMap.end()) { + // CNOT reversal (unidirectional edge q1 -> q2): + // CX(q2,q1) = H(q1) H(q2) CX(q1,q2) H(q1) H(q2) + twoQubitFidelityCosts[second][first] = + twoQubitFidelityCosts[first][second] + + 2 * singleQubitFidelityCosts[first] + + 2 * singleQubitFidelityCosts[second]; + // SWAP decomposition (unidirectional edge q1 -> q2): + // SWAP(q1,q2) = CX(q1,q2) H(q1) H(q2) CX(q1,q2) H(q1) H(q2) CX(q1,q2) + swapFidelityCosts[first][second] = + 3 * twoQubitFidelityCosts[first][second] + + 2 * singleQubitFidelityCosts[first] + + 2 * singleQubitFidelityCosts[second]; + swapFidelityCosts[second][first] = swapFidelityCosts[first][second]; + } else { + // SWAP decomposition (bidirectional edge q1 <-> q2): + // SWAP(q1,q2) = CX(q1,q2) CX(q2,q1) CX(q1,q2) + swapFidelityCosts[first][second] = + 3 * twoQubitFidelityCosts[first][second]; + } + } else { + fidelityAvailable = false; + fidelityTable.clear(); + singleQubitFidelities.clear(); + twoQubitFidelityCosts.clear(); + swapFidelityCosts.clear(); + return; + } + } + + fidelityDistanceTables.clear(); + if (fidelityAvailable) { + Matrix distances = {}; + Dijkstra::buildTable(nqubits, couplingMap, distances, swapFidelityCosts, 0., + false); + Dijkstra::buildEdgeSkipTable(distances, couplingMap, + fidelityDistanceTables); } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 65b087cb2..cca697ca8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,12 +36,14 @@ macro(ADD_QMAP_LIBRARY libname srcfile) ${PROJECT_SOURCE_DIR}/include/${libname}/${srcfile}.hpp ${PROJECT_SOURCE_DIR}/include/Architecture.hpp ${PROJECT_SOURCE_DIR}/include/configuration + ${PROJECT_SOURCE_DIR}/include/DataLogger.hpp ${PROJECT_SOURCE_DIR}/include/heuristic/UniquePriorityQueue.hpp ${PROJECT_SOURCE_DIR}/include/Mapper.hpp ${PROJECT_SOURCE_DIR}/include/MappingResults.hpp ${PROJECT_SOURCE_DIR}/include/utils.hpp Architecture.cpp configuration/Configuration.cpp + DataLogger.cpp Mapper.cpp utils.cpp) diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp new file mode 100644 index 000000000..40921452d --- /dev/null +++ b/src/DataLogger.cpp @@ -0,0 +1,320 @@ +// +// 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. +// + +#include "DataLogger.hpp" + +#include "nlohmann/json.hpp" + +#include + +void DataLogger::initLog() { + if (dataLoggingPath.back() != '/') { + dataLoggingPath += '/'; + } + std::filesystem::path dirPath(dataLoggingPath); + if (!std::filesystem::exists(dirPath)) { + std::filesystem::create_directory(dirPath); + } + clearLog(); +}; + +void DataLogger::clearLog() { + for (const auto& entry : + std::filesystem::directory_iterator(dataLoggingPath)) { + std::filesystem::remove_all(entry.path()); + } +}; + +void DataLogger::logArchitecture(Architecture& arch) { + if (deactivated) { + return; + } + + auto of = std::ofstream(dataLoggingPath + "architecture.json"); + if (!of.good()) { + deactivated = true; + std::cerr << "[data-logging] Error opening file: " << dataLoggingPath + << "architecture.json" << std::endl; + return; + } + nlohmann::json json; + json["name"] = arch.getName(); + json["nqubits"] = arch.getNqubits(); + json["coupling_map"] = arch.getCouplingMap(); + json["distances"] = arch.getDistanceTable(); + if (arch.isFidelityAvailable()) { + auto& fidelity = json["fidelity"]; + fidelity["single_qubit_fidelities"] = arch.getSingleQubitFidelities(); + fidelity["two_qubit_fidelities"] = arch.getFidelityTable(); + fidelity["single_qubit_fidelity_costs"] = + arch.getSingleQubitFidelityCosts(); + fidelity["two_qubit_fidelity_costs"] = arch.getTwoQubitFidelityCosts(); + fidelity["swap_fidelity_costs"] = arch.getSwapFidelityCosts(); + fidelity["fidelity_distances"] = arch.getFidelityDistanceTables(); + } + of << json.dump(2); + of.close(); +}; + +void DataLogger::openNewLayer(std::size_t layer) { + if (deactivated) { + return; + } + + for (std::size_t i = searchNodesLogFiles.size(); i <= layer; ++i) { + searchNodesLogFiles.push_back(std::ofstream( + dataLoggingPath + "nodes_layer_" + std::to_string(i) + ".csv")); + if (!searchNodesLogFiles.at(i).good()) { + deactivated = true; + std::cerr << "[data-logging] Error opening file: " << dataLoggingPath + << "layer_" << i << ".json" << std::endl; + return; + } + } +}; + +void DataLogger::logFinalizeLayer( + std::size_t layer, const qc::CompoundOperation& ops, + std::vector singleQubitMultiplicity, + std::map, + std::pair> + twoQubitMultiplicity, + const std::array& initialLayout, + std::size_t finalNodeId, double finalCostFixed, double finalCostHeur, + double finalLookaheadPenalty, + const std::array& finalLayout, + const std::vector>& finalSwaps, + std::size_t finalSearchDepth) { + if (deactivated) { + return; + } + + if (!searchNodesLogFiles.at(layer).is_open()) { + std::cerr << "[data-logging] Error: layer " << layer + << " has already been finalized" << std::endl; + return; + } + searchNodesLogFiles.at(layer).close(); + + auto of = std::ofstream(dataLoggingPath + "layer_" + std::to_string(layer) + + ".json"); + if (!of.good()) { + deactivated = true; + std::cerr << "[data-logging] Error opening file: " << dataLoggingPath + << "layer_" << layer << ".json" << std::endl; + return; + } + nlohmann::json json; + std::stringstream qasmStream; + ops.dumpOpenQASM(qasmStream, qregs, cregs); + json["qasm"] = qasmStream.str(); + if (twoQubitMultiplicity.empty()) { + json["two_qubit_multiplicity"] = nlohmann::json::array(); + } else { + auto& twoMultJSON = json["two_qubit_multiplicity"]; + std::size_t j = 0; + for (const auto& [qubits, multiplicity] : twoQubitMultiplicity) { + twoMultJSON[j]["q1"] = qubits.first; + twoMultJSON[j]["q2"] = qubits.second; + twoMultJSON[j]["forward"] = multiplicity.first; + twoMultJSON[j]["backward"] = multiplicity.second; + ++j; + } + } + json["single_qubit_multiplicity"] = singleQubitMultiplicity; + auto& initialLayoutJSON = json["initial_layout"]; + for (std::size_t i = 0; i < nqubits; ++i) { + initialLayoutJSON[i] = initialLayout.at(i); + } + json["final_node_id"] = finalNodeId; + json["final_cost_fixed"] = finalCostFixed; + json["final_cost_heur"] = finalCostHeur; + json["final_lookahead_penalty"] = finalLookaheadPenalty; + auto& finalLayoutJSON = json["final_layout"]; + for (std::size_t i = 0; i < nqubits; ++i) { + finalLayoutJSON[i] = finalLayout.at(i); + } + if (finalSwaps.empty()) { + json["final_swaps"] = nlohmann::json::array(); + } else { + auto& finalSwapsJSON = json["final_swaps"]; + for (const auto& swaps : finalSwaps) { + std::size_t i = 0; + for (const auto& swap : swaps) { + finalSwapsJSON[i][0] = swap.first; + finalSwapsJSON[i][1] = swap.second; + ++i; + } + } + } + json["final_search_depth"] = finalSearchDepth; + of << json.dump(2); + of.close(); +}; + +void DataLogger::logSearchNode( + std::size_t layer, std::size_t nodeId, std::size_t parentId, + double costFixed, double costHeur, double lookaheadPenalty, + const std::array& qubits, + bool validMapping, const std::vector>& swaps, + std::size_t depth) { + if (deactivated) { + return; + } + + if (layer >= searchNodesLogFiles.size()) { + openNewLayer(layer); + } + + auto& of = searchNodesLogFiles.at(layer); + if (!of.is_open()) { + deactivated = true; + std::cerr << "[data-logging] Error: layer " << layer + << " has already been finalized" << std::endl; + return; + } + of << nodeId << ";" << parentId << ";" << costFixed << ";" << costHeur << ";" + << lookaheadPenalty << ";" << validMapping << ";" << depth << ";"; + for (std::size_t i = 0; i < nqubits; ++i) { + of << qubits.at(i) << ","; + } + if (nqubits > 0) { + of.seekp(-1, of.cur); // remove last comma + } + of << ";"; + for (const auto& sw : swaps) { + for (const auto& s : sw) { + of << s.first << " " << s.second; + if (s.op != qc::OpType::SWAP) { + of << " " << s.op; + if (s.middleAncilla != + std::numeric_limits::max()) { + of << " " << s.middleAncilla; + } + } + of << ","; + } + } + if (swaps.size() > 0) { + of.seekp(-1, of.cur); // remove last comma + } + of << std::endl; +}; + +void DataLogger::logMappingResult(MappingResults& result) { + if (deactivated) { + return; + } + + auto of = std::ofstream(dataLoggingPath + "mapping_result.json"); + if (!of.good()) { + deactivated = true; + std::cerr << "[data-logging] Error opening file: " << dataLoggingPath + << "mapping_result.json" << std::endl; + return; + } + nlohmann::json json; + auto& circuit = json["input_circuit"]; + circuit["name"] = result.input.name; + circuit["qubits"] = result.input.qubits; + circuit["gates"] = result.input.gates; + circuit["single_qubit_gates"] = result.input.singleQubitGates; + circuit["cnots"] = result.input.cnots; + + auto& mappedCirc = json["output_circuit"]; + mappedCirc["name"] = result.output.name; + mappedCirc["qubits"] = result.output.qubits; + mappedCirc["gates"] = result.output.gates; + mappedCirc["single_qubit_gates"] = result.output.singleQubitGates; + mappedCirc["cnots"] = result.output.cnots; + + auto& config = json["config"]; + config["method"] = toString(result.config.method); + config["pre_mapping_optimizations"] = result.config.preMappingOptimizations; + config["post_mapping_optimizations"] = result.config.postMappingOptimizations; + config["add_measurements_to_mapped_circuit"] = + result.config.addMeasurementsToMappedCircuit; + config["layering"] = toString(result.config.layering); + config["initial_layout"] = toString(result.config.initialLayout); + if (result.config.useTeleportation) { + auto& teleportation = config["teleportation"]; + teleportation["qubits"] = result.config.teleportationQubits; + teleportation["seed"] = result.config.teleportationSeed; + teleportation["fake"] = result.config.teleportationFake; + } else { + config["teleportation"] = false; + } + config["timeout"] = result.config.timeout; + + auto& stats = json["statistics"]; + stats["timeout"] = result.timeout; + stats["mapping_time"] = result.time; + stats["layers"] = result.input.layers; + stats["swaps"] = result.output.swaps; + stats["total_fidelity"] = result.output.totalFidelity; + stats["total_log_fidelity"] = result.output.totalLogFidelity; + stats["additional_gates"] = + static_cast>( + result.output.gates) - + static_cast>( + result.input.gates); + + if (result.config.method == Method::Exact) { + config["encoding"] = result.config.encoding; + config["commander_grouping"] = result.config.commanderGrouping; + config["subgraph"] = result.config.subgraph; + config["use_subsets"] = result.config.useSubsets; + + stats["direction_reverse"] = result.output.directionReverse; + if (result.config.includeWCNF && !result.wcnf.empty()) { + stats["WCNF"] = result.wcnf; + } + } else if (result.config.method == Method::Heuristic) { + if (result.config.lookahead) { + auto& lookahead = config["lookahead"]; + lookahead["nr_lookaheads"] = result.config.nrLookaheads; + lookahead["first_factor"] = result.config.firstLookaheadFactor; + lookahead["factor"] = result.config.lookaheadFactor; + } else { + config["lookahead"] = false; + } + config["admissible_heuristic"] = result.config.admissibleHeuristic; + config["consider_fidelity"] = result.config.considerFidelity; + + stats["teleportations"] = result.output.teleportations; + auto& benchmark = stats["benchmark"]; + benchmark["expanded_nodes"] = result.heuristicBenchmark.expandedNodes; + benchmark["generated_nodes"] = result.heuristicBenchmark.generatedNodes; + benchmark["time_per_node"] = result.heuristicBenchmark.timePerNode; + benchmark["average_branching_factor"] = + result.heuristicBenchmark.averageBranchingFactor; + benchmark["effective_branching_factor"] = + result.heuristicBenchmark.effectiveBranchingFactor; + for (std::size_t i = 0; i < result.layerHeuristicBenchmark.size(); ++i) { + auto& layerBenchmark = result.layerHeuristicBenchmark.at(i); + auto& jsonLayerBenchmark = benchmark["layers"][i]; + jsonLayerBenchmark["expanded_nodes"] = layerBenchmark.expandedNodes; + jsonLayerBenchmark["generated_nodes"] = layerBenchmark.generatedNodes; + jsonLayerBenchmark["solution_depth"] = layerBenchmark.solutionDepth; + jsonLayerBenchmark["time_per_node"] = layerBenchmark.timePerNode; + jsonLayerBenchmark["average_branching_factor"] = + layerBenchmark.averageBranchingFactor; + jsonLayerBenchmark["effective_branching_factor"] = + layerBenchmark.effectiveBranchingFactor; + } + } + of << json.dump(2); + of.close(); +}; + +void DataLogger::close() { + for (std::size_t i = 0; i < searchNodesLogFiles.size(); ++i) { + if (searchNodesLogFiles.at(i).is_open()) { + std::cerr << "[data-logging] Error: layer " << i << " was not finalized" + << std::endl; + searchNodesLogFiles.at(i).close(); + } + } +} diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 37495b53e..9de9443c2 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -299,10 +299,20 @@ void Mapper::countGates(decltype(qcMapped.cbegin()) it, if (g->isStandardOperation()) { if (g->getType() == qc::SWAP) { - if (architecture.bidirectional()) { + auto q1 = static_cast(g->getTargets()[0]); + auto q2 = static_cast(g->getTargets()[1]); + if (architecture.isFidelityAvailable()) { + info.totalLogFidelity += architecture.getSwapFidelityCost(q1, q2); + } + if (architecture.getCouplingMap().find({q1, q2}) != + architecture.getCouplingMap().end() && + architecture.getCouplingMap().find({q2, q1}) != + architecture.getCouplingMap().end()) { + // bidirectional edge info.gates += GATES_OF_BIDIRECTIONAL_SWAP; info.cnots += GATES_OF_BIDIRECTIONAL_SWAP; } else { + // unidirectional edge info.gates += GATES_OF_UNIDIRECTIONAL_SWAP; info.cnots += GATES_OF_BIDIRECTIONAL_SWAP; info.singleQubitGates += GATES_OF_DIRECTION_REVERSE; @@ -310,10 +320,20 @@ void Mapper::countGates(decltype(qcMapped.cbegin()) it, } else if (g->getControls().empty()) { ++info.singleQubitGates; ++info.gates; + auto q1 = static_cast(g->getTargets()[0]); + if (architecture.isFidelityAvailable()) { + info.totalLogFidelity += architecture.getSingleQubitFidelityCost(q1); + } } else { assert(g->getType() == qc::X); ++info.cnots; ++info.gates; + auto q1 = + static_cast((*(g->getControls().begin())).qubit); + auto q2 = static_cast(g->getTargets()[0]); + if (architecture.isFidelityAvailable()) { + info.totalLogFidelity += architecture.getTwoQubitFidelityCost(q1, q2); + } } continue; } @@ -323,4 +343,5 @@ void Mapper::countGates(decltype(qcMapped.cbegin()) it, countGates(cg->cbegin(), cg->cend(), info); } } + info.totalFidelity = std::pow(2, -info.totalLogFidelity); } diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 4a79a98e6..63658d515 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -8,6 +8,10 @@ #include void HeuristicMapper::map(const Configuration& configuration) { + if (configuration.dataLoggingEnabled()) { + dataLogger = + new DataLogger(configuration.dataLoggingPath, architecture, qc); + } results = MappingResults{}; results.config = configuration; auto& config = results.config; @@ -17,6 +21,34 @@ void HeuristicMapper::map(const Configuration& configuration) { << " not suitable for heuristic mapper!" << std::endl; return; } + if (config.considerFidelity && !architecture.isFidelityAvailable()) { + std::cerr << "No calibration data available for this architecture! " + << "Performing mapping without considering fidelity." + << std::endl; + config.considerFidelity = false; + } + if (config.considerFidelity && config.lookahead) { + std::cerr << "Lookahead is not yet supported for heuristic mapper using " + "fidelity-aware mapping! Performing mapping without " + "using lookahead." + << std::endl; + config.lookahead = false; + } + if (config.considerFidelity && + config.initialLayout == InitialLayout::Dynamic) { + std::cerr << "Initial layout strategy " << toString(config.initialLayout) + << " not yet supported for heuristic mapper using fidelity-aware " + "mapping! Mapping aborted." + << std::endl; + return; + } + if (config.considerFidelity && config.teleportationQubits > 0) { + std::cerr + << "Teleportation is not yet supported for heuristic mapper using " + "fidelity-aware mapping! Performing mapping without teleportation." + << std::endl; + config.teleportationQubits = 0; + } const auto start = std::chrono::steady_clock::now(); initResults(); @@ -50,7 +82,7 @@ void HeuristicMapper::map(const Configuration& configuration) { } // initial layer needs no swaps - if (i != 0) { + if (i != 0 || config.swapOnFirstLayer) { for (const auto& swaps : result.swaps) { for (const auto& swap : swaps) { if (swap.op == qc::SWAP) { @@ -58,10 +90,8 @@ void HeuristicMapper::map(const Configuration& configuration) { std::clog << "SWAP: " << swap.first << " <-> " << swap.second << "\n"; } - if (architecture.getCouplingMap().find({swap.first, swap.second}) == - architecture.getCouplingMap().end() && - architecture.getCouplingMap().find({swap.second, swap.first}) == - architecture.getCouplingMap().end()) { + if (!architecture.isEdgeConnected({swap.first, swap.second}) && + !architecture.isEdgeConnected({swap.second, swap.first})) { throw QMAPException( "Invalid SWAP: " + std::to_string(swap.first) + "<->" + std::to_string(swap.second)); @@ -112,11 +142,9 @@ void HeuristicMapper::map(const Configuration& configuration) { const Edge cnot = { locations.at(static_cast(gate.control)), locations.at(gate.target)}; - if (architecture.getCouplingMap().find(cnot) == - architecture.getCouplingMap().end()) { + if (!architecture.isEdgeConnected(cnot)) { const Edge reverse = {cnot.second, cnot.first}; - if (architecture.getCouplingMap().find(reverse) == - architecture.getCouplingMap().end()) { + if (!architecture.isEdgeConnected(reverse)) { throw QMAPException( "Invalid CNOT: " + std::to_string(reverse.first) + "-" + std::to_string(reverse.second)); @@ -236,6 +264,11 @@ void HeuristicMapper::map(const Configuration& configuration) { const std::chrono::duration diff = end - start; results.time = diff.count(); results.timeout = false; + + if (config.dataLoggingEnabled()) { + dataLogger->logOutputCircuit(qcMapped); + dataLogger->logMappingResult(results); + } } void HeuristicMapper::staticInitialMapping() { @@ -344,7 +377,28 @@ void HeuristicMapper::createInitialMapping() { } void HeuristicMapper::mapUnmappedGates( - const TwoQubitMultiplicity& twoQubitGateMultiplicity) { + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity) { + if (results.config.considerFidelity) { + for (std::uint16_t q = 0; q < singleQubitGateMultiplicity.size(); ++q) { + if (singleQubitGateMultiplicity.at(q) == 0) { + continue; + } + if (locations.at(q) == DEFAULT_POSITION) { + // TODO: consider fidelity + // map to first free physical qubit + for (std::uint16_t phys_q = 0; phys_q < architecture.getNqubits(); + ++phys_q) { + if (qubits.at(phys_q) == -1) { + locations.at(q) = static_cast(phys_q); + qubits.at(phys_q) = static_cast(q); + break; + } + } + } + } + } + for (const auto& [logEdge, _] : twoQubitGateMultiplicity) { const auto& [q1, q2] = logEdge; @@ -426,12 +480,30 @@ void HeuristicMapper::mapToMinDistance(const std::uint16_t source, } HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { + nextNodeId = 0; + std::unordered_set consideredQubits{}; - Node node{}; - TwoQubitMultiplicity twoQubitGateMultiplicity{}; + Node node(nextNodeId++); + // number of single qubit gates acting on each logical qubit in the current + // layer + SingleQubitMultiplicity singleQubitGateMultiplicity(architecture.getNqubits(), + 0); + // number of two qubit gates acting on each logical qubit edge in the current + // layer where the first number in the value pair corresponds to the number of + // edges having their gates given as (control, target) in the key, and the + // second with all gates in reverse to that + TwoQubitMultiplicity twoQubitGateMultiplicity{}; + Node bestDoneNode(0); + bool done = false; + bool considerFidelity = results.config.considerFidelity; for (const auto& gate : layers.at(layer)) { - if (!gate.singleQubit()) { + if (gate.singleQubit()) { + singleQubitGateMultiplicity.at(gate.target)++; + if (considerFidelity) { + consideredQubits.emplace(gate.target); + } + } else { consideredQubits.emplace(gate.control); consideredQubits.emplace(gate.target); if (gate.control >= gate.target) { @@ -456,14 +528,23 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { } } - mapUnmappedGates(twoQubitGateMultiplicity); + mapUnmappedGates(singleQubitGateMultiplicity, twoQubitGateMultiplicity); node.locations = locations; node.qubits = qubits; - node.recalculateFixedCost(architecture); - node.updateHeuristicCost(architecture, twoQubitGateMultiplicity, - results.config.admissibleHeuristic); - + node.recalculateFixedCost(architecture, singleQubitGateMultiplicity, + twoQubitGateMultiplicity, + results.config.considerFidelity); + node.updateHeuristicCost(architecture, singleQubitGateMultiplicity, + twoQubitGateMultiplicity, consideredQubits, + results.config.admissibleHeuristic, + results.config.considerFidelity); + + if (results.config.dataLoggingEnabled()) { + dataLogger->logSearchNode(layer, node.id, node.parent, node.costFixed, + node.costHeur, node.lookaheadPenalty, node.qubits, + node.done, node.swaps, node.depth); + } nodes.push(node); const auto& debug = results.config.debug; @@ -474,10 +555,22 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { auto& totalExpandedNodes = results.heuristicBenchmark.expandedNodes; auto layerResultsIt = results.layerHeuristicBenchmark.rbegin(); - while (!nodes.top().done) { + while (!nodes.empty() && (!done || nodes.top().getTotalCost() < + bestDoneNode.getTotalFixedCost())) { Node current = nodes.top(); + if (current.done) { + if (!done || + current.getTotalFixedCost() < bestDoneNode.getTotalFixedCost()) { + bestDoneNode = current; + } + done = true; + if (!considerFidelity) { + break; + } + } nodes.pop(); - expandNode(consideredQubits, current, layer, twoQubitGateMultiplicity); + expandNode(consideredQubits, current, layer, singleQubitGateMultiplicity, + twoQubitGateMultiplicity); if (debug) { ++totalExpandedNodes; @@ -485,7 +578,11 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { } } - Node result = nodes.top(); + if (!done) { + throw QMAPException("No viable mapping found."); + } + + Node result = bestDoneNode; if (debug) { const auto end = std::chrono::steady_clock::now(); @@ -510,6 +607,19 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { layerResultsIt->expandedNodes + 1, result.depth); } + if (results.config.dataLoggingEnabled()) { + qc::CompoundOperation compOp(architecture.getNqubits()); + for (const auto& gate : layers.at(layer)) { + std::unique_ptr op = gate.op->clone(); + compOp.emplace_back(op); + } + + dataLogger->logFinalizeLayer( + layer, compOp, singleQubitGateMultiplicity, twoQubitGateMultiplicity, + qubits, result.id, result.costFixed, result.costHeur, + result.lookaheadPenalty, result.qubits, result.swaps, result.depth); + } + // clear nodes while (!nodes.empty()) { nodes.pop(); @@ -520,7 +630,9 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { void HeuristicMapper::expandNode( const std::unordered_set& consideredQubits, Node& node, - std::size_t layer, const TwoQubitMultiplicity& twoQubitGateMultiplicity) { + std::size_t layer, + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity) { std::vector> usedSwaps; usedSwaps.reserve(architecture.getNqubits()); for (int p = 0; p < architecture.getNqubits(); ++p) { @@ -579,14 +691,16 @@ void HeuristicMapper::expandNode( auto q1 = node.qubits.at(edge.first); auto q2 = node.qubits.at(edge.second); if (q2 == -1 || q1 == -1) { - expandNodeAddOneSwap(edge, node, layer, twoQubitGateMultiplicity); + expandNodeAddOneSwap(edge, node, layer, singleQubitGateMultiplicity, + twoQubitGateMultiplicity, consideredQubits); } else if (!usedSwaps.at(static_cast(q1)) .at(static_cast(q2))) { usedSwaps.at(static_cast(q1)) .at(static_cast(q2)) = true; usedSwaps.at(static_cast(q2)) .at(static_cast(q1)) = true; - expandNodeAddOneSwap(edge, node, layer, twoQubitGateMultiplicity); + expandNodeAddOneSwap(edge, node, layer, singleQubitGateMultiplicity, + twoQubitGateMultiplicity, consideredQubits); } } } @@ -595,23 +709,26 @@ void HeuristicMapper::expandNode( void HeuristicMapper::expandNodeAddOneSwap( const Edge& swap, Node& node, const std::size_t layer, - const TwoQubitMultiplicity& twoQubitGateMultiplicity) { + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity, + const std::unordered_set& consideredQubits) { const auto& config = results.config; - Node newNode = Node(node.qubits, node.locations, node.swaps, node.costFixed, - node.depth + 1); + Node newNode = Node(nextNodeId++, node.id, node.qubits, node.locations, + node.swaps, node.costFixed, node.depth + 1); - if (architecture.getCouplingMap().find(swap) != - architecture.getCouplingMap().end() || - architecture.getCouplingMap().find(Edge{swap.second, swap.first}) != - architecture.getCouplingMap().end()) { - newNode.applySWAP(swap, architecture); + if (architecture.isEdgeConnected(swap) || + architecture.isEdgeConnected(Edge{swap.second, swap.first})) { + newNode.applySWAP(swap, architecture, singleQubitGateMultiplicity, + twoQubitGateMultiplicity, config.considerFidelity); } else { newNode.applyTeleportation(swap, architecture); } - newNode.updateHeuristicCost(architecture, twoQubitGateMultiplicity, - results.config.admissibleHeuristic); + newNode.updateHeuristicCost(architecture, singleQubitGateMultiplicity, + twoQubitGateMultiplicity, consideredQubits, + results.config.admissibleHeuristic, + results.config.considerFidelity); // calculate heuristics for the cost of the following layers if (config.lookahead) { @@ -619,6 +736,12 @@ void HeuristicMapper::expandNodeAddOneSwap( } nodes.push(newNode); + if (results.config.dataLoggingEnabled()) { + dataLogger->logSearchNode(layer, newNode.id, newNode.parent, + newNode.costFixed, newNode.costHeur, + newNode.lookaheadPenalty, newNode.qubits, + newNode.done, newNode.swaps, newNode.depth); + } } void HeuristicMapper::lookahead(const std::size_t layer, @@ -682,7 +805,11 @@ void HeuristicMapper::lookahead(const std::size_t layer, } } -void HeuristicMapper::Node::applySWAP(const Edge& swap, Architecture& arch) { +void HeuristicMapper::Node::applySWAP( + const Edge& swap, Architecture& arch, + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity, + bool considerFidelity) { ++nswaps; swaps.emplace_back(); const auto q1 = qubits.at(swap.first); @@ -700,18 +827,76 @@ void HeuristicMapper::Node::applySWAP(const Edge& swap, Architecture& arch) { static_cast(swap.first); } - if (arch.getCouplingMap().find(swap) != arch.getCouplingMap().end() || - arch.getCouplingMap().find(Edge{swap.second, swap.first}) != - arch.getCouplingMap().end()) { + if (arch.isEdgeConnected(swap) || + arch.isEdgeConnected(Edge{swap.second, swap.first})) { swaps.back().emplace_back(swap.first, swap.second, qc::SWAP); } else { throw QMAPException("Something wrong in applySWAP."); } - if (arch.bidirectional()) { - costFixed += COST_BIDIRECTIONAL_SWAP; + if (considerFidelity) { + std::uint16_t q1Mult = 0; + std::uint16_t q2Mult = 0; + if (q1 != -1) { + q1Mult = singleQubitGateMultiplicity.at(static_cast(q1)); + } + if (q2 != -1) { + q2Mult = singleQubitGateMultiplicity.at(static_cast(q2)); + } + // accounting for fidelity difference of single qubit gates (two qubit + // gates are handled in the heuristic) + costFixed += + ((q2Mult - q1Mult) * arch.getSingleQubitFidelityCost(swap.first) + + (q1Mult - q2Mult) * arch.getSingleQubitFidelityCost(swap.second)); + // adding cost of the swap gate itself + costFixed += arch.getSwapFidelityCost(swap.first, swap.second); + // add cost of newly validly mapped gates and + // remove cost of now no longer validly mapped gates + for (const auto& [edge, mult] : twoQubitGateMultiplicity) { + auto [q3, q4] = edge; + if (q3 == q1 || q3 == q2 || q4 == q1 || q4 == q2) { + auto physQ3 = static_cast(locations.at(q3)); + auto physQ4 = static_cast(locations.at(q4)); + if (arch.isEdgeConnected(Edge{physQ3, physQ4}) || + arch.isEdgeConnected(Edge{physQ4, physQ3})) { + // validly mapped now + if (validMappedTwoQubitGates.find(edge) == + validMappedTwoQubitGates.end()) { // not mapped validly before + costFixed += + mult.first * arch.getTwoQubitFidelityCost(physQ3, physQ4) + + mult.second * arch.getTwoQubitFidelityCost(physQ4, physQ3); + validMappedTwoQubitGates.emplace(edge); + } + } else { // not mapped validly now + if (validMappedTwoQubitGates.find(edge) != + validMappedTwoQubitGates.end()) { // mapped validly before + auto prevPhysQ3 = physQ3; + if (prevPhysQ3 == swap.first) { + prevPhysQ3 = swap.second; + } else if (prevPhysQ3 == swap.second) { + prevPhysQ3 = swap.first; + } + auto prevPhysQ4 = physQ4; + if (prevPhysQ4 == swap.first) { + prevPhysQ4 = swap.second; + } else if (prevPhysQ4 == swap.second) { + prevPhysQ4 = swap.first; + } + costFixed -= mult.first * arch.getTwoQubitFidelityCost(prevPhysQ3, + prevPhysQ4) + + mult.second * arch.getTwoQubitFidelityCost(prevPhysQ4, + prevPhysQ3); + validMappedTwoQubitGates.erase(edge); + } + } + } + } } else { - costFixed += COST_UNIDIRECTIONAL_SWAP; + if (arch.bidirectional()) { + costFixed += COST_BIDIRECTIONAL_SWAP; + } else { + costFixed += COST_UNIDIRECTIONAL_SWAP; + } } } @@ -751,10 +936,8 @@ void HeuristicMapper::Node::applyTeleportation(const Edge& swap, std::uint16_t source = std::numeric_limits::max(); std::uint16_t target = std::numeric_limits::max(); - if (arch.getCouplingMap().find({swap.first, middleAnc}) != - arch.getCouplingMap().end() || - arch.getCouplingMap().find({middleAnc, swap.first}) != - arch.getCouplingMap().end()) { + if (arch.isEdgeConnected({swap.first, middleAnc}) || + arch.isEdgeConnected({middleAnc, swap.first})) { source = swap.first; target = swap.second; } else { @@ -774,30 +957,115 @@ void HeuristicMapper::Node::applyTeleportation(const Edge& swap, costFixed += COST_TELEPORTATION; } -void HeuristicMapper::Node::recalculateFixedCost(const Architecture& arch) { +void HeuristicMapper::Node::recalculateFixedCost( + const Architecture& arch, + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity, + bool considerFidelity) { costFixed = 0; - for (auto& swapNode : swaps) { - for (auto& swap : swapNode) { - if (swap.op == qc::SWAP) { - if (arch.bidirectional()) { - costFixed += COST_BIDIRECTIONAL_SWAP; - } else { - costFixed += COST_UNIDIRECTIONAL_SWAP; + if (considerFidelity) { + // adding costs of single qubit gates + for (std::uint16_t i = 0U; i < arch.getNqubits(); ++i) { + if (singleQubitGateMultiplicity.at(i) == 0) { + continue; + } + costFixed += singleQubitGateMultiplicity.at(i) * + arch.getSingleQubitFidelityCost( + static_cast(locations.at(i))); + } + // adding cost of the swap gates + for (auto& swapNode : swaps) { + for (auto& swap : swapNode) { + if (swap.op == qc::SWAP) { + costFixed += arch.getSwapFidelityCost(swap.first, swap.second); + } else if (swap.op == qc::Teleportation) { + throw QMAPException("Teleportation currently not supported for " + "noise-aware mapping"); + } + } + } + validMappedTwoQubitGates.clear(); + // adding cost of two qubit gates that are already mapped next to each other + for (const auto& edgeMultiplicity : twoQubitGateMultiplicity) { + const auto& q1 = edgeMultiplicity.first.first; + const auto& q2 = edgeMultiplicity.first.second; + const auto& straightMultiplicity = edgeMultiplicity.second.first; + const auto& reverseMultiplicity = edgeMultiplicity.second.second; + + if (arch.isEdgeConnected( + {static_cast(locations.at(q1)), + static_cast(locations.at(q2))}) || + arch.isEdgeConnected({static_cast(locations.at(q2)), + static_cast( + locations.at(q1))})) { // validly mapped + costFixed += (straightMultiplicity * + arch.getTwoQubitFidelityCost( + static_cast(locations.at(q1)), + static_cast(locations.at(q2))) + + reverseMultiplicity * + arch.getTwoQubitFidelityCost( + static_cast(locations.at(q2)), + static_cast(locations.at(q1)))); + validMappedTwoQubitGates.emplace(q1, q2); + } + } + // 2-qubit-gates not yet mapped next to eachother are handled in the + // heuristic + } else { + for (auto& swapNode : swaps) { + for (auto& swap : swapNode) { + if (swap.op == qc::SWAP) { + if (arch.bidirectional()) { + costFixed += COST_BIDIRECTIONAL_SWAP; + } else { + costFixed += COST_UNIDIRECTIONAL_SWAP; + } + } else if (swap.op == qc::Teleportation) { + costFixed += COST_TELEPORTATION; } - } else if (swap.op == qc::Teleportation) { - costFixed += COST_TELEPORTATION; } } } } void HeuristicMapper::Node::updateHeuristicCost( - const Architecture& arch, - const TwoQubitMultiplicity& twoQubitGateMultiplicity, - const bool admissibleHeuristic) { + const Architecture& arch, + const SingleQubitMultiplicity& singleQubitGateMultiplicity, + const TwoQubitMultiplicity& twoQubitGateMultiplicity, + const std::unordered_set& consideredQubits, + bool admissibleHeuristic, bool considerFidelity) { costHeur = 0.; done = true; + // single qubit gate savings potential by moving them to different physical + // qubits with higher fidelity + double savingsPotential = 0.; + if (considerFidelity) { + for (std::uint16_t log_qbit = 0U; log_qbit < arch.getNqubits(); + ++log_qbit) { + if (singleQubitGateMultiplicity.at(log_qbit) == 0) { + continue; + } + double qbitSavings = 0; + double currFidelity = arch.getSingleQubitFidelityCost( + static_cast(locations.at(log_qbit))); + for (std::uint16_t phys_qbit = 0U; phys_qbit < arch.getNqubits(); + ++phys_qbit) { + if (arch.getSingleQubitFidelityCost(phys_qbit) >= currFidelity) { + continue; + } + double curSavings = + singleQubitGateMultiplicity.at(log_qbit) * + (currFidelity - arch.getSingleQubitFidelityCost(phys_qbit)) - + arch.fidelityDistance( + static_cast(locations.at(log_qbit)), phys_qbit, + consideredQubits.size()); + qbitSavings = std::max(qbitSavings, curSavings); + } + savingsPotential += qbitSavings; + } + } + // iterating over all virtual qubit pairs, that share a gate on the // current layer for (const auto& [edge, multiplicity] : twoQubitGateMultiplicity) { @@ -805,36 +1073,80 @@ void HeuristicMapper::Node::updateHeuristicCost( const auto& [straightMultiplicity, reverseMultiplicity] = multiplicity; + bool edgeDone = + (arch.isEdgeConnected({static_cast(locations.at(q1)), + static_cast(locations.at(q2))}) || + arch.isEdgeConnected({static_cast(locations.at(q2)), + static_cast(locations.at(q1))})); // only if all qubit pairs are mapped next to each other the mapping // is complete - if (arch.getCouplingMap().find( - {static_cast(locations.at(q1)), - static_cast(locations.at(q2))}) == - arch.getCouplingMap().end() && - arch.getCouplingMap().find( - {static_cast(locations.at(q2)), - static_cast(locations.at(q1))}) == - arch.getCouplingMap().end()) { + if (!edgeDone) { done = false; } - const double swapCostStraight = - arch.distance(static_cast(locations.at(q1)), - static_cast(locations.at(q2))); - const double swapCostReverse = - arch.distance(static_cast(locations.at(q2)), - static_cast(locations.at(q1))); - - if (admissibleHeuristic) { - if (straightMultiplicity > 0) { - costHeur = std::max(costHeur, swapCostStraight); + if (considerFidelity) { + // find the optimal edge, to which to remap the given virtual qubit + // 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] : arch.getCouplingMap()) { + swapCost = std::min( + swapCost, + straightMultiplicity * arch.getTwoQubitFidelityCost(q3, q4) + + reverseMultiplicity * arch.getTwoQubitFidelityCost(q4, q3) + + arch.fidelityDistance( + static_cast(locations.at(q1)), q3, + consideredQubits.size()) + + arch.fidelityDistance( + static_cast(locations.at(q2)), q4, + consideredQubits.size())); + swapCost = std::min( + swapCost, + straightMultiplicity * arch.getTwoQubitFidelityCost(q4, q3) + + reverseMultiplicity * arch.getTwoQubitFidelityCost(q3, q4) + + arch.fidelityDistance( + static_cast(locations.at(q2)), q3, + consideredQubits.size()) + + arch.fidelityDistance( + static_cast(locations.at(q1)), q4, + consideredQubits.size())); } - if (reverseMultiplicity > 0) { - costHeur = std::max(costHeur, swapCostReverse); + + if (edgeDone) { + double currEdgeCost = + (straightMultiplicity * + arch.getTwoQubitFidelityCost( + static_cast(locations.at(q1)), + static_cast(locations.at(q2))) + + reverseMultiplicity * + arch.getTwoQubitFidelityCost( + static_cast(locations.at(q2)), + static_cast(locations.at(q1)))); + savingsPotential += (currEdgeCost - swapCost); + } else { + costHeur += swapCost; } } else { - costHeur += swapCostStraight * straightMultiplicity + - swapCostReverse * reverseMultiplicity; + const double swapCostStraight = + arch.distance(static_cast(locations.at(q1)), + static_cast(locations.at(q2))); + const double swapCostReverse = + arch.distance(static_cast(locations.at(q2)), + static_cast(locations.at(q1))); + + if (admissibleHeuristic) { + if (straightMultiplicity > 0) { + costHeur = std::max(costHeur, swapCostStraight); + } + if (reverseMultiplicity > 0) { + costHeur = std::max(costHeur, swapCostReverse); + } + } else { + costHeur += swapCostStraight * straightMultiplicity + + swapCostReverse * reverseMultiplicity; + } } } + costHeur -= savingsPotential; } diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 3f9989870..252d7897f 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -10,6 +10,7 @@ from qiskit.providers import Backend from qiskit.providers.models import BackendProperties from qiskit.transpiler.target import Target + from src.mqt.qmap.visualization.search_visualizer import SearchVisualizer from .load_architecture import load_architecture from .load_calibration import load_calibration @@ -59,6 +60,8 @@ def compile( # noqa: A001 method: str | Method = "heuristic", initial_layout: str | InitialLayout = "dynamic", layering: str | Layering = "individual_gates", + lookaheads: int | None = None, + lookahead_factor: float = 0.5, use_teleportation: bool = False, teleportation_fake: bool = False, teleportation_seed: int = 0, @@ -75,6 +78,7 @@ def compile( # noqa: A001 add_measurements_to_mapped_circuit: bool = True, verbose: bool = False, debug: bool = False, + visualizer: SearchVisualizer | None = None, ) -> tuple[QuantumCircuit, MappingResults]: """Interface to the MQT QMAP tool for mapping quantum circuits. @@ -135,6 +139,15 @@ def compile( # noqa: A001 config.add_measurements_to_mapped_circuit = add_measurements_to_mapped_circuit config.verbose = verbose config.debug = debug + if visualizer is not None: + config.data_logging_path = visualizer.data_logging_path + if lookaheads is None: + config.lookaheads = 0 + config.lookahead = False + else: + config.lookaheads = lookaheads + config.lookahead = True + config.lookahead_factor = lookahead_factor results = map(circ, architecture, config) diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index b472c0962..5444f1207 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -135,6 +135,7 @@ class Configuration: use_teleportation: bool verbose: bool debug: bool + data_logging_path: str def __init__(self) -> None: ... def json(self) -> dict[str, Any]: ... diff --git a/src/mqt/qmap/visualization/__init__.py b/src/mqt/qmap/visualization/__init__.py new file mode 100644 index 000000000..50a647b17 --- /dev/null +++ b/src/mqt/qmap/visualization/__init__.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from mqt.qmap.visualization.search_visualizer import SearchVisualizer +from mqt.qmap.visualization.visualize_search_graph import visualize_search_graph + +__all__ = [ + "visualize_search_graph", + "SearchVisualizer", +] diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py new file mode 100644 index 000000000..3bc6d3360 --- /dev/null +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +from tempfile import TemporaryDirectory +from typing import TYPE_CHECKING, Any, Callable, Literal + +from mqt.qmap.visualization.visualize_search_graph import Position, SearchNode, visualize_search_graph + +if TYPE_CHECKING: + from ipywidgets import Widget + + +class SearchVisualizer: + def __init__(self, data_logging_path: str | None = None) -> None: + if data_logging_path is not None: + self.data_logging_path = data_logging_path + self.data_logging_tmp_dir = None + else: + self.data_logging_tmp_dir = TemporaryDirectory() + self.data_logging_path = self.data_logging_tmp_dir.name + + def close(self): + if self.data_logging_tmp_dir is not None: + self.data_logging_tmp_dir.cleanup() + self.data_logging_path = None + + def visualize_search_graph( + self, + layer: int | Literal["interactive"] = "interactive", # 'interactive' (slider menu) | index + architecture_node_positions: dict[int, Position] | None = None, + architecture_layout_method: Literal[ + "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" + ] = "sfdp", + search_node_layout_method: Literal[ + "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" + ] = "walker", + search_node_layout_walker_factor: float = 0.6, + search_graph_border: float = 0.05, + architecture_border: float = 0.05, + swap_arrow_spacing: float = 0.05, + swap_arrow_offset: float = 0.05, + use3d: bool = True, + projection: Literal["orthographic", "perspective"] = "perspective", + width: int = 1400, + height: int = 700, + draw_search_edges: bool = True, + search_edges_width: float = 0.5, + search_edges_color: str = "#888", + search_edges_dash: str = "solid", # 'solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot', string containing a dash length list in pixels or percentages (e.g. '5px 10px 2px 2px', '5, 10, 2, 2', '10% 20% 40%') + tapered_search_layer_heights: bool = True, + show_layout: Literal["hover", "click"] | None = "hover", + show_swaps: bool = True, + show_shared_swaps: bool = True, # if one swap moves 2 considered qubits -> combine into one two-colored and two-headed arrow + color_valid_mapping: str | None = "green", # static HTML color (e.g. 'blue' or '#0000FF') + color_final_node: str | None = "red", # static HTML color (e.g. 'blue' or '#0000FF') + search_node_color: str + | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]) = "total_cost", + # 'total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty' | static HTML color (e.g. 'blue' or '#0000FF') | + # function that takes a SearchNode and returns a float (e.g. lambda n: n.fixed_cost + n.heuristic_cost) + # node fields: {"fixed_cost": float, "heuristic_cost": float, "lookahead_penalty": float, "is_valid_mapping": bool, + # "final": bool, "depth": int, "layout": Tuple[int, ...], "swaps": Tuple[Tuple[int, int], ...]} + # or list of the above if 3d graph is used and multiple points per node are defined in search_node_height (lengths need to match) + # if in that case no is list is provided all points per node will be the same color + prioritize_search_node_color: bool + | list[ + bool + ] = False, # if True, search_node_color will be prioritized over color_valid_mapping and color_final_node + search_node_color_scale: str | list[str] = "YlGnBu", # https://plotly.com/python/builtin-colorscales/ + # aggrnyl agsunset blackbody bluered blues blugrn bluyl brwnyl + # bugn bupu burg burgyl cividis darkmint electric emrld + # gnbu greens greys hot inferno jet magenta magma + # mint orrd oranges oryel peach pinkyl plasma plotly3 + # pubu pubugn purd purp purples purpor rainbow rdbu + # rdpu redor reds sunset sunsetdark teal tealgrn turbo + # viridis ylgn ylgnbu ylorbr ylorrd algae amp deep + # dense gray haline ice matter solar speed tempo + # thermal turbid armyrose brbg earth fall geyser prgn + # piyg picnic portland puor rdgy rdylbu rdylgn spectral + # tealrose temps tropic balance curl delta oxy edge + # hsv icefire phase twilight mrybm mygbm armylg falllg + search_node_invert_color_scale: bool | list[bool] = True, + search_node_colorbar_title: str | list[str | None] | None = None, + search_node_colorbar_spacing: float = 0.06, + search_node_height: str + | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]) = "total_cost", + # just as with search_node_color (without color strings), but possible to specify a list to draw multiple point per node on different heights + # only applicable if use3d is True + draw_stems: bool = False, + stems_width: float = 0.7, + stems_color: str = "#444", + stems_dash: str = "solid", # 'solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot', string containing a dash length list in pixels or percentages (e.g. '5px 10px 2px 2px', '5, 10, 2, 2', '10% 20% 40%') + show_search_progression: bool = True, + search_progression_step: int = 10, + search_progression_speed: float = 2, # steps per second + plotly_settings: dict[str, dict[str, Any]] | None = None, + # { + # 'layout': settings for plotly.graph_objects.Layout (of subplots figure) + # 'arrows': settings for plotly.graph_objects.layout.Annotation + # 'stats_legend': settings for plotly.graph_objects.layout.Annotation + # 'search_nodes': settings for plotly.graph_objects.Scatter resp. ...Scatter3d + # 'search_edges': settings for plotly.graph_objects.Scatter resp. ...Scatter3d + # 'architecture_nodes': settings for plotly.graph_objects.Scatter + # 'architecture_edges': settings for plotly.graph_objects.Scatter + # 'architecture_edge_labels': settings for plotly.graph_objects.Scatter + # 'search_xaxis': settings for plotly.graph_objects.layout.XAxis resp. ...layout.scene.XAxis + # 'search_yaxis': settings for plotly.graph_objects.layout.YAxis resp. ...layout.scene.YAxis + # 'search_zaxis': settings for plotly.graph_objects.layout.scene.ZAxis + # 'architecture_xaxis': settings for plotly.graph_objects.layout.XAxis + # 'architecture_yaxis': settings for plotly.graph_objects.layout.YAxis + # } + ) -> Widget: + if plotly_settings is None: + plotly_settings = {} + if self.data_logging_path is None: + msg = "SearchVisualizer has already been closed and data logs have been discarded." + raise ValueError(msg) + return visualize_search_graph( + data_logging_path=self.data_logging_path, + layer=layer, + architecture_node_positions=architecture_node_positions, + architecture_layout_method=architecture_layout_method, + search_node_layout_method=search_node_layout_method, + search_node_layout_walker_factor=search_node_layout_walker_factor, + search_graph_border=search_graph_border, + architecture_border=architecture_border, + swap_arrow_spacing=swap_arrow_spacing, + swap_arrow_offset=swap_arrow_offset, + use3d=use3d, + projection=projection, + width=width, + height=height, + draw_search_edges=draw_search_edges, + search_edges_width=search_edges_width, + search_edges_color=search_edges_color, + search_edges_dash=search_edges_dash, + tapered_search_layer_heights=tapered_search_layer_heights, + show_layout=show_layout, + show_swaps=show_swaps, + show_shared_swaps=show_shared_swaps, + color_valid_mapping=color_valid_mapping, + color_final_node=color_final_node, + search_node_color=search_node_color, + prioritize_search_node_color=prioritize_search_node_color, + search_node_color_scale=search_node_color_scale, + search_node_invert_color_scale=search_node_invert_color_scale, + search_node_colorbar_title=search_node_colorbar_title, + search_node_colorbar_spacing=search_node_colorbar_spacing, + search_node_height=search_node_height, + draw_stems=draw_stems, + stems_width=stems_width, + stems_color=stems_color, + stems_dash=stems_dash, + show_search_progression=show_search_progression, + search_progression_step=search_progression_step, + search_progression_speed=search_progression_speed, + plotly_settings=plotly_settings, + ) diff --git a/src/mqt/qmap/visualization/treedraw.py b/src/mqt/qmap/visualization/treedraw.py new file mode 100644 index 000000000..1d6b02376 --- /dev/null +++ b/src/mqt/qmap/visualization/treedraw.py @@ -0,0 +1,341 @@ +"""treedraw.py +===========. + +Adapted from: +https://github.com/cvzi/py_treedraw + +This module implements a layout algorithm for a tree. +Each node in the tree has a layout property that contains an x and y +coordinate. +The layout is calculated by calling the method "walker(distance)". +The distance property indicated the distance between nodes on the same level. +If the tree is build up using the addChild/removeChild methods, the layout +will be calculated in linear time. +The algoithm is a python implemenation of this publication + "Improving +Walker's Algorithm to Run in Linear Time" by Christoph Buchheim, Michael +Junger, Sebastian Leipert" +""" +from __future__ import annotations + +__all__ = ["Tree", "Node"] + +__version__ = "1.4" + +import math + +try: + xrange(5) + myrange = xrange +except NameError: + myrange = range + + +class Tree: + def __init__(self, data) -> None: + # Start a tree with a root node + self.root = Node(data) + self.root.tree = self + self.root.leftSibling = None + self.nodes = [self.root] + + def addNode(self, node): + # add a child to the root + return self.root.addNode(node) + + def addChild(self, data): + # add a child to the root + return self.root.addChild(data) + + def removeChild(self, node): + # remove a child from the root + return self.root.removeChild(node) + + def walker(self, distance=1.0): + # Init layout algorithm + self._firstWalk(self.root, distance) + self._secondWalk(self.root, -self.root.layout.prelim) + + def _add(self, node): + if node not in self.nodes: + self.nodes.append(node) + return node + + def _firstWalk(self, v, distance): + if v.children: + defaultAncestor = v.children[0] + for w in v.children: + self._firstWalk(w, distance) + self._apportion(w, defaultAncestor, distance) + self._executeShifts(v) + midpoint = 0.5 * (v.children[0].layout.prelim + v.children[-1].layout.prelim) + w = self._leftSibling(v) + if w is not None: + v.layout.prelim = w.layout.prelim + distance + v.layout.mod = v.layout.prelim - midpoint + else: + v.layout.prelim = midpoint + else: + ls = self._leftSibling(v) + if ls: + v.layout.prelim = ls.layout.prelim + distance + + def _secondWalk(self, v, m): + v.layout.x(v.layout.prelim + m) + for w in v.children: + self._secondWalk(w, m + v.layout.mod) + + def _apportion(self, v, defaultAncestor, distance): + w = self._leftSibling(v) + if w is not None: + v_p_o = v + v_p_i = v + v_m_i = w + v_m_o = v_p_i.parent.children[0] + s_p_i = v_p_i.layout.mod + s_p_o = v_p_o.layout.mod + s_m_i = v_m_i.layout.mod + s_m_o = v_m_o.layout.mod + while v_m_i.nextRight() and v_p_i.nextLeft(): + v_m_i = v_m_i.nextRight() + v_p_i = v_p_i.nextLeft() + v_m_o = v_m_o.nextLeft() + v_p_o = v_p_o.nextRight() + v_p_o.layout.ancestor = v + shift = v_m_i.layout.prelim + s_m_i - (v_p_i.layout.prelim + s_p_i) + distance + if shift > 0: + self._moveSubtree(self._ancestor(v_m_i, v, defaultAncestor), v, shift) + s_p_i = s_p_i + shift + s_p_o = s_p_o + shift + s_m_i = s_m_i + v_m_i.layout.mod + s_p_i = s_p_i + v_p_i.layout.mod + s_m_o = s_m_o + v_m_o.layout.mod + s_p_o = s_p_o + v_p_o.layout.mod + if v_m_i.nextRight() and v_p_o.nextRight() is None: + v_p_o.layout.thread = v_m_i.nextRight() + v_p_o.layout.mod = v_p_o.layout.mod + s_m_i - s_p_o + if v_p_i.nextLeft() and v_m_o.nextLeft() is None: + v_m_o.layout.thread = v_p_i.nextLeft() + v_m_o.layout.mod = v_m_o.layout.mod + s_p_i - s_m_o + defaultAncestor = v + return defaultAncestor + + @staticmethod + def _leftSibling(v): + if v.leftSibling != -1: + return v.leftSibling + else: + if v.parent is None or not v.parent.children: + return None + last = None + for w in v.parent.children: + if w == v: + if last is not None: + return last + return None + last = w + return None + + @staticmethod + def _moveSubtree(w_m, w_p, shift): + subtrees = w_p.number() - w_m.number() + if subtrees == 0: + subtrees = 0.0000001 + w_p.layout.change = w_p.layout.change - shift / subtrees + w_p.layout.shift = w_p.layout.shift + shift + w_m.layout.change = w_m.layout.change + shift / subtrees + w_p.layout.prelim = w_p.layout.prelim + shift + w_p.layout.mod = w_p.layout.mod + shift + + @staticmethod + def _executeShifts(v): + shift = 0 + change = 0 + i = len(v.children) + for i in myrange(len(v.children) - 1, -1, -1): + w = v.children[i] + w.layout.prelim = w.layout.prelim + shift + w.layout.mod = w.layout.mod + shift + change = change + w.layout.change + shift = shift + w.layout.shift + change + + @staticmethod + def _ancestor(v_i, v, defaultAncestor): + if v_i.layout.ancestor.parent == v.parent: + return v_i.layout.ancestor + return defaultAncestor + + +class Node: + class Layout: + def __init__(self, v) -> None: + self.node = v + self.mod = 0 + self.thread = None + self.ancestor = v + self.prelim = 0 + self.shift = 0 + self.change = 0 + self.pos = [None, None] + self.number = -1 # undefined + + def x(self, value=None): + if value is not None: + self.pos[0] = value + return None + else: + return self.pos[0] + + def y(self, value=None): + if value is not None: + self.pos[1] = value + return None + else: + if self.pos[1] is None: + self.pos[1] = self.node.level() + return self.pos[1] + + def __init__(self, data) -> None: + self.tree = None + self.data = data + self.leftSibling = -1 # undefined, outdated + self.children = [] + self.parent = None + self.layout = self.Layout(self) + + def addNode(self, node): + # Add an existing tree/node as a child + + # Set left sibling + if self.children: + node.leftSibling = self.children[-1] + node.layout.number = node.leftSibling.layout.number + 1 + else: + node.leftSibling = None + node.layout.number = 0 + + # Append to node + node.parent = self + self.children.append(node) + + # Add to tree + root = self + i = 0 + while root.parent is not None: + root = root.parent + i += 1 + root.tree._add(node) + node.tree = root.tree + self.layout.pos[1] = i # Level + return node + + def addChild(self, data): + # Create a new node and add it as a child + return self.addNode(Node(data)) + + def removeChild(self, v): + j = -1 + for i in myrange(len(self.children)): + if self.children[i] == v: + del self.children[i] + j = i + break + + for i in myrange(len(self.tree.nodes)): + if self.tree.nodes[i] == v: + del self.tree.nodes[i] + break + + # Update left sibling + if j == 0: + self.children[0].leftSibling = None + elif j > 0: + self.children[j].leftSibling = self.children[j - 1] + else: # j == -1 + return + + # Update numbers + for i in myrange(j, len(self.children)): + self.children[i].layout.number = i + + # Remove children of the deleted node + i = 0 + while i < len(self.tree.nodes): + if self.tree.nodes[i] in v.children: + del self.tree.nodes[i] + else: + i += 1 + + v.children = [] + + def nextLeft(self): + if self.children: + return self.children[0] + else: + return self.layout.thread + + def nextRight(self): + if self.children: + return self.children[-1] + else: + return self.layout.thread + + def level(self): + if self.layout.pos[1] is not None: + return self.layout.pos[1] + n = self.parent + i = 0 + while n is not None: + n = n.parent + i += 1 + return i + + def position(self, origin=(0, 0), scalex=1.0, scaley=None): + """Return position as integer + Examples: + position(origin) + position(origin, 10) + position(origin, 10, 15) + position(origin, (10, 15)). + """ + if scaley is None: + if hasattr(scalex, "__getitem__"): + scaley = scalex[1] + scalex = scalex[0] + else: + scaley = scalex + return ( + origin[0] + int(math.ceil(self.layout.x() * scalex)), + origin[1] + int(math.ceil(self.layout.y() * scaley)), + ) + + def positionf(self, origin=(0.0, 0.0), scalex=1.0, scaley=None): + """Return position as floating point + Examples: + position(origin) + position(origin, 10) + position(origin, 10, 15) + position(origin, (10, 15)). + """ + if scaley is None: + if hasattr(scalex, "__getitem__"): + scaley = scalex[1] + scalex = scalex[0] + else: + scaley = scalex + return (origin[0] + (self.layout.x() * scalex), origin[1] + (self.layout.y() * scaley)) + + def number(self): + if self.layout.number != -1: + return self.layout.number + else: + if self.parent is None: + return 0 + else: + i = 0 + for node in self.parent.children: + if node == self: + return i + i += 1 + msg = "Error in number(self)!" + raise Exception(msg) diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py new file mode 100644 index 000000000..ff6f7e24e --- /dev/null +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -0,0 +1,2003 @@ +from __future__ import annotations + +import json +import os +import re +from copy import deepcopy +from dataclasses import dataclass +from random import shuffle +from typing import Any, Callable, List, Literal, Tuple, Union + +import networkx as nx +import plotly.graph_objects as go +from _plotly_utils.basevalidators import ColorscaleValidator, ColorValidator +from distinctipy import distinctipy +from ipywidgets import HBox, IntSlider, Layout, Play, VBox, Widget, interactive, jslink +from networkx.drawing.nx_pydot import graphviz_layout +from plotly.subplots import make_subplots + +from mqt.qmap.visualization.treedraw import Tree + + +@dataclass +class TwoQbitMultiplicity: + q0: int + q1: int + forward: int + backward: int + + +@dataclass +class SearchNode: + id: int + parent: int | None + fixed_cost: float + heuristic_cost: float + lookahead_penalty: float + is_valid_mapping: bool + final: bool + depth: int + layout: tuple[int, ...] + swaps: tuple[tuple[int, int], ...] + + def total_cost(self): + return self.fixed_cost + self.heuristic_cost + self.lookahead_penalty + + def total_fixed_cost(self): + return self.fixed_cost + self.lookahead_penalty + + +Position = Tuple[float, float] +Colorscale = Union[str, List[str], List[Tuple[float, str]]] + + +def _is_len_iterable(obj) -> bool: + if isinstance(obj, str): + return False + # this is actually the safest way to do this: + # https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable + try: + iter(obj) + len(obj) + return True + except TypeError: + return False + + +def _is_number(x): + return type(x) is float or type(x) is int + + +def _remove_first_lines(string: str, n: int) -> str: + return string.split("\n", n)[n] + + +def _get_avg_min_distance(seq): + arr = sorted(seq) + sum_dist = 0 + for i in range(1, len(arr)): + sum_dist += arr[i] - arr[i - 1] + return sum_dist / (len(arr) - 1) + + +def _reverse_layout(seq): + r = [-1] * len(seq) + for i, v in enumerate(seq): + r[v] = i + return r + + +def _copy_to_dict(target: dict[Any, Any], source: dict[Any, Any], recursive: bool = True): + for key, value in source.items(): + if recursive and isinstance(value, dict): + if key not in target or not isinstance(target[key], dict): + target[key] = value + else: + _copy_to_dict(target[key], value) + else: + target[key] = value + return target + + +def _parse_search_graph(file_path: str, final_node_id: int) -> tuple[nx.Graph, int]: + graph = nx.Graph() + root = None + with open(file_path) as file: + for line in file: + line = line.strip().split(";") + node = int(line[0]) + parent = int(line[1]) + data = SearchNode( + node, + parent if parent != node else None, + float(line[2]), + float(line[3]), + float(line[4]), + line[5].strip() == "1", + node == final_node_id, + int(line[6]), + tuple(int(q) for q in line[7].strip().split(",")), + () + if len(line[8].strip()) == 0 + else tuple(tuple(int(q) for q in swap.split(" ")) for swap in line[8].strip().split(",")), + ) + graph.add_node(node, data=data) + if parent == node: + root = node + else: + graph.add_edge(node, parent) + return graph, root + + +def _layout_search_graph( + search_graph: nx.Graph, + root: int, + method: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], + walker_factor: float, # 0-1 + tapered_layer_heights: bool, +) -> dict[int, Position]: + if method == "walker": + tree = Tree(root) + nodes = {root: tree.root} + for node in search_graph.nodes: + if node != root: + nodes[node] = nodes[search_graph.nodes[node]["data"].parent].addChild(node) + tree.walker(walker_factor) + pos = {} + for node in tree.nodes: + pos[node.data] = node.position(origin=(0, 0), scalex=100, scaley=-1) + else: + pos = graphviz_layout(search_graph, prog=method, root=search_graph.nodes[0]) + + if not tapered_layer_heights: + return pos + + layers_x = {} + layer_spacings = {} + for n in pos: + x, y = pos[n] + if y not in layers_x: + layers_x[y] = [x] + layers_x[y].append(x) + if len(layers_x) <= len(search_graph.nodes): + return pos + + avg_spacing = 0 + for y in layers_x: + layer_spacings[y] = _get_avg_min_distance(layers_x[y]) + avg_spacing += layer_spacings[y] + avg_spacing /= len(layers_x) + layer_spacings_items = sorted(layer_spacings.items(), key=lambda x: x[0]) + for i in range(len(layer_spacings_items)): + before = layer_spacings_items[i - 1][1] if i != 0 else 0 + after = layer_spacings_items[i + 1][1] if i != len(layer_spacings_items) - 1 else before + if before == 0: + before = after + y, spacing = layer_spacings_items[i] + if spacing == 0 or (spacing < (before + after) / 3): + layer_spacings[y] = (before + after) / 2 + if layer_spacings[y] == 0: + layer_spacings[y] = avg_spacing + current_y = 0 + layers_new_y = {} + for old_y in sorted(layers_x.keys()): + layers_new_y[old_y] = current_y + current_y += layer_spacings[old_y] + for n in pos: + x, y = pos[n] + pos[n] = (x, layers_new_y[y]) + + return pos + + +def _prepare_search_graph_scatters( + number_of_scatters: int, + color_scale: list[Colorscale], + invert_color_scale: list[bool], + search_node_colorbar_title: list[str | None], + search_node_colorbar_spacing: float, + use3d: bool, + draw_stems: bool, + draw_edges: bool, + plotly_settings: dict[str, dict[str, Any]], +) -> tuple[ + list[go.Scatter | go.Scatter3d], list[go.Scatter | go.Scatter3d], go.Scatter3d | None +]: # nodes, edges, stems + if not use3d: + _copy_to_dict( + plotly_settings["search_nodes"], + { + "marker": { + "colorscale": color_scale[0], + "reversescale": invert_color_scale[0], + "colorbar": { + "title": search_node_colorbar_title[0], + }, + } + }, + ) + ps = plotly_settings["search_nodes"] + if search_node_colorbar_title[0] is None: + ps = deepcopy(ps) + del ps["marker"]["colorbar"] + node_scatter = go.Scatter(**ps) + node_scatter.x = [] + node_scatter.y = [] + if draw_edges: + edge_scatter = go.Scatter(**plotly_settings["search_edges"]) + edge_scatter.x = [] + edge_scatter.y = [] + edge_scatters = [edge_scatter] + else: + edge_scatters = [] + return [node_scatter], edge_scatters, None + else: + node_scatters = [] + n_colorbars = 0 + for i in range(number_of_scatters): + _copy_to_dict( + plotly_settings["search_nodes"], + { + "marker": { + "colorscale": color_scale[i], + "reversescale": invert_color_scale[i], + "colorbar": { + "y": 1 - search_node_colorbar_spacing * n_colorbars, + "title": search_node_colorbar_title[i], + }, + } + }, + ) + ps = plotly_settings["search_nodes"] + if search_node_colorbar_title[i] is None: + ps = deepcopy(ps) + del ps["marker"]["colorbar"] + else: + n_colorbars += 1 + scatter = go.Scatter3d(**ps) + scatter.x = [] + scatter.y = [] + scatter.z = [] + node_scatters.append(scatter) + if draw_edges: + edge_scatters = [] + for _ in range(number_of_scatters): + scatter = go.Scatter3d(**plotly_settings["search_edges"]) + scatter.x = [] + scatter.y = [] + scatter.z = [] + edge_scatters.append(scatter) + else: + edge_scatters = [] + stem_scatter = None + if draw_stems: + stem_scatter = go.Scatter3d(**plotly_settings["search_node_stems"]) + stem_scatter.x = [] + stem_scatter.y = [] + stem_scatter.z = [] + return node_scatters, edge_scatters, stem_scatter + + +def _prepare_search_graph_scatter_data( + number_of_scatters: int, + search_graph: nx.Graph, + search_pos: dict[int, Position], + use3d: bool, + search_node_color: list[str | Callable[[SearchNode], float]], + # 'total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty' | static HTML color (e.g. 'blue' or '#0000FF') | + # function that takes a node parameter dict and returns a float (e.g. lambda node: node['total_cost'] + node['fixed_cost']) + # node parameter dict: {"fixed_cost": float, "heuristic_cost": float, "lookahead_penalty": float, "is_valid_mapping": bool, + # "final": bool, "depth": int, "layout": Tuple[int, ...], "swaps": Tuple[Tuple[int, int], ...]} + # or list of the above if 3d graph is used and multiple points per node are defined in search_node_height (lengths need to match) + # if in that case no is list is provided all points per node will be the same color + prioritize_search_node_color: list[bool], + search_node_height: list[Callable[[SearchNode], float]], + color_valid_mapping: str | None, # static HTML color (e.g. 'blue' or '#0000FF') + color_final_node: str | None, # static HTML color (e.g. 'blue' or '#0000FF') + draw_stems: bool, # only applicable for 3D plots + draw_edges: bool, +) -> tuple[ + list[float | None], + list[float | None], + tuple[list[float | None], ...], + list[float], + list[float], + tuple[list[float], ...], + list[float | str], + list[float | None], + list[float | None], + list[float | None], + float, + float, + float, + float, + float, + float, +]: # edge_x, edge_y, edge_z, node_x, node_y, node_z, node_color, stem_x, stem_y, stem_z, min_x, max_x, min_y, max_y, min_z, max_z + edge_x = [] + edge_y = [] + edge_z = tuple([] for _ in range(number_of_scatters)) + node_x = [] + node_y = [] + node_z = tuple([] for _ in range(number_of_scatters)) + node_color = tuple([] for _ in range(number_of_scatters)) + stem_x = [] + stem_y = [] + stem_z = [] + min_x = None + max_x = None + min_y = None + max_y = None + min_z = None + max_z = None + + for node in search_graph.nodes(): + node_params = search_graph.nodes[node]["data"] + nx, ny = search_pos[node] + min_nz = 0 + max_nz = 0 + + node_x.append(nx) + node_y.append(ny) + if use3d: + for i in range(len(search_node_height)): + nz = search_node_height[i](node_params) + node_z[i].append(nz) + min_nz = min(min_nz, nz) + max_nz = max(max_nz, nz) + if draw_stems: + stem_x.append(nx) + stem_y.append(ny) + stem_z.append(min_nz) + stem_x.append(nx) + stem_y.append(ny) + stem_z.append(max_nz) + stem_x.append(None) + stem_y.append(None) + stem_z.append(None) + if min_x is None: + min_x = nx + max_x = nx + min_y = ny + max_y = ny + min_z = min_nz + max_z = max_nz + else: + min_x = min(min_x, nx) + max_x = max(max_x, nx) + min_y = min(min_y, ny) + max_y = max(max_y, ny) + if use3d: + min_z = min(min_z, min_nz) + max_z = max(max_z, max_nz) + + ncolor = None + if len(search_node_color) == 1: + ncolor = search_node_color[0](node_params) if callable(search_node_color[0]) else search_node_color[0] + for i in range(number_of_scatters): + prio_color = ( + prioritize_search_node_color[i] + if len(prioritize_search_node_color) > 1 + else prioritize_search_node_color[0] + ) + if not prio_color and color_final_node is not None and node_params.final: + node_color[i].append(color_final_node) + elif not prio_color and color_valid_mapping is not None and node_params.is_valid_mapping: + node_color[i].append(color_valid_mapping) + elif ncolor is not None: + node_color[i].append(ncolor) + elif callable(search_node_color[i]): + node_color[i].append(search_node_color[i](node_params)) + else: + node_color[i].append(search_node_color[i]) + + if draw_edges: + nodes_indices = {} + for i, n in enumerate(list(search_graph.nodes())): + nodes_indices[n] = i + for n0, n1 in search_graph.edges(): + n0_i = nodes_indices[n0] + n1_i = nodes_indices[n1] + edge_x.append(node_x[n0_i]) + edge_x.append(node_x[n1_i]) + edge_x.append(None) + edge_y.append(node_y[n0_i]) + edge_y.append(node_y[n1_i]) + edge_y.append(None) + if use3d: + for i in range(len(node_z)): + edge_z[i].append(node_z[i][n0_i]) + edge_z[i].append(node_z[i][n1_i]) + edge_z[i].append(None) + + return ( + edge_x, + edge_y, + edge_z, + node_x, + node_y, + node_z, + node_color, + stem_x, + stem_y, + stem_z, + min_x, + max_x, + min_y, + max_y, + min_z, + max_z, + ) + + +def _draw_search_graph_nodes( + scatters: list[go.Scatter | go.Scatter3d], + x: list[float], + y: list[float], + z: tuple[list[float], ...], + color: list[float | str], + use3d: bool, +): + for i in range(len(scatters)): + scatters[i].x = x + scatters[i].y = y + if use3d: + scatters[i].z = z[i] + scatters[i].marker.color = color[i] if len(color) > 1 else color[0] + + +def _draw_search_graph_stems(scatter: go.Scatter3d, x: list[float], y: list[float], z: list[float]): + scatter.x = x + scatter.y = y + scatter.z = z + + +def _draw_search_graph_edges( + scatters: list[go.Scatter | go.Scatter3d], x: list[float], y: list[float], z: tuple[list[float], ...], use3d: bool +): + for i in range(len(scatters)): + scatters[i].x = x + scatters[i].y = y + if use3d: + scatters[i].z = z[i] + + +def _parse_arch_graph(file_path: str) -> nx.Graph: + arch = None + with open(file_path) as file: + arch = json.load(file) + fidelity = None + if "fidelity" in arch: + fidelity = arch["fidelity"] + + edges = set() + nqbits = 0 + for q0, q1 in arch["coupling_map"]: + edge = (q0, q1) if q0 < q1 else (q1, q0) + if fidelity is not None: + edge += (fidelity["swap_fidelity_costs"][q0][q1],) + else: + edge += (float(30),) + # TODO: this is the cost of 1 swap for the non-noise-aware heuristic + # mapper; depending on directionality this might be different; once + # more dynamic swap cost system in Architecture.cpp is implemented + # replace wiht dynamic cost lookup + edges.add(edge) + nqbits = max(nqbits, q0, q1) + nqbits += 1 + + graph = nx.Graph() + graph.add_nodes_from(list(range(nqbits))) + graph.add_weighted_edges_from(edges) + return graph + + +def _draw_architecture_edges( + arch_graph: nx.Graph, arch_pos: dict[int, Position], plotly_settings: dict[str, dict[str, Any]] +) -> tuple[go.Scatter, go.Scatter]: + edge_x = [] + edge_y = [] + edge_label_x = [] + edge_label_y = [] + edge_label_text = [] + for n1, n2 in arch_graph.edges(): + x0, y0 = arch_pos[n1] + x1, y1 = arch_pos[n2] + mid_point = ((x0 + x1) / 2, (y0 + y1) / 2) + edge_x.append(x0) + edge_x.append(x1) + edge_x.append(None) + edge_y.append(y0) + edge_y.append(y1) + edge_y.append(None) + edge_label_x.append(mid_point[0]) + edge_label_y.append(mid_point[1]) + edge_label_text.append("{:.3f}".format(arch_graph[n1][n2]["weight"])) + + _copy_to_dict(plotly_settings["architecture_edges"], {"x": edge_x, "y": edge_y}) + _copy_to_dict( + plotly_settings["architecture_edge_labels"], {"x": edge_label_x, "y": edge_label_y, "text": edge_label_text} + ) + + return ( + go.Scatter(**plotly_settings["architecture_edges"]), + go.Scatter(**plotly_settings["architecture_edge_labels"]), + ) + + +def _draw_architecture_nodes( + scatter: go.Scatter, + arch_pos: dict[int, Position], + considered_qubit_colors: dict[int, str], + initial_qubit_position: list[int], + single_qubit_multiplicity: list[int], + two_qubit_individual_multiplicity: list[int], +): + x = [] + y = [] + color = [] + text = [] + for log_qubit, phys_qubit in enumerate(initial_qubit_position): + if phys_qubit == -1: + continue + if log_qubit not in considered_qubit_colors: + continue + nx, ny = arch_pos[phys_qubit] + x.append(nx) + y.append(ny) + color.append(considered_qubit_colors[log_qubit]) + text.append( + f"q{log_qubit}
1-gates: {single_qubit_multiplicity[log_qubit]}x
2-gates: {two_qubit_individual_multiplicity[log_qubit]}x" + ) + + scatter.x = x + scatter.y = y + scatter.text = text + scatter.marker.color = color + + +def _draw_swap_arrows( + arch_pos: dict[int, Position], + initial_layout: list[int], + swaps: tuple[tuple[int, int], ...], + considered_qubit_colors: dict[int, str], + arrow_offset: float, + arrow_spacing_x: float, + arrow_spacing_y: float, + shared_swaps: bool, + plotly_settings: dict[str, dict[str, Any]], +) -> list[go.layout.Annotation]: + layout = initial_layout.copy() + + swap_arrow_props = ( + {} + ) # (q0, q1) -> [{color: str, straight: bool, color2: Optional[str]}, ...] \\ q0 < q1, color2 is color at shaft side of arrow for a shared swap + for sw in swaps: + edge = (sw[0], sw[1]) if sw[0] < sw[1] else (sw[1], sw[0]) + if edge not in swap_arrow_props: + swap_arrow_props[edge] = [] + props_list = swap_arrow_props[edge] + + sw0_considered = layout[sw[0]] in considered_qubit_colors + sw1_considered = layout[sw[1]] in considered_qubit_colors + if shared_swaps and sw0_considered and sw1_considered: + props_list.append( + { + "color": considered_qubit_colors[layout[sw[0]]], + "straight": sw[0] < sw[1], + "color2": considered_qubit_colors[layout[sw[1]]], + } + ) + else: + if sw0_considered: + props_list.append( + {"color": considered_qubit_colors[layout[sw[0]]], "straight": sw[0] < sw[1], "color2": None} + ) + if sw1_considered: + props_list.append( + {"color": considered_qubit_colors[layout[sw[1]]], "straight": sw[0] >= sw[1], "color2": None} + ) + layout[sw[0]], layout[sw[1]] = layout[sw[1]], layout[sw[0]] + + list_of_arch_arrows = [] + for edge, props in swap_arrow_props.items(): + x0, y0 = arch_pos[edge[0]] + x1, y1 = arch_pos[edge[1]] + v = (x1 - x0, y1 - y0) + n = -v[1], v[0] + norm = (v[0] ** 2 + v[1] ** 2) ** 0.5 + + x0, y0 = x0 + arrow_offset * v[0], y0 + arrow_offset * v[1] + x1, y1 = x1 - arrow_offset * v[0], y1 - arrow_offset * v[1] + n = arrow_spacing_x * n[0] / norm, arrow_spacing_y * n[1] / norm + + n_offsets = (len(props) - 1) / 2 + for p in props: + a_x0, a_y0, a_x1, a_y1 = (x0, y0, x1, y1) if p["straight"] else (x1, y1, x0, y0) + a_x0, a_y0, a_x1, a_y1 = ( + a_x0 + n_offsets * n[0], + a_y0 + n_offsets * n[1], + a_x1 + n_offsets * n[0], + a_y1 + n_offsets * n[1], + ) + n_offsets -= 1 + if p["color2"] is not None: + mx, my = (a_x0 + a_x1) / 2, (a_y0 + a_y1) / 2 + _copy_to_dict( + plotly_settings["arrows"], + { + "x": a_x1, # arrow end + "y": a_y1, + "ax": mx, # arrow start + "ay": my, + "arrowcolor": p["color"], + }, + ) + arrow = go.layout.Annotation(plotly_settings["arrows"]) + list_of_arch_arrows.append(arrow) + _copy_to_dict( + plotly_settings["arrows"], + { + "x": a_x0, # arrow end + "y": a_y0, + "ax": mx, # arrow start + "ay": my, + "arrowcolor": p["color2"], + }, + ) + arrow = go.layout.Annotation(plotly_settings["arrows"]) + list_of_arch_arrows.append(arrow) + else: + _copy_to_dict( + plotly_settings["arrows"], + { + "x": a_x1, # arrow end + "y": a_y1, + "ax": a_x0, # arrow start + "ay": a_y0, + "arrowcolor": p["color"], + }, + ) + arrow = go.layout.Annotation(plotly_settings["arrows"]) + list_of_arch_arrows.append(arrow) + return list_of_arch_arrows + + +def _visualize_layout( + fig: go.Figure, + search_node: SearchNode, + arch_node_trace: go.Scatter, + arch_node_positions: dict[int, Position], + initial_layout: list[int], + considered_qubit_colors: dict[int, str], + swap_arrow_offset: float, + arch_x_arrow_spacing: float, + arch_y_arrow_spacing: float, + show_shared_swaps: bool, + layout_node_trace_index: int, # current_node_layout_visualized + search_node_trace: go.Scatter | None, + plotly_settings: dict[str, dict[str, Any]], +): + layout = search_node.layout + swaps = search_node.swaps + _copy_to_dict( + plotly_settings["stats_legend"], + { + "text": f"Node: {search_node.id}
" + + f"Cost: {search_node.total_cost():.3f} = {search_node.fixed_cost:.3f} + " + + f"{search_node.heuristic_cost:.3f} + {search_node.lookahead_penalty:.3f} (fixed + heuristic + lookahead)
" + + f"Depth: {search_node.depth}
" + + f'Valid / Final: {"yes" if search_node.is_valid_mapping else "no"} / {"yes" if search_node.final else "no"}', + }, + ) + stats = go.layout.Annotation(**plotly_settings["stats_legend"]) + annotations = [] + if len(swaps) > 0: + annotations = _draw_swap_arrows( + arch_node_positions, + initial_layout, + swaps, + considered_qubit_colors, + swap_arrow_offset, + arch_x_arrow_spacing, + arch_y_arrow_spacing, + show_shared_swaps, + plotly_settings, + ) + annotations.append(stats) + + arch_node_x = [] + arch_node_y = [] + for log_qubit, phys_qubit in enumerate(_reverse_layout(layout)): + if phys_qubit == -1: + continue + if log_qubit not in considered_qubit_colors: + continue + x, y = arch_node_positions[phys_qubit] + arch_node_x.append(x) + arch_node_y.append(y) + + with fig.batch_update(): + arch_node_trace.x = arch_node_x + arch_node_trace.y = arch_node_y + fig.layout.annotations = annotations + if search_node_trace is not None: + marker_line_widths = [0] * len(search_node_trace.x) + marker_line_widths[layout_node_trace_index] = 2 + search_node_trace.marker.line.width = marker_line_widths + + +def _load_layer_data( + data_logging_path: str, + layer: int, + layout_method: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], + layout_walker_factor: float, # 0-1 + tapered_layer_heights: bool, + number_of_node_traces: int, + use3d: bool, + node_color: list[str | Callable[[SearchNode], float]], + prioritize_node_color: list[bool], + node_height: list[Callable[[SearchNode], float]], + color_valid_mapping: str | None, + color_final_node: str | None, + draw_stems: bool, + draw_edges: bool, +) -> tuple[ + nx.Graph, # search_graph + list[int], # initial_layout + list[int], # initial_qbit_positions + dict[int, str], # considered_qubit_colors + list[int], # single_qbit_multiplicities + list[TwoQbitMultiplicity], # two_qbit_multiplicities + list[int], # individual_two_qbit_multiplicities + int, # final_node_id + list[float], # search_node_scatter_data_x + list[float], # search_node_scatter_data_y + list[list[float]], # search_node_scatter_data_z + list[list[float | str]], # search_node_scatter_data_color + list[float], # search_node_stem_scatter_data_x + list[float], # search_node_stem_scatter_data_y + list[float], # search_node_stem_scatter_data_z + list[float], # search_edge_scatter_data_x + list[float], # search_edge_scatter_data_y + list[list[float]], # search_edge_scatter_data_z + float, # search_min_x + float, # search_max_x + float, # search_min_y + float, # search_max_y + float, # search_min_z + float, # search_max_z +]: + if not os.path.exists(f"{data_logging_path}layer_{layer}.json"): + msg = f"No data at {data_logging_path}layer_{layer}.json" + raise FileNotFoundError(msg) + if not os.path.exists(f"{data_logging_path}nodes_layer_{layer}.csv"): + msg = f"No data at {data_logging_path}nodes_layer_{layer}.csv" + raise FileNotFoundError(msg) + + circuit_layer = None + with open(f"{data_logging_path}layer_{layer}.json") as circuit_layer_file: + circuit_layer = json.load(circuit_layer_file) + + single_q_mult = circuit_layer["single_qubit_multiplicity"] + two_q_mult_raw = circuit_layer["two_qubit_multiplicity"] + two_q_mult: list[TwoQbitMultiplicity] = [] + for mult in two_q_mult_raw: + two_q_mult.append(TwoQbitMultiplicity(mult["q1"], mult["q2"], mult["backward"], mult["forward"])) + initial_layout = circuit_layer["initial_layout"] + initial_positions = _reverse_layout(initial_layout) + final_node_id = circuit_layer["final_node_id"] + + graph, graph_root = _parse_search_graph(f"{data_logging_path}nodes_layer_{layer}.csv", final_node_id) + + pos = _layout_search_graph(graph, graph_root, layout_method, layout_walker_factor, tapered_layer_heights) + + ( + edge_x, + edge_y, + edge_z, + node_x, + node_y, + node_z, + node_color_data, + stem_x, + stem_y, + stem_z, + min_x, + max_x, + min_y, + max_y, + min_z, + max_z, + ) = _prepare_search_graph_scatter_data( + number_of_node_traces, + graph, + pos, + use3d, + node_color, + prioritize_node_color, + node_height, + color_valid_mapping, + color_final_node, + draw_stems, + draw_edges, + ) + + considered_qubit_colors = {} + considered_qubit_ngroups = 0 + two_q_mult_individual = [0] * len(single_q_mult) + for mult in two_q_mult: + two_q_mult_individual[mult.q0] += mult.backward + mult.forward + two_q_mult_individual[mult.q1] += mult.backward + mult.forward + considered_qubit_colors[mult.q0] = considered_qubit_ngroups + considered_qubit_colors[mult.q1] = considered_qubit_ngroups + considered_qubit_ngroups += 1 + for i, q in enumerate(single_q_mult): + if q != 0 and i not in considered_qubit_colors: + considered_qubit_colors[i] = considered_qubit_ngroups + considered_qubit_ngroups += 1 + considered_qubits_color_codes = [ + distinctipy.get_hex(c) for c in distinctipy.get_colors(max(10, considered_qubit_ngroups)) + ] + shuffle(considered_qubits_color_codes) + for q in considered_qubit_colors: + considered_qubit_colors[q] = considered_qubits_color_codes[considered_qubit_colors[q]] + + return ( + graph, + initial_layout, + initial_positions, + considered_qubit_colors, + single_q_mult, + two_q_mult, + two_q_mult_individual, + final_node_id, + node_x, + node_y, + node_z, + node_color_data, + stem_x, + stem_y, + stem_z, + edge_x, + edge_y, + edge_z, + min_x, + max_x, + min_y, + max_y, + min_z, + max_z, + ) + + +def _total_cost_lambda(n: SearchNode) -> float: + return n.total_cost() + + +def _total_fixed_cost_lambda(n: SearchNode) -> float: + return n.total_fixed_cost() + + +def _fixed_cost_lambda(n: SearchNode) -> float: + return n.fixed_cost + + +def _heuristic_cost_lambda(n: SearchNode) -> float: + return n.heuristic_cost + + +def _lookahead_penalty_lambda(n: SearchNode) -> float: + return n.lookahead_penalty + + +def _cost_string_to_lambda(cost_string: str) -> Callable[[SearchNode], float] | None: + if cost_string == "total_cost": + return _total_cost_lambda + elif cost_string == "total_fixed_cost": + return _total_fixed_cost_lambda + elif cost_string == "fixed_cost": + return _fixed_cost_lambda + elif cost_string == "heuristic_cost": + return _heuristic_cost_lambda + elif cost_string == "lookahead_penalty": + return _lookahead_penalty_lambda + else: + return None + + +default_plotly_settings = { + "layout": { + "autosize": False, + "showlegend": False, + "hovermode": "closest", + "coloraxis_colorbar_x": -0.15, + }, + "arrows": { + "text": "", + "showarrow": True, + "arrowhead": 5, + "arrowwidth": 2, + }, + "stats_legend": { + "align": "left", + "showarrow": False, + "xref": "paper", + "yref": "paper", + "x": 1, + "y": 1.175, + "bordercolor": "black", + "borderwidth": 1, + }, + "search_nodes": { + "mode": "markers", + "hoverinfo": "none", + "marker": { + "showscale": True, + "size": 10, + "colorbar": { + "thickness": 15, + "orientation": "h", + "lenmode": "fraction", + "len": 0.5, + "xref": "paper", + "x": 0.21, + "yref": "container", + }, + }, + }, + "search_node_stems": {"hoverinfo": "none", "mode": "lines"}, + "search_edges": {"hoverinfo": "none", "mode": "lines"}, + "architecture_nodes": { + "mode": "markers", + "hoverinfo": "text", + "marker": { + "size": 10, + }, + }, + "architecture_edges": {"line": {"width": 0.5, "color": "#888"}, "hoverinfo": "none", "mode": "lines"}, + "architecture_edge_labels": {"hoverinfo": "none", "mode": "text"}, + "search_xaxis": {"showgrid": False, "zeroline": False, "showticklabels": False, "title_text": ""}, + "search_yaxis": {"showgrid": False, "zeroline": False, "showticklabels": False, "title_text": ""}, + "search_zaxis": {"title_text": ""}, + "architecture_xaxis": {"showgrid": False, "zeroline": False, "showticklabels": False, "title_text": ""}, + "architecture_yaxis": {"showgrid": False, "zeroline": False, "showticklabels": False, "title_text": ""}, +} + + +def _visualize_search_graph_check_parameters( + data_logging_path: str, + layer: int | Literal["interactive"], + architecture_node_positions: dict[int, Position] | None, + architecture_layout_method: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], + search_node_layout_method: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], + search_node_layout_walker_factor: float, + search_graph_border: float, + architecture_border: float, + swap_arrow_spacing: float, + swap_arrow_offset: float, + use3d: bool, + projection: Literal["orthographic", "perspective"], + width: int, + height: int, + draw_search_edges: bool, + search_edges_width: float, + search_edges_color: str, + search_edges_dash: str, + tapered_search_layer_heights: bool, + show_layout: Literal["hover", "click"] | None, + show_swaps: bool, + show_shared_swaps: bool, + color_valid_mapping: str | None, + color_final_node: str | None, + search_node_color: str | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]), + prioritize_search_node_color: bool | list[bool], + search_node_color_scale: Colorscale | list[Colorscale], + search_node_invert_color_scale: bool | list[bool], + search_node_colorbar_title: str | list[str | None] | None, + search_node_colorbar_spacing: float, + search_node_height: str | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]), + draw_stems: bool, + stems_width: float, + stems_color: str, + stems_dash: str, + show_search_progression: bool, + search_progression_step: int, + search_progression_speed: float, + plotly_settings: dict[str, dict[str, Any]], +) -> tuple[ + str, # data_logging_path + bool, # hide_layout + bool, # draw_stems + int, # number_of_node_traces + list[Callable[[SearchNode], float]], # search_node_height + list[str | Callable[[SearchNode], float]], # search_node_color + list[Colorscale], # search_node_color_scale + list[bool], # search_node_invert_color_scale + list[bool], # prioritize_search_node_color + list[str], # search_node_colorbar_title + dict[str, dict[str, Any]], # plotly_settings +]: + if type(data_logging_path) is not str: + msg = "data_logging_path must be a string" + raise ValueError(msg) + if data_logging_path[-1] != "/": + data_logging_path += "/" + if not os.path.exists(data_logging_path): + msg = f"Path {data_logging_path} does not exist." + raise FileNotFoundError(msg) + + if type(layer) is not int and layer != "interactive": + msg = 'layer must be an integer or string literal "interactive"' + raise ValueError(msg) + + if architecture_node_positions is not None: + if type(architecture_node_positions) is not dict: + msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" + raise ValueError(msg) + for i in architecture_node_positions: + if type(i) is not int: + msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" + raise ValueError(msg) + if type(architecture_node_positions[i]) is not tuple: + msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" + raise ValueError(msg) + if len(architecture_node_positions[i]) != 2: + msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" + raise ValueError(msg) + if not _is_number(architecture_node_positions[i][0]) or not _is_number(architecture_node_positions[i][1]): + msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" + raise ValueError(msg) + + if architecture_layout_method not in ["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"]: + msg = 'architecture_layout_method must be one of "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"' + raise ValueError(msg) + + if search_node_layout_method not in [ + "walker", + "dot", + "neato", + "fdp", + "sfdp", + "circo", + "twopi", + "osage", + "patchwork", + ]: + msg = 'search_node_layout_method must be one of "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"' + raise ValueError(msg) + + if search_node_layout_method == "walker" and ( + not _is_number(search_node_layout_walker_factor) or search_node_layout_walker_factor < 0 + ): + msg = "search_node_layout_walker_factor must be a non-negative float" + raise ValueError(msg) + + if not _is_number(search_graph_border) or search_graph_border < 0: + msg = "search_graph_border must be a non-negative float" + raise ValueError(msg) + + if not _is_number(architecture_border) or architecture_border < 0: + msg = "architecture_border must be a non-negative float" + raise ValueError(msg) + + if not _is_number(swap_arrow_spacing) or swap_arrow_spacing < 0: + msg = "swap_arrow_spacing must be a non-negative float" + raise ValueError(msg) + + if not _is_number(swap_arrow_offset) or swap_arrow_offset < 0 or swap_arrow_offset >= 0.5: + msg = "swap_arrow_offset must be a float between 0 and 0.5" + raise ValueError(msg) + + if type(use3d) is not bool: + msg = "use3d must be a boolean" + raise ValueError(msg) + + if projection != "orthographic" and projection != "perspective": + msg = 'projection must be either "orthographic" or "perspective"' + raise ValueError(msg) + + if type(width) is not int or width < 1: + msg = "width must be a positive integer" + raise ValueError(msg) + + if type(height) is not int or height < 1: + msg = "height must be a positive integer" + raise ValueError(msg) + + if type(draw_search_edges) is not bool: + msg = "draw_search_edges must be a boolean" + raise ValueError(msg) + + if type(search_edges_width) is not float or search_edges_width <= 0: + msg = "search_edges_width must be a positive float" + raise ValueError(msg) + + if ColorValidator.perform_validate_coerce(search_edges_color, allow_number=False) is None: + raise ValueError(ColorValidator("search_edges_color", "visualize_search_graph").description()) + + if search_edges_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not re.match( + r"^(\d+(px|%)?(\s*\d+(px|%)?)*)$", search_edges_dash + ): + raise ValueError( + 'search_edges_dash must be one of "solid", "dot", "dash", "longdash", "dashdot", "longdashdot" or a string containing a dash length list in ' + + r'pixels or percentages (e.g. "5px 10px 2px 2px", "5, 10, 2, 2", "10\% 20\% 40\%")' + ) + + if type(tapered_search_layer_heights) is not bool: + msg = "tapered_search_layer_heights must be a boolean" + raise ValueError(msg) + + if show_layout not in ["hover", "click"] and show_layout is not None: + msg = 'show_layout must be one of "hover", "click" or None' + raise ValueError(msg) + hide_layout = show_layout is None + + if type(show_swaps) is not bool: + msg = "show_swaps must be a boolean" + raise ValueError(msg) + + if type(show_shared_swaps) is not bool: + msg = "show_shared_swaps must be a boolean" + raise ValueError(msg) + + if ( + color_valid_mapping is not None + and ColorValidator.perform_validate_coerce(color_valid_mapping, allow_number=False) is None + ): + raise ValueError( + "color_valid_mapping must be None or a color specified as:\n" + + _remove_first_lines(ColorValidator("color_valid_mapping", "visualize_search_graph").description(), 1) + ) + + if ( + color_final_node is not None + and ColorValidator.perform_validate_coerce(color_final_node, allow_number=False) is None + ): + raise ValueError( + "color_final_node must be None or a color specified as:\n" + + _remove_first_lines(ColorValidator("color_final_node", "visualize_search_graph").description(), 1) + ) + + if type(draw_stems) is not bool: + msg = "draw_stems must be a boolean" + raise ValueError(msg) + + if type(stems_width) is not float or stems_width <= 0: + msg = "stems_width must be a positive float" + raise ValueError(msg) + + if ColorValidator.perform_validate_coerce(stems_color, allow_number=False) is None: + raise ValueError(ColorValidator("stems_color", "visualize_search_graph").description()) + + if stems_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not re.match( + r"^(\d+(px|%)?(\s*\d+(px|%)?)*)$", stems_dash + ): + raise ValueError( + 'stems_dash must be one of "solid", "dot", "dash", "longdash", "dashdot", "longdashdot" or a string containing a dash length list in ' + + r'pixels or percentages (e.g. "5px 10px 2px 2px", "5, 10, 2, 2", "10\% 20\% 40\%")' + ) + + if type(show_search_progression) is not bool: + msg = "show_search_progression must be a boolean" + raise ValueError(msg) + + if type(search_progression_step) is not int or search_progression_step < 1: + msg = "search_porgression_step must be a positive integer" + raise ValueError(msg) + + if not _is_number(search_progression_speed) or search_progression_speed <= 0: + msg = "search_progression_speed must be a positive float" + raise ValueError(msg) + + if type(plotly_settings) is not dict and any( + ( + type(plotly_settings[key]) is not dict + or key + not in [ + "layout", + "arrows", + "stats_legend", + "search_nodes", + "search_edges", + "architecture_nodes", + "architecture_edges", + "architecture_edge_labels", + "search_xaxis", + "search_yaxis", + "search_zaxis", + "architecture_xaxis", + "architecture_yaxis", + ] + ) + for key in plotly_settings + ): + raise ValueError( + "plotly_settings must be a dict with any of these entries:" + + "{\n" + + " 'layout': settings for plotly.graph_objects.Layout (of subplots figure)\n" + + " 'arrows': settings for plotly.graph_objects.layout.Annotation\n" + + " 'stats_legend': settings for plotly.graph_objects.layout.Annotation\n" + + " 'search_nodes': settings for plotly.graph_objects.Scatter resp. ...Scatter3d\n" + + " 'search_edges': settings for plotly.graph_objects.Scatter resp. ...Scatter3d\n" + + " 'architecture_nodes': settings for plotly.graph_objects.Scatter\n" + + " 'architecture_edges': settings for plotly.graph_objects.Scatter\n" + + " 'architecture_edge_labels': settings for plotly.graph_objects.Scatter\n" + + " 'search_xaxis': settings for plotly.graph_objects.layout.XAxis resp. ...layout.scene.XAxis\n" + + " 'search_yaxis': settings for plotly.graph_objects.layout.YAxis resp. ...layout.scene.YAxis\n" + + " 'search_zaxis': settings for plotly.graph_objects.layout.scene.ZAxis\n" + + " 'architecture_xaxis': settings for plotly.graph_objects.layout.XAxis\n" + + " 'architecture_yaxis': settings for plotly.graph_objects.layout.YAxis\n" + + "}" + ) + + plotly_set = deepcopy(default_plotly_settings) + plotly_set["layout"]["width"] = width + plotly_set["layout"]["height"] = height + plotly_set["search_node_stems"]["line"] = {"width": stems_width, "color": stems_color, "dash": stems_dash} + plotly_set["search_edges"]["line"] = { + "width": search_edges_width, + "color": search_edges_color, + "dash": search_edges_dash, + } + if use3d: + plotly_set["layout"]["scene"] = {"camera": {"projection": {"type": projection}}} + plotly_set["arrows"]["xref"] = "x" + plotly_set["arrows"]["yref"] = "y" + plotly_set["arrows"]["axref"] = "x" + plotly_set["arrows"]["ayref"] = "y" + else: + draw_stems = False + plotly_set["arrows"]["xref"] = "x2" + plotly_set["arrows"]["yref"] = "y2" + plotly_set["arrows"]["axref"] = "x2" + plotly_set["arrows"]["ayref"] = "y2" + _copy_to_dict(plotly_set, plotly_settings) + + number_of_node_traces = len(search_node_height) if use3d and _is_len_iterable(search_node_height) else 1 + + if ( + not _is_number(search_node_colorbar_spacing) + or search_node_colorbar_spacing <= 0 + or search_node_colorbar_spacing >= 1 + ): + msg = "search_node_colorbar_spacing must be a float between 0 and 1" + raise ValueError(msg) + + if search_node_colorbar_title is None: + search_node_colorbar_title = [None] * number_of_node_traces + if _is_len_iterable(search_node_colorbar_title): + if len(search_node_colorbar_title) > 1 and not use3d: + msg = "search_node_colorbar_title can only be a list in a 3D plot." + raise ValueError(msg) + if len(search_node_colorbar_title) != number_of_node_traces: + msg = f"Length of search_node_colorbar_title ({len(search_node_colorbar_title)}) does not match length of search_node_height ({number_of_node_traces})." + raise ValueError(msg) + untitled = 1 + for i, title in enumerate(search_node_colorbar_title): + if title is None: + color = None + if _is_len_iterable(search_node_color): + if len(search_node_color) == len(search_node_colorbar_title): + color = search_node_color[i] + else: + color = search_node_color[0] + else: + color = search_node_color + if type(color) is not str: + search_node_colorbar_title[i] = f"Untitled{untitled}" + untitled += 1 + elif color == "total_cost": + search_node_colorbar_title[i] = "Total cost" + elif color == "total_fixed_cost": + search_node_colorbar_title[i] = "Total fixed cost" + elif color == "fixed_cost": + search_node_colorbar_title[i] = "Fixed cost" + elif color == "heuristic_cost": + search_node_colorbar_title[i] = "Heuristic cost" + elif color == "lookahead_penalty": + search_node_colorbar_title[i] = "Lookahead penalty" + else: + search_node_colorbar_title[i] = color + elif type(title) is not str: + msg = "search_node_colorbar_title must be None, a string, or list of strings and None." + raise ValueError(msg) + elif type(search_node_colorbar_title) is str: + search_node_colorbar_title = [search_node_colorbar_title] * number_of_node_traces + else: + msg = "search_node_colorbar_title must be None, a string, or list of strings and None." + raise ValueError(msg) + + if _is_len_iterable(search_node_color_scale): + if len(search_node_color_scale) > 1 and not use3d: + msg = "search_node_color_scale can only be a list in a 3D plot." + raise ValueError(msg) + if len(search_node_color_scale) != number_of_node_traces: + msg = f"Length of search_node_color_scale ({len(search_node_color_scale)}) does not match length of search_node_height ({number_of_node_traces})." + raise ValueError(msg) + cs_validator = ColorscaleValidator("search_node_color_scale", "visualize_search_graph") + for cs in search_node_color_scale: + try: + cs_validator.validate_coerce(cs) + except: + raise ValueError( + "search_node_color_scale must be a list of colorscales or a colorscale, specified as:\n" + + _remove_first_lines(cs_validator.description(), 1) + ) + else: + cs_validator = ColorscaleValidator("search_node_color_scale", "visualize_search_graph") + try: + cs_validator.validate_coerce(search_node_color_scale) + except: + raise ValueError( + "search_node_color_scale must be a list of colorscales or a colorscale, specified as:\n" + + _remove_first_lines(cs_validator.description(), 1) + ) + search_node_color_scale = [search_node_color_scale] * number_of_node_traces + + if _is_len_iterable(search_node_invert_color_scale): + if len(search_node_invert_color_scale) > 1 and not use3d: + msg = "search_node_invert_color_scale can only be a list in a 3D plot." + raise ValueError(msg) + if len(search_node_invert_color_scale) != number_of_node_traces: + msg = f"Length of search_node_invert_color_scale ({len(search_node_invert_color_scale)}) does not match length of search_node_height ({number_of_node_traces})." + raise ValueError(msg) + for i, invert in enumerate(search_node_invert_color_scale): + if type(invert) is not bool: + msg = "search_node_invert_color_scale must be a boolean or list of booleans." + raise ValueError(msg) + elif type(search_node_invert_color_scale) is not bool: + msg = "search_node_invert_color_scale must be a boolean or list of booleans." + raise ValueError(msg) + else: + search_node_invert_color_scale = [search_node_invert_color_scale] * number_of_node_traces + + if _is_len_iterable(prioritize_search_node_color): + if len(prioritize_search_node_color) > 1 and not use3d: + msg = "prioritize_search_node_color can only be a list in a 3D plot." + raise ValueError(msg) + if len(prioritize_search_node_color) != number_of_node_traces: + msg = f"Length of prioritize_search_node_color ({len(prioritize_search_node_color)}) does not match length of search_node_height ({number_of_node_traces})." + raise ValueError(msg) + for i, invert in enumerate(prioritize_search_node_color): + if type(invert) is not bool: + msg = "prioritize_search_node_color must be a boolean or list of booleans." + raise ValueError(msg) + elif type(prioritize_search_node_color) is not bool: + msg = "prioritize_search_node_color must be a boolean or list of booleans." + raise ValueError(msg) + else: + prioritize_search_node_color = [prioritize_search_node_color] * number_of_node_traces + + if _is_len_iterable(search_node_color): + if len(search_node_color) > 1 and not use3d: + msg = "search_node_color can only be a list in a 3D plot." + raise ValueError(msg) + if len(search_node_color) != number_of_node_traces: + msg = f"Length of search_node_color ({len(search_node_color)}) does not match length of search_node_height ({number_of_node_traces})." + raise ValueError(msg) + for i, c in enumerate(search_node_color): + if isinstance(c, str): + l = _cost_string_to_lambda(c) + if l is not None: + search_node_color[i] = l + elif ColorValidator.perform_validate_coerce(c, allow_number=False) is None: + raise ValueError( + f'search_node_color[{i}] is neither a valid cost function preset ("total_cost", "total_fixed_cost", "fixed_cost", ' + + '"heuristic_cost", "lookahead_penalty") nor a respective callable, nor a valid color string specified as:\n' + + _remove_first_lines( + ColorValidator("search_node_color", "visualize_search_graph").description(), 1 + ) + ) + else: # static color + search_node_colorbar_title[i] = None + elif isinstance(search_node_color, str): + l = _cost_string_to_lambda(search_node_color) + if l is not None: + search_node_color = [l] + elif ColorValidator.perform_validate_coerce(search_node_color, allow_number=False) is None: + raise ValueError( + 'search_node_color is neither a list nor a valid cost function preset ("total_cost", "total_fixed_cost", "fixed_cost", ' + + '"heuristic_cost", "lookahead_penalty") nor a respective callable, nor a valid color string specified as:\n' + + _remove_first_lines(ColorValidator("search_node_color", "visualize_search_graph").description(), 1) + ) + else: # static color + search_node_color = [search_node_color] + search_node_colorbar_title = [None] * number_of_node_traces + else: + raise ValueError( + 'search_node_color must be a cost function preset ("total_cost", "total_fixed_cost", "fixed_cost", "heuristic_cost", ' + + '"lookahead_penalty") or a respective callable, or a valid color string, or a list of the above.' + ) + + if use3d: + if _is_len_iterable(search_node_height): + lambdas = set() + for i, c in enumerate(search_node_height): + if isinstance(c, str): + l = _cost_string_to_lambda(c) + if l is None: + msg = f"Unkown cost function preset search_node_height[{i}]: {c}" + raise ValueError(msg) + elif l in lambdas: + msg = f"search_node_height must not contain the same cost function multiple times: {c}" + raise ValueError(msg) + else: + search_node_height[i] = l + lambdas.add(l) + elif not callable(c): + raise ValueError( + "search_node_height must be a cost function preset ('total_cost', 'total_fixed_cost', 'fixed_cost', 'heuristic_cost', " + + "'lookahead_penalty') or a respective callable, or a list of the above." + ) + elif isinstance(search_node_height, str): + l = _cost_string_to_lambda(search_node_height) + if l is not None: + search_node_height = [l] + else: + msg = f"Unkown cost function preset search_node_height: {search_node_height}" + raise ValueError(msg) + else: + msg = "search_node_height must be a list of cost functions or a single cost function." + raise ValueError(msg) + else: + search_node_height = [] + + return ( + data_logging_path, + hide_layout, + draw_stems, + number_of_node_traces, + search_node_height, + search_node_color, + search_node_color_scale, + search_node_invert_color_scale, + prioritize_search_node_color, + search_node_colorbar_title, + plotly_set, + ) + + +def visualize_search_graph( + data_logging_path: str, + layer: int | Literal["interactive"] = "interactive", # 'interactive' (slider menu) | index + architecture_node_positions: dict[int, Position] | None = None, + architecture_layout_method: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"] = "sfdp", + search_node_layout_method: Literal[ + "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" + ] = "walker", + search_node_layout_walker_factor: float = 0.6, + search_graph_border: float = 0.05, + architecture_border: float = 0.05, + swap_arrow_spacing: float = 0.05, + swap_arrow_offset: float = 0.05, + use3d: bool = True, + projection: Literal["orthographic", "perspective"] = "perspective", + width: int = 1400, + height: int = 700, + draw_search_edges: bool = True, + search_edges_width: float = 0.5, + search_edges_color: str = "#888", + search_edges_dash: str = "solid", # 'solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot', string containing a dash length list in pixels or percentages (e.g. '5px 10px 2px 2px', '5, 10, 2, 2', '10% 20% 40%') + tapered_search_layer_heights: bool = True, + show_layout: Literal["hover", "click"] | None = "hover", + show_swaps: bool = True, + show_shared_swaps: bool = True, # if one swap moves 2 considered qubits -> combine into one two-colored and two-headed arrow + color_valid_mapping: str | None = "green", # static HTML color (e.g. 'blue' or '#0000FF') + color_final_node: str | None = "red", # static HTML color (e.g. 'blue' or '#0000FF') + search_node_color: str | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]) = "total_cost", + # 'total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty' | static HTML color (e.g. 'blue' or '#0000FF') | + # function that takes a SearchNode and returns a float (e.g. lambda n: n.fixed_cost + n.heuristic_cost) + # node fields: {"fixed_cost": float, "heuristic_cost": float, "lookahead_penalty": float, "is_valid_mapping": bool, + # "final": bool, "depth": int, "layout": Tuple[int, ...], "swaps": Tuple[Tuple[int, int], ...]} + # or list of the above if 3d graph is used and multiple points per node are defined in search_node_height (lengths need to match) + # if in that case no is list is provided all points per node will be the same color + prioritize_search_node_color: bool + | list[ + bool + ] = False, # if True, search_node_color will be prioritized over color_valid_mapping and color_final_node + search_node_color_scale: str | list[str] = "YlGnBu", # https://plotly.com/python/builtin-colorscales/ + # aggrnyl agsunset blackbody bluered blues blugrn bluyl brwnyl + # bugn bupu burg burgyl cividis darkmint electric emrld + # gnbu greens greys hot inferno jet magenta magma + # mint orrd oranges oryel peach pinkyl plasma plotly3 + # pubu pubugn purd purp purples purpor rainbow rdbu + # rdpu redor reds sunset sunsetdark teal tealgrn turbo + # viridis ylgn ylgnbu ylorbr ylorrd algae amp deep + # dense gray haline ice matter solar speed tempo + # thermal turbid armyrose brbg earth fall geyser prgn + # piyg picnic portland puor rdgy rdylbu rdylgn spectral + # tealrose temps tropic balance curl delta oxy edge + # hsv icefire phase twilight mrybm mygbm armylg falllg + search_node_invert_color_scale: bool | list[bool] = True, + search_node_colorbar_title: str | list[str | None] | None = None, + search_node_colorbar_spacing: float = 0.06, + search_node_height: str + | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]) = "total_cost", + # just as with search_node_color (without color strings), but possible to specify a list to draw multiple point per node on different heights + # only applicable if use3d is True + draw_stems: bool = False, + stems_width: float = 0.7, + stems_color: str = "#444", + stems_dash: str = "solid", # 'solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot', string containing a dash length list in pixels or percentages (e.g. '5px 10px 2px 2px', '5, 10, 2, 2', '10% 20% 40%') + show_search_progression: bool = True, + search_progression_step: int = 10, + search_progression_speed: float = 2, # steps per second + plotly_settings: dict[str, dict[str, Any]] | None = None, + # { + # 'layout': settings for plotly.graph_objects.Layout (of subplots figure) + # 'arrows': settings for plotly.graph_objects.layout.Annotation + # 'stats_legend': settings for plotly.graph_objects.layout.Annotation + # 'search_nodes': settings for plotly.graph_objects.Scatter resp. ...Scatter3d + # 'search_edges': settings for plotly.graph_objects.Scatter resp. ...Scatter3d + # 'architecture_nodes': settings for plotly.graph_objects.Scatter + # 'architecture_edges': settings for plotly.graph_objects.Scatter + # 'architecture_edge_labels': settings for plotly.graph_objects.Scatter + # 'search_xaxis': settings for plotly.graph_objects.layout.XAxis resp. ...layout.scene.XAxis + # 'search_yaxis': settings for plotly.graph_objects.layout.YAxis resp. ...layout.scene.YAxis + # 'search_zaxis': settings for plotly.graph_objects.layout.scene.ZAxis + # 'architecture_xaxis': settings for plotly.graph_objects.layout.XAxis + # 'architecture_yaxis': settings for plotly.graph_objects.layout.YAxis + # } + # TODO: show archticture edge labels (and control text?) + # TODO: control hover text of search (especially for multiple points per node!) and architecture nodes? +) -> Widget: + # check and process all parameters + if plotly_settings is None: + plotly_settings = {} + ( + data_logging_path, + hide_layout, + draw_stems, + number_of_node_traces, + search_node_height, + search_node_color, + search_node_color_scale, + search_node_invert_color_scale, + prioritize_search_node_color, + search_node_colorbar_title, + plotly_settings, + ) = _visualize_search_graph_check_parameters( + data_logging_path, + layer, + architecture_node_positions, + architecture_layout_method, + search_node_layout_method, + search_node_layout_walker_factor, + search_graph_border, + architecture_border, + swap_arrow_spacing, + swap_arrow_offset, + use3d, + projection, + width, + height, + draw_search_edges, + search_edges_width, + search_edges_color, + search_edges_dash, + tapered_search_layer_heights, + show_layout, + show_swaps, + show_shared_swaps, + color_valid_mapping, + color_final_node, + search_node_color, + prioritize_search_node_color, + search_node_color_scale, + search_node_invert_color_scale, + search_node_colorbar_title, + search_node_colorbar_spacing, + search_node_height, + draw_stems, + stems_width, + stems_color, + stems_dash, + show_search_progression, + search_progression_step, + search_progression_speed, + plotly_settings, + ) + + # function-wide variables + search_graph: nx.Graph | None = None + arch_graph: nx.Graph | None = None + initial_layout: list[int] = [] + considered_qubit_colors: dict[int, str] = {} + current_node_layout_visualized: int | None = None + current_layer: int | None = None + + arch_x_spacing: float | None = None + arch_y_spacing: float | None = None + arch_x_arrow_spacing: float | None = None + arch_y_arrow_spacing: float | None = None + arch_x_min: float | None = None + arch_x_max: float | None = None + arch_y_min: float | None = None + arch_y_max: float | None = None + + search_node_traces: list[go.Scatter | go.Scatter3d] = [] + search_edge_traces: list[go.Scatter | go.Scatter3d] = [] + search_node_stem_trace: go.Scatter3d | None = None + arch_node_trace: go.Scatter | None = None + arch_edge_trace: go.Scatter | None = None + arch_edge_label_trace: go.Scatter | None = None + + # one possible entry per layer (if not present, layer was not loaded yet) + search_graphs: dict[int, nx.Graph] = {} + initial_layouts: dict[int, list[int]] = {} + initial_qbit_positions: dict[int, list[int]] = {} # reverses of initial_layouts + layers_considered_qubit_colors: dict[int, dict[int, str]] = {} + single_qbit_multiplicities: dict[int, list[int]] = {} + individual_two_qbit_multiplicities: dict[int, list[int]] = {} + two_qbit_multiplicities: dict[int, list[TwoQbitMultiplicity]] = {} + final_node_ids: dict[int, int] = {} + search_node_scatter_data_x: dict[int, list[float]] = {} + search_node_scatter_data_y: dict[int, list[float]] = {} + search_node_scatter_data_z: dict[int, list[list[float]]] = {} + search_node_scatter_data_color: dict[int, list[list[float | str]]] = {} + search_node_stem_scatter_data_x: dict[int, list[float]] = {} + search_node_stem_scatter_data_y: dict[int, list[float]] = {} + search_node_stem_scatter_data_z: dict[int, list[float]] = {} + search_edge_scatter_data_x: dict[int, list[float]] = {} + search_edge_scatter_data_y: dict[int, list[float]] = {} + search_edge_scatter_data_z: dict[int, list[list[float]]] = {} + search_min_x: dict[int, float] = {} + search_max_x: dict[int, float] = {} + search_min_y: dict[int, float] = {} + search_max_y: dict[int, float] = {} + search_min_z: dict[int, float] = {} + search_max_z: dict[int, float] = {} + + sub_plots: go.Figure | None = None + + number_of_layers = 0 + + # parse general mapping info + with open(f"{data_logging_path}mapping_result.json") as result_file: + number_of_layers = json.load(result_file)["statistics"]["layers"] + + if type(layer) is int and layer >= number_of_layers: + msg = f"Invalid layer {layer}. There are only {number_of_layers} layers in the data log." + raise ValueError(msg) + + # prepare search graph traces + search_node_traces, search_edge_traces, search_node_stem_trace = _prepare_search_graph_scatters( + number_of_node_traces, + search_node_color_scale, + search_node_invert_color_scale, + search_node_colorbar_title, + search_node_colorbar_spacing, + use3d, + draw_stems, + draw_search_edges, + plotly_settings, + ) + + # parse architecture info and prepare respective traces + if not hide_layout: + arch_graph = _parse_arch_graph(f"{data_logging_path}architecture.json") + + if architecture_node_positions is None: + for node in arch_graph.nodes: + print(node, arch_graph.nodes[node]) + for edge in arch_graph.edges: + print(edge, arch_graph.edges[edge]) + architecture_node_positions = graphviz_layout(arch_graph, prog=architecture_layout_method) + elif len(architecture_node_positions) != len(arch_graph.nodes): + msg = f"architecture_node_positions must contain positions for all {len(arch_graph.nodes)} architecture nodes." + raise ValueError(msg) + + arch_x_spacing = _get_avg_min_distance([p[0] for p in architecture_node_positions.values()]) + arch_y_spacing = _get_avg_min_distance([p[1] for p in architecture_node_positions.values()]) + arch_x_arrow_spacing = (arch_x_spacing if arch_x_spacing > 0 else arch_y_spacing) * swap_arrow_spacing + arch_y_arrow_spacing = (arch_y_spacing if arch_y_spacing > 0 else arch_x_spacing) * swap_arrow_spacing + arch_x_min = min(architecture_node_positions.values(), key=lambda p: p[0])[0] + arch_x_max = max(architecture_node_positions.values(), key=lambda p: p[0])[0] + arch_y_min = min(architecture_node_positions.values(), key=lambda p: p[1])[1] + arch_y_max = max(architecture_node_positions.values(), key=lambda p: p[1])[1] + + arch_edge_trace, arch_edge_label_trace = _draw_architecture_edges( + arch_graph, architecture_node_positions, plotly_settings + ) + + arch_node_trace = go.Scatter(**plotly_settings["architecture_nodes"]) + arch_node_trace.x = [] + arch_node_trace.y = [] + + # create figure and add traces + if not hide_layout: + sub_plots = make_subplots(rows=1, cols=2, specs=[[{"is_3d": use3d}, {"is_3d": False}]]) + else: + sub_plots = go.Figure() + sub_plots.update_layout(plotly_settings["layout"]) + + active_trace_indices = {} + + active_trace_indices["search_edges"] = [] + for trace in search_edge_traces: + active_trace_indices["search_edges"].append(len(sub_plots.data)) + sub_plots.add_trace(trace, row=1 if not hide_layout else None, col=1 if not hide_layout else None) + + if draw_stems: + active_trace_indices["search_node_stems"] = len(sub_plots.data) + sub_plots.add_trace( + search_node_stem_trace, row=1 if not hide_layout else None, col=1 if not hide_layout else None + ) + + active_trace_indices["search_nodes"] = [] + for trace in search_node_traces: + active_trace_indices["search_nodes"].append(len(sub_plots.data)) + sub_plots.add_trace(trace, row=1 if not hide_layout else None, col=1 if not hide_layout else None) + + xaxis1 = ( + sub_plots.layout.scene.xaxis if use3d else (sub_plots.layout.xaxis if hide_layout else sub_plots.layout.xaxis1) + ) + yaxis1 = ( + sub_plots.layout.scene.yaxis if use3d else (sub_plots.layout.yaxis if hide_layout else sub_plots.layout.yaxis1) + ) + xaxis1.update(**plotly_settings["search_xaxis"]) + yaxis1.update(**plotly_settings["search_yaxis"]) + if use3d: + sub_plots.layout.scene.zaxis.update(**plotly_settings["search_zaxis"]) + + if not hide_layout: + active_trace_indices["arch_edges"] = len(sub_plots.data) + sub_plots.add_trace(arch_edge_trace, row=1, col=2) + active_trace_indices["arch_edge_labels"] = len(sub_plots.data) + sub_plots.add_trace(arch_edge_label_trace, row=1, col=2) + active_trace_indices["arch_nodes"] = len(sub_plots.data) + sub_plots.add_trace(arch_node_trace, row=1, col=2) + arch_node_trace = sub_plots.data[-1] + + xaxis2 = sub_plots["layout"]["xaxis"] if use3d else sub_plots["layout"]["xaxis2"] + yaxis2 = sub_plots["layout"]["yaxis"] if use3d else sub_plots["layout"]["yaxis2"] + + plotly_settings["architecture_xaxis"]["range"] = [ + arch_x_min - abs(arch_x_max - arch_x_min) * architecture_border, + arch_x_max + abs(arch_x_max - arch_x_min) * architecture_border, + ] + plotly_settings["architecture_yaxis"]["range"] = [ + arch_y_min - abs(arch_y_max - arch_y_min) * architecture_border, + arch_y_max + abs(arch_y_max - arch_y_min) * architecture_border, + ] + x_diff = plotly_settings["architecture_xaxis"]["range"][1] - plotly_settings["architecture_xaxis"]["range"][0] + y_diff = plotly_settings["architecture_yaxis"]["range"][1] - plotly_settings["architecture_yaxis"]["range"][0] + if x_diff == 0: + mid = plotly_settings["architecture_xaxis"]["range"][0] + plotly_settings["architecture_xaxis"]["range"] = [mid - y_diff / 2, mid + y_diff / 2] + if y_diff == 0: + mid = plotly_settings["architecture_yaxis"]["range"][0] + plotly_settings["architecture_yaxis"]["range"] = [mid - x_diff / 2, mid + x_diff / 2] + + xaxis2.update(**plotly_settings["architecture_xaxis"]) + yaxis2.update(**plotly_settings["architecture_yaxis"]) + + fig = go.FigureWidget(sub_plots) + + # update trace variables to active traces (traces are internally copied when creating fig) + search_node_traces = [] + for i in active_trace_indices["search_nodes"]: + search_node_traces.append(fig.data[i]) + search_edge_traces = [] + for i in active_trace_indices["search_edges"]: + search_edge_traces.append(fig.data[i]) + if draw_stems: + search_node_stem_trace = fig.data[active_trace_indices["search_node_stems"]] + + if not hide_layout: + arch_node_trace = fig.data[active_trace_indices["arch_nodes"]] + arch_edge_trace = fig.data[active_trace_indices["arch_edges"]] + arch_edge_label_trace = fig.data[active_trace_indices["arch_edge_labels"]] + + # define interactive callbacks + def visualize_search_node_layout(trace, points, selector): + nonlocal current_node_layout_visualized + + if current_layer is None: + return + point_inds = [] + try: + point_inds = points.point_inds + except: + point_inds = points + if len(point_inds) == 0: + return + if current_node_layout_visualized == point_inds[0]: + return + current_node_layout_visualized = point_inds[0] + node_index = list(search_graph.nodes())[current_node_layout_visualized] + + _visualize_layout( + fig, + search_graph.nodes[node_index]["data"], + arch_node_trace, + architecture_node_positions, + initial_layout, + considered_qubit_colors, + swap_arrow_offset, + arch_x_arrow_spacing, + arch_y_arrow_spacing, + show_shared_swaps, + current_node_layout_visualized, + search_node_traces[0] if not use3d else None, + plotly_settings, + ) + + if not hide_layout: + for trace in search_node_traces: + if show_layout == "hover": + trace.on_hover(visualize_search_node_layout) + elif show_layout == "click": + trace.on_click(visualize_search_node_layout) + + def update_timestep(timestep): + timestep = timestep["new"] + if current_layer is None: + return + with fig.batch_update(): + data_x = search_node_scatter_data_x[current_layer][:timestep] + data_y = search_node_scatter_data_y[current_layer][:timestep] + if draw_stems: + # line trace data: begin, end, None, begin, end, None, ... + search_node_stem_trace.x = search_node_stem_scatter_data_x[current_layer][: timestep * 3] + search_node_stem_trace.y = search_node_stem_scatter_data_y[current_layer][: timestep * 3] + search_node_stem_trace.z = search_node_stem_scatter_data_z[current_layer][: timestep * 3] + for i, trace in enumerate(search_node_traces): + trace.x = data_x + trace.y = data_y + if use3d: + trace.z = search_node_scatter_data_z[current_layer][i][:timestep] + + timestep_play = Play( + value=1, + min=1, + max=2, + step=search_progression_step, + interval=int(1000 / search_progression_speed), + disabled=False, + ) + timestep_slider = IntSlider( + min=timestep_play.min, max=timestep_play.max, step=timestep_play.step, layout=Layout(width=f"{width-232}px") + ) + jslink((timestep_play, "value"), (timestep_slider, "value")) + timestep_play.observe(update_timestep, names="value") + timestep = HBox([timestep_play, timestep_slider]) + + def update_layer(new_layer: int): + nonlocal current_layer, search_graph, initial_layout, considered_qubit_colors, current_node_layout_visualized + + if current_layer == new_layer: + return + current_layer = new_layer + + if current_layer not in search_graphs: + ( + search_graphs[current_layer], + initial_layouts[current_layer], + initial_qbit_positions[current_layer], + layers_considered_qubit_colors[current_layer], + single_qbit_multiplicities[current_layer], + two_qbit_multiplicities[current_layer], + individual_two_qbit_multiplicities[current_layer], + final_node_ids[current_layer], + search_node_scatter_data_x[current_layer], + search_node_scatter_data_y[current_layer], + search_node_scatter_data_z[current_layer], + search_node_scatter_data_color[current_layer], + search_node_stem_scatter_data_x[current_layer], + search_node_stem_scatter_data_y[current_layer], + search_node_stem_scatter_data_z[current_layer], + search_edge_scatter_data_x[current_layer], + search_edge_scatter_data_y[current_layer], + search_edge_scatter_data_z[current_layer], + search_min_x[current_layer], + search_max_x[current_layer], + search_min_y[current_layer], + search_max_y[current_layer], + search_min_z[current_layer], + search_max_z[current_layer], + ) = _load_layer_data( + data_logging_path, + current_layer, + search_node_layout_method, + search_node_layout_walker_factor, + tapered_search_layer_heights, + number_of_node_traces, + use3d, + search_node_color, + prioritize_search_node_color, + search_node_height, + color_valid_mapping, + color_final_node, + draw_stems, + draw_search_edges, + ) + search_graph = search_graphs[current_layer] + initial_layout = initial_layouts[current_layer] + considered_qubit_colors = layers_considered_qubit_colors[current_layer] + with fig.batch_update(): + xaxis1 = fig.layout.scene.xaxis if use3d else (fig.layout.xaxis if hide_layout else fig.layout.xaxis1) + yaxis1 = fig.layout.scene.yaxis if use3d else (fig.layout.yaxis if hide_layout else fig.layout.yaxis1) + xaxis1.range = [ + search_min_x[current_layer] + - abs(search_max_x[current_layer] - search_min_x[current_layer]) * search_graph_border, + search_max_x[current_layer] + + abs(search_max_x[current_layer] - search_min_x[current_layer]) * search_graph_border, + ] + yaxis1.range = [ + search_min_y[current_layer] + - abs(search_max_y[current_layer] - search_min_y[current_layer]) * search_graph_border, + search_max_y[current_layer] + + abs(search_max_y[current_layer] - search_min_y[current_layer]) * search_graph_border, + ] + if use3d: + fig.layout.scene.zaxis.range = [ + search_min_z[current_layer] + - abs(search_max_z[current_layer] - search_min_z[current_layer]) * search_graph_border, + search_max_z[current_layer] + + abs(search_max_z[current_layer] - search_min_z[current_layer]) * search_graph_border, + ] + + _draw_search_graph_nodes( + search_node_traces, + search_node_scatter_data_x[current_layer], + search_node_scatter_data_y[current_layer], + search_node_scatter_data_z[current_layer], + search_node_scatter_data_color[current_layer], + use3d, + ) + if draw_stems: + _draw_search_graph_stems( + search_node_stem_trace, + search_node_stem_scatter_data_x[current_layer], + search_node_stem_scatter_data_y[current_layer], + search_node_stem_scatter_data_z[current_layer], + ) + if draw_search_edges: + _draw_search_graph_edges( + search_edge_traces, + search_edge_scatter_data_x[current_layer], + search_edge_scatter_data_y[current_layer], + search_edge_scatter_data_z[current_layer], + use3d, + ) + + if not hide_layout: + _draw_architecture_nodes( + arch_node_trace, + architecture_node_positions, + considered_qubit_colors, + initial_qbit_positions[current_layer], + single_qbit_multiplicities[current_layer], + individual_two_qbit_multiplicities[current_layer], + ) + + current_node_layout_visualized = None + visualize_search_node_layout(None, [0], None) + cl = current_layer + current_layer = None # to prevent triggering redraw in update_timestep + timestep_play.max = len(search_graph.nodes) + timestep_slider.max = len(search_graph.nodes) + timestep_play.value = len(search_graph.nodes) + timestep_slider.value = len(search_graph.nodes) + current_layer = cl + + if type(layer) is int: + update_layer(layer) + else: + update_layer(0) + + layer_slider = interactive( + update_layer, + new_layer=IntSlider( + min=0, max=number_of_layers, step=1, value=0, description="Layer:", layout=Layout(width=f"{width-80}px") + ), + ) + + vbox_elems = [fig] + if show_search_progression: + vbox_elems.append(timestep) + if layer == "interactive": + vbox_elems.append(layer_slider) + return VBox(vbox_elems) diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index c3719f6d3..1297c1c35 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -180,6 +180,7 @@ PYBIND11_MODULE(pyqmap, m) { .def_readwrite("method", &Configuration::method) .def_readwrite("verbose", &Configuration::verbose) .def_readwrite("debug", &Configuration::debug) + .def_readwrite("data_logging_path", &Configuration::dataLoggingPath) .def_readwrite("layering", &Configuration::layering) .def_readwrite("initial_layout", &Configuration::initialLayout) .def_readwrite("lookahead", &Configuration::lookahead) @@ -244,6 +245,8 @@ PYBIND11_MODULE(pyqmap, m) { &MappingResults::CircuitInfo::singleQubitGates) .def_readwrite("cnots", &MappingResults::CircuitInfo::cnots) .def_readwrite("layers", &MappingResults::CircuitInfo::layers) + .def_readwrite("total_fidelity", + &MappingResults::CircuitInfo::totalFidelity) .def_readwrite("swaps", &MappingResults::CircuitInfo::swaps) .def_readwrite("direction_reverse", &MappingResults::CircuitInfo::directionReverse) diff --git a/src/utils.cpp b/src/utils.cpp index f3708d497..16b0b9ef5 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -9,8 +9,7 @@ void Dijkstra::buildTable(const std::uint16_t n, const CouplingMap& couplingMap, Matrix& distanceTable, const Matrix& edgeWeights, - const double reversalCost, - const bool removeLastEdge) { + double reversalCost, bool removeLastEdge) { distanceTable.clear(); distanceTable.resize(n, std::vector(n, -1.)); @@ -48,7 +47,7 @@ void Dijkstra::buildTable(const std::uint16_t n, const CouplingMap& couplingMap, void Dijkstra::dijkstra(const CouplingMap& couplingMap, std::vector& nodes, std::uint16_t start, - const Matrix& edgeWeights, const double reversalCost) { + const Matrix& edgeWeights, double reversalCost) { std::priority_queue, NodeComparator> queue{}; queue.push(&nodes.at(start)); while (!queue.empty()) { @@ -95,6 +94,61 @@ void Dijkstra::dijkstra(const CouplingMap& couplingMap, } } +void Dijkstra::buildEdgeSkipTable(const Matrix& distanceTable, + const CouplingMap& couplingMap, + std::vector& edgeSkipDistanceTable) { + /* to find the cheapest distance between 2 qubits skipping any 1 edge, we + iterate over all edges, for each assume the current edge to be the one skipped + and are thereby able to retrieve the distance by just adding the distances + from the source and target qubit to each of the qubits on the edge. Taking the + minimum over all edges gives us the cheapest distance skipping any 1 edge. + + For skipping any 2 edges, we can use the same approach, but on one side of the + edge taking not the regular distance but the previously calculated distance + skipping 1 edge. The same approach can be used for skipping any 3 edges, etc. + */ + edgeSkipDistanceTable.clear(); + edgeSkipDistanceTable.emplace_back(distanceTable); + std::size_t n = distanceTable.size(); + for (std::size_t k = 1; k <= n; ++k) { + // k...number of edges to be skipped along each path + edgeSkipDistanceTable.emplace_back( + Matrix(n, std::vector(n, std::numeric_limits::max()))); + Matrix* currentTable = &edgeSkipDistanceTable.back(); + for (std::size_t q = 0; q < n; ++q) { + currentTable->at(q).at(q) = 0.; + } + bool done = false; + for (const auto& [e1, e2] : couplingMap) { // edge to be skipped + for (std::size_t l = 0; l < k; ++l) { + done = true; + // l ... number of edges to skip before edge + for (std::size_t q1 = 0; q1 < n; ++q1) { // q1 ... source qubit + for (std::size_t q2 = q1 + 1; q2 < n; ++q2) { // q2 ... target qubit + currentTable->at(q1).at(q2) = + std::min(currentTable->at(q1).at(q2), + edgeSkipDistanceTable.at(l).at(q1).at(e1) + + edgeSkipDistanceTable.at(k - l - 1).at(e2).at(q2)); + currentTable->at(q1).at(q2) = + std::min(currentTable->at(q1).at(q2), + edgeSkipDistanceTable.at(l).at(q1).at(e2) + + edgeSkipDistanceTable.at(k - l - 1).at(e1).at(q2)); + currentTable->at(q2).at(q1) = currentTable->at(q1).at(q2); + if (done && currentTable->at(q2).at(q1) > 0) { + done = false; + } + } + } + } + } + if (done) { + // all distances of the last matrix where 0 + edgeSkipDistanceTable.pop_back(); + break; + } + } +} + /// Create a string representation of a given permutation /// \param pi permutation /// \return string representation of pi diff --git a/test/test_architecture.cpp b/test/test_architecture.cpp index 5aabacb67..8e47f1b90 100644 --- a/test/test_architecture.cpp +++ b/test/test_architecture.cpp @@ -8,6 +8,30 @@ #include "gtest/gtest.h" #include +::testing::AssertionResult MatrixNear(const Matrix& a, const Matrix& b, + double delta) { + if (a.size() != b.size()) { + return ::testing::AssertionFailure() + << "Matrices differ in size: " << a.size() << " != " << b.size(); + } + for (std::size_t i = 0; i < a.size(); ++i) { + if (a.at(i).size() != b.at(i).size()) { + return ::testing::AssertionFailure() + << "Matrices differ in size in row " << i << ": " << a.at(i).size() + << " != " << b.at(i).size(); + } + for (std::size_t j = 0; j < a.at(i).size(); ++j) { + if (std::abs(a.at(i).at(j) - b.at(i).at(j)) > delta) { + return ::testing::AssertionFailure() + << "Matrix entries in [" << i << "," << j + << "] differ by more than " << delta << ": " << a.at(i).at(j) + << " !~ " << b.at(i).at(j); + } + } + } + return ::testing::AssertionSuccess(); +} + class TestArchitecture : public testing::TestWithParam { protected: std::string testArchitectureDir = "../extern/architectures/"; @@ -227,3 +251,634 @@ TEST(TestArchitecture, opTypeFromString) { EXPECT_EQ(props.getTwoQubitErrorRate(0, 1, opName), errorRate); } } + +TEST(TestArchitecture, FidelityDistanceBidirectionalTest) { + /* + 6 [0.03] + | + [0.9] + | + [0.03] 4 5 [0.02] + | | + [0.1] [0.5] + | | + 0 -[0.9]- 1 -[0.5]- 2 -[0.1]- 3 + +[0.03] [0.03] [0.02] [0.03] + + -[]- ... 2-qubit error rates + [] ... 1-qubit error rates + */ + Architecture architecture{}; + const CouplingMap cm = {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 3}, {3, 2}, + {1, 4}, {4, 1}, {2, 5}, {5, 2}, {5, 6}, {6, 5}}; + architecture.loadCouplingMap(7, cm); + + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", 0.03); + props.setSingleQubitErrorRate(1, "x", 0.03); + props.setSingleQubitErrorRate(2, "x", 0.02); + props.setSingleQubitErrorRate(3, "x", 0.03); + props.setSingleQubitErrorRate(4, "x", 0.03); + props.setSingleQubitErrorRate(5, "x", 0.02); + props.setSingleQubitErrorRate(6, "x", 0.03); + + props.setTwoQubitErrorRate(0, 1, 0.9); + props.setTwoQubitErrorRate(1, 0, 0.9); + props.setTwoQubitErrorRate(1, 2, 0.5); + props.setTwoQubitErrorRate(2, 1, 0.5); + props.setTwoQubitErrorRate(2, 3, 0.1); + props.setTwoQubitErrorRate(3, 2, 0.1); + props.setTwoQubitErrorRate(1, 4, 0.1); + props.setTwoQubitErrorRate(4, 1, 0.1); + props.setTwoQubitErrorRate(2, 5, 0.5); + props.setTwoQubitErrorRate(5, 2, 0.5); + props.setTwoQubitErrorRate(5, 6, 0.9); + props.setTwoQubitErrorRate(6, 5, 0.9); + + architecture.loadProperties(props); + + const Matrix targetTable = { + {// distance from 0 to i + 0., -3 * std::log2(1 - 0.9), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.1)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5) + + std::log2(1 - 0.9))}, + {// distance from 1 to i + -3 * std::log2(1 - 0.9), 0., -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), -3 * std::log2(1 - 0.1), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.9))}, + { + // distance from 2 to i + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5)), + -3 * std::log2(1 - 0.5), + 0., + -3 * std::log2(1 - 0.1), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.9)), + }, + { + // distance from 3 to i + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.9)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * std::log2(1 - 0.1), + 0., + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.9)), + }, + {// distance from 4 to i + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.9)), -3 * std::log2(1 - 0.1), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.1)), 0., + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.5) + + std::log2(1 - 0.9))}, + {// distance from 5 to i + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.9)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.1)), 0., + -3 * std::log2(1 - 0.9)}, + { + // distance from 6 to i + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5) + + std::log2(1 - 0.9)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5) + + std::log2(1 - 0.1)), + -3 * std::log2(1 - 0.9), + 0., + }}; + EXPECT_TRUE( + MatrixNear(architecture.getFidelityDistanceTable(), targetTable, 1e-6)); + + const Matrix targetTableSkip1Edge = { + {// distance from 0 to i + 0., 0., -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), -3 * std::log2(1 - 0.1), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.9))}, + {// distance from 1 to i + 0., 0., 0., -3 * std::log2(1 - 0.1), 0., -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5))}, + { + // distance from 2 to i + -3 * std::log2(1 - 0.5), + 0., + 0., + 0., + -3 * std::log2(1 - 0.1), + 0., + -3 * std::log2(1 - 0.5), + }, + { + // distance from 3 to i + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5)), + -3 * std::log2(1 - 0.1), + 0., + 0., + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.1)), + -3 * std::log2(1 - 0.1), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5)), + }, + {// distance from 4 to i + -3 * std::log2(1 - 0.1), 0., -3 * std::log2(1 - 0.1), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.1)), 0., + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.5))}, + {// distance from 5 to i + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), -3 * std::log2(1 - 0.5), + 0., -3 * std::log2(1 - 0.1), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), 0., 0.}, + { + // distance from 6 to i + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.9)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.1)), + 0., + 0., + }}; + EXPECT_TRUE(MatrixNear(architecture.getFidelityDistanceTable(1), + targetTableSkip1Edge, 1e-6)); + + const Matrix targetTableSkip3Edges = { + {// distance from 0 to i + 0., 0., 0., 0., 0., 0., -3 * std::log2(1 - 0.5)}, + { + // distance from 1 to i + 0., + 0., + 0., + 0., + 0., + 0., + 0., + }, + { + // distance from 2 to i + 0., + 0., + 0., + 0., + 0., + 0., + 0., + }, + { + // distance from 3 to i + 0., + 0., + 0., + 0., + 0., + 0., + 0., + }, + {// distance from 4 to i + 0., 0., 0., 0., 0., 0., -3 * std::log2(1 - 0.1)}, + { + // distance from 5 to i + 0., + 0., + 0., + 0., + 0., + 0., + 0., + }, + { + // distance from 6 to i + -3 * std::log2(1 - 0.5), + 0., + 0., + 0., + -3 * std::log2(1 - 0.1), + 0., + 0., + }}; + EXPECT_TRUE(MatrixNear(architecture.getFidelityDistanceTable(3), + targetTableSkip3Edges, 1e-6)); + + const Matrix zeroMatrix = { + {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}, + {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}, + {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}, + {0., 0., 0., 0., 0., 0., 0.}}; + EXPECT_TRUE( + MatrixNear(architecture.getFidelityDistanceTable(4), zeroMatrix, 1e-6)); + EXPECT_TRUE( + MatrixNear(architecture.getFidelityDistanceTable(5), zeroMatrix, 1e-6)); + EXPECT_TRUE( + MatrixNear(architecture.getFidelityDistanceTable(6), zeroMatrix, 1e-6)); +} + +TEST(TestArchitecture, FidelityDistanceSemiBidirectionalTest) { + /* + 6 [0.03] + | + [0.9] + | + [0.03] 4 5 [0.02] + | || + [0.1] [0.5] + | || +0 =[0.9]= 1 =[0.5]= 2 =[0.1]= 3 + +[0.03] [0.03] [0.02] [0.03] + +-[]- ... 2-qubit error rates of unidirectional edge +=[]= ... 2-qubit error rates of bidirectional edge +[] ... 1-qubit error rates +*/ + Architecture architecture{}; + const CouplingMap cm = {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 3}, + {3, 2}, {1, 4}, {2, 5}, {5, 2}, {6, 5}}; + architecture.loadCouplingMap(7, cm); + + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", 0.03); + props.setSingleQubitErrorRate(1, "x", 0.03); + props.setSingleQubitErrorRate(2, "x", 0.02); + props.setSingleQubitErrorRate(3, "x", 0.03); + props.setSingleQubitErrorRate(4, "x", 0.03); + props.setSingleQubitErrorRate(5, "x", 0.02); + props.setSingleQubitErrorRate(6, "x", 0.03); + + props.setTwoQubitErrorRate(0, 1, 0.9); + props.setTwoQubitErrorRate(1, 0, 0.9); + props.setTwoQubitErrorRate(1, 2, 0.5); + props.setTwoQubitErrorRate(2, 1, 0.5); + props.setTwoQubitErrorRate(2, 3, 0.1); + props.setTwoQubitErrorRate(3, 2, 0.1); + props.setTwoQubitErrorRate(1, 4, 0.1); + props.setTwoQubitErrorRate(2, 5, 0.5); + props.setTwoQubitErrorRate(5, 2, 0.5); + props.setTwoQubitErrorRate(6, 5, 0.9); + + architecture.loadProperties(props); + + const Matrix targetTable = { + {// distance from 0 to i + 0., -3 * std::log2(1 - 0.9), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5) + + std::log2(1 - 0.9)) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03))}, + {// distance from 1 to i + -3 * std::log2(1 - 0.9), 0., -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * std::log2(1 - 0.1) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.9)) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03))}, + {// distance from 2 to i + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5)), -3 * std::log2(1 - 0.5), + 0., -3 * std::log2(1 - 0.1), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.9)) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03))}, + {// distance from 3 to i + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.9)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), -3 * std::log2(1 - 0.1), + 0., + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.9)) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03))}, + {// distance from 4 to i + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.9)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * std::log2(1 - 0.1) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + 0., + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.5)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.5) + + std::log2(1 - 0.9)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03) + + std::log2(1 - 0.02) + std::log2(1 - 0.03))}, + { + // distance from 5 to i + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.9)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + 0., + -3 * std::log2(1 - 0.9) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03)), + }, + {// distance from 6 to i + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5) + + std::log2(1 - 0.9)) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5)) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5)) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5) + + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03) + + std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * std::log2(1 - 0.9) - + 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03)), + 0.}}; + EXPECT_TRUE( + MatrixNear(architecture.getFidelityDistanceTable(), targetTable, 1e-6)); + + const Matrix targetTableSkip1Edge = { + {// distance from 0 to i + 0., 0., -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * std::log2(1 - 0.1) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * (std::log2(1 - 0.9) + std::log2(1 - 0.5) + std::log2(1 - 0.5))}, + {// distance from 1 to i + 0., 0., 0., -3 * std::log2(1 - 0.1), 0., -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5))}, + {// distance from 2 to i + -3 * std::log2(1 - 0.5), 0., 0., 0., + -3 * std::log2(1 - 0.1) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + 0., -3 * std::log2(1 - 0.5)}, + {// distance from 3 to i + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5)), -3 * std::log2(1 - 0.1), + 0., 0., + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * std::log2(1 - 0.1), -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5))}, + {// distance from 4 to i + -3 * std::log2(1 - 0.1) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + 0., + -3 * std::log2(1 - 0.1) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + 0., + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + -3 * (std::log2(1 - 0.1) + std::log2(1 - 0.5) + std::log2(1 - 0.5)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03))}, + { + // distance from 5 to i + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), + -3 * std::log2(1 - 0.5), + 0., + -3 * std::log2(1 - 0.1), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + 0., + 0., + }, + {// distance from 6 to i + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.9)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5)), -3 * std::log2(1 - 0.5), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.1)), + -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.1)) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + 0., 0.}}; + EXPECT_TRUE(MatrixNear(architecture.getFidelityDistanceTable(1), + targetTableSkip1Edge, 1e-6)); + + const Matrix targetTableSkip3Edges = { + {// distance from 0 to i + 0., 0., 0., 0., 0., 0., -3 * std::log2(1 - 0.5)}, + { + // distance from 1 to i + 0., + 0., + 0., + 0., + 0., + 0., + 0., + }, + { + // distance from 2 to i + 0., + 0., + 0., + 0., + 0., + 0., + 0., + }, + { + // distance from 3 to i + 0., + 0., + 0., + 0., + 0., + 0., + 0., + }, + {// distance from 4 to i + 0., 0., 0., 0., 0., 0., + -3 * std::log2(1 - 0.1) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03))}, + { + // distance from 5 to i + 0., + 0., + 0., + 0., + 0., + 0., + 0., + }, + { + // distance from 6 to i + -3 * std::log2(1 - 0.5), + 0., + 0., + 0., + -3 * std::log2(1 - 0.1) - + 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), + 0., + 0., + }}; + EXPECT_TRUE(MatrixNear(architecture.getFidelityDistanceTable(3), + targetTableSkip3Edges, 1e-6)); + + const Matrix zeroMatrix = { + {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}, + {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}, + {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}, + {0., 0., 0., 0., 0., 0., 0.}}; + EXPECT_TRUE( + MatrixNear(architecture.getFidelityDistanceTable(4), zeroMatrix, 1e-6)); + EXPECT_TRUE( + MatrixNear(architecture.getFidelityDistanceTable(5), zeroMatrix, 1e-6)); + EXPECT_TRUE( + MatrixNear(architecture.getFidelityDistanceTable(6), zeroMatrix, 1e-6)); +} + +TEST(TestArchitecture, FidelitySwapCostTest) { + const double tolerance = 1e-6; + Architecture architecture{}; + const CouplingMap cm = {{0, 1}, {1, 2}, {2, 1}, {2, 3}, {2, 4}, {4, 2}}; + architecture.loadCouplingMap(5, cm); + + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", 0.11); + props.setSingleQubitErrorRate(1, "x", 0.12); + props.setSingleQubitErrorRate(2, "x", 0.13); + props.setSingleQubitErrorRate(3, "x", 0.14); + props.setSingleQubitErrorRate(4, "x", 0.15); + + props.setTwoQubitErrorRate(0, 1, 0.1); + props.setTwoQubitErrorRate(1, 2, 0.2); + props.setTwoQubitErrorRate(2, 1, 0.2); + props.setTwoQubitErrorRate(2, 3, 0.3); + props.setTwoQubitErrorRate(2, 4, 0.4); + props.setTwoQubitErrorRate(4, 2, 0.4); + + architecture.loadProperties(props); + + const Matrix swapFidCost = architecture.getSwapFidelityCosts(); + + EXPECT_EQ(swapFidCost.size(), 5); + EXPECT_EQ(swapFidCost[0].size(), 5); + EXPECT_NEAR(swapFidCost[0][1], + -3 * std::log2(1 - 0.1) - 2 * std::log2(1 - 0.11) - + 2 * std::log2(1 - 0.12), + tolerance); + EXPECT_GT(swapFidCost[0][2], 1e20); + EXPECT_GT(swapFidCost[0][3], 1e20); + EXPECT_GT(swapFidCost[0][4], 1e20); + EXPECT_EQ(swapFidCost[1].size(), 5); + EXPECT_NEAR(swapFidCost[1][0], + -3 * std::log2(1 - 0.1) - 2 * std::log2(1 - 0.11) - + 2 * std::log2(1 - 0.12), + tolerance); + EXPECT_NEAR(swapFidCost[1][2], -3 * std::log2(1 - 0.2), tolerance); + EXPECT_GT(swapFidCost[1][3], 1e20); + EXPECT_GT(swapFidCost[1][4], 1e20); + EXPECT_EQ(swapFidCost[2].size(), 5); + EXPECT_GT(swapFidCost[2][0], 1e20); + EXPECT_NEAR(swapFidCost[2][1], -3 * std::log2(1 - 0.2), tolerance); + EXPECT_NEAR(swapFidCost[2][3], + -3 * std::log2(1 - 0.3) - 2 * std::log2(1 - 0.13) - + 2 * std::log2(1 - 0.14), + tolerance); + EXPECT_NEAR(swapFidCost[2][4], -3 * std::log2(1 - 0.4), tolerance); + EXPECT_EQ(swapFidCost[3].size(), 5); + EXPECT_GT(swapFidCost[3][0], 1e20); + EXPECT_GT(swapFidCost[3][1], 1e20); + EXPECT_NEAR(swapFidCost[3][2], + -3 * std::log2(1 - 0.3) - 2 * std::log2(1 - 0.13) - + 2 * std::log2(1 - 0.14), + tolerance); + EXPECT_GT(swapFidCost[3][4], 1e20); + EXPECT_EQ(swapFidCost[4].size(), 5); + EXPECT_GT(swapFidCost[4][0], 1e20); + EXPECT_GT(swapFidCost[4][1], 1e20); + EXPECT_NEAR(swapFidCost[4][2], -3 * std::log2(1 - 0.4), tolerance); + EXPECT_GT(swapFidCost[4][3], 1e20); +} + +TEST(TestArchitecture, FidelityDistanceCheapestPathTest) { + // tests if the distance measure actually finds the cheapest path and + // not just the shortest + Architecture architecture{}; + const CouplingMap cm = {{0, 1}, {1, 0}, {2, 1}, {2, 6}, {6, 2}, + {0, 5}, {5, 0}, {5, 6}, {6, 5}, {0, 3}, + {3, 0}, {3, 4}, {4, 3}, {4, 6}, {6, 4}}; + architecture.loadCouplingMap(7, cm); + + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", 0.1); + props.setSingleQubitErrorRate(1, "x", 0.1); + props.setSingleQubitErrorRate(2, "x", 0.1); + props.setSingleQubitErrorRate(3, "x", 0.1); + props.setSingleQubitErrorRate(4, "x", 0.1); + props.setSingleQubitErrorRate(5, "x", 0.1); + props.setSingleQubitErrorRate(6, "x", 0.1); + + props.setTwoQubitErrorRate(0, 1, 0.1); + props.setTwoQubitErrorRate(1, 0, 0.1); + props.setTwoQubitErrorRate(2, 1, 0.1); + props.setTwoQubitErrorRate(2, 6, 0.1); + props.setTwoQubitErrorRate(6, 2, 0.1); + props.setTwoQubitErrorRate(0, 5, 0.7); + props.setTwoQubitErrorRate(5, 0, 0.7); + props.setTwoQubitErrorRate(5, 6, 0.7); + props.setTwoQubitErrorRate(6, 5, 0.7); + props.setTwoQubitErrorRate(0, 3, 0.5); + props.setTwoQubitErrorRate(3, 0, 0.5); + props.setTwoQubitErrorRate(3, 4, 0.5); + props.setTwoQubitErrorRate(4, 3, 0.5); + props.setTwoQubitErrorRate(4, 6, 0.5); + props.setTwoQubitErrorRate(6, 4, 0.5); + + architecture.loadProperties(props); + + const Matrix fidDistance = architecture.getFidelityDistanceTable(); + + EXPECT_EQ(fidDistance.size(), 7); + EXPECT_EQ(fidDistance[0].size(), 7); + EXPECT_NEAR(fidDistance[0][6], + -3 * 3 * std::log2(1 - 0.1) - 2 * 2 * std::log2(1 - 0.1), 1e-6); +} + +TEST(TestArchitecture, DistanceCheapestPathTest) { + // tests if the distance measure actually finds the cheapest path and + // not just the shortest + Architecture architecture{}; + + // minimum number of unidirectional edges on a path where the same path with + // bidirectional edges can afford at least 1 more edge and still be cheaper + std::uint8_t nrEdges = + 1 + static_cast( + std::ceil(static_cast(COST_BIDIRECTIONAL_SWAP) / + (static_cast(COST_UNIDIRECTIONAL_SWAP) - + static_cast(COST_BIDIRECTIONAL_SWAP)))); + + CouplingMap cm = {}; + for (std::uint8_t i = 0; i < nrEdges; ++i) { + cm.insert(Edge{i + 1, i}); + } + for (std::uint8_t i = nrEdges + 1; i < 2 * nrEdges; ++i) { + cm.insert(Edge{i, i + 1}); + cm.insert(Edge{i + 1, i}); + } + cm.insert(Edge{0, nrEdges + 1}); + cm.insert(Edge{nrEdges + 1, 0}); + cm.insert(Edge{2 * nrEdges, nrEdges}); + cm.insert(Edge{nrEdges, 2 * nrEdges}); + architecture.loadCouplingMap(static_cast(2 * nrEdges + 1), cm); + + const Matrix distances = architecture.getDistanceTable(); + + EXPECT_EQ(distances.size(), 2 * nrEdges + 1); + EXPECT_EQ(distances[0].size(), 2 * nrEdges + 1); + EXPECT_NEAR(distances[0][nrEdges], nrEdges * COST_BIDIRECTIONAL_SWAP, 1e-6); +} diff --git a/test/test_general.cpp b/test/test_general.cpp index 1580df7e5..fb82c65fd 100644 --- a/test/test_general.cpp +++ b/test/test_general.cpp @@ -113,3 +113,86 @@ TEST(General, DijkstraCNOTReversal) { Dijkstra::buildTable(5, cm, distanceTable, edgeWeights, 1, true); EXPECT_EQ(distanceTable, targetTable2); } + +TEST(General, DijkstraSkipEdges) { + /* + 0 -[2]- 1 -[5]- 2 + | | + [4] [1] + | | + 6 -[2]- 5 -[2]- 4 -[2]- 3 + | | + [1] [9] + | | + 7 -[1]- 8 -[1]- 9 + */ + + const CouplingMap cm = { + {0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 3}, {3, 2}, {3, 4}, {4, 3}, + {4, 5}, {5, 4}, {5, 0}, {0, 5}, {5, 6}, {6, 5}, {6, 7}, {7, 6}, + {7, 8}, {8, 7}, {8, 9}, {9, 8}, {9, 4}, {4, 9}, + }; + + const Matrix edgeWeights = { + {0, 2, 0, 0, 0, 4, 0, 0, 0, 0}, {2, 0, 5, 0, 0, 0, 0, 0, 0, 0}, + {0, 5, 0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 0, 2, 0, 0, 0, 0, 0}, + {0, 0, 0, 2, 0, 2, 0, 0, 0, 9}, {4, 0, 0, 0, 2, 0, 2, 0, 0, 0}, + {0, 0, 0, 0, 0, 2, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 0, 1, 0}, + {0, 0, 0, 0, 0, 0, 0, 1, 0, 1}, {0, 0, 0, 0, 9, 0, 0, 0, 1, 0}}; + + const Matrix targetTable = { + {0, 2, 7, 8, 6, 4, 6, 7, 8, 9}, {2, 0, 5, 6, 8, 6, 8, 9, 10, 11}, + {7, 5, 0, 1, 3, 5, 7, 8, 9, 10}, {8, 6, 1, 0, 2, 4, 6, 7, 8, 9}, + {6, 8, 3, 2, 0, 2, 4, 5, 6, 7}, {4, 6, 5, 4, 2, 0, 2, 3, 4, 5}, + {6, 8, 7, 6, 4, 2, 0, 1, 2, 3}, {7, 9, 8, 7, 5, 3, 1, 0, 1, 2}, + {8, 10, 9, 8, 6, 4, 2, 1, 0, 1}, {9, 11, 10, 9, 7, 5, 3, 2, 1, 0}}; + Matrix distanceTable{}; + Dijkstra::buildTable(10, cm, distanceTable, edgeWeights, 0, false); + EXPECT_EQ(distanceTable, targetTable); + + const std::vector edgeSkipTargetTable = { + targetTable, + {{0, 0, 2, 3, 2, 0, 2, 3, 4, 5}, + {0, 0, 0, 1, 3, 2, 4, 5, 6, 7}, + {2, 0, 0, 0, 1, 3, 5, 5, 4, 3}, + {3, 1, 0, 0, 0, 2, 4, 4, 3, 2}, + {2, 3, 1, 0, 0, 0, 2, 2, 1, 0}, + {0, 2, 3, 2, 0, 0, 0, 1, 2, 2}, + {2, 4, 5, 4, 2, 0, 0, 0, 1, 2}, + {3, 5, 5, 4, 2, 1, 0, 0, 0, 1}, + {4, 6, 4, 3, 1, 2, 1, 0, 0, 0}, + {5, 7, 3, 2, 0, 2, 2, 1, 0, 0}}, + {{0, 0, 0, 1, 0, 0, 0, 1, 2, 2}, + {0, 0, 0, 0, 1, 0, 2, 3, 4, 3}, + {0, 0, 0, 0, 0, 1, 3, 3, 2, 1}, + {1, 0, 0, 0, 0, 0, 2, 2, 1, 0}, + {0, 1, 0, 0, 0, 0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0, 0, 0, 0, 1, 0}, + {0, 2, 3, 2, 0, 0, 0, 0, 0, 1}, + {1, 3, 3, 2, 1, 0, 0, 0, 0, 0}, + {2, 4, 2, 1, 0, 1, 0, 0, 0, 0}, + {2, 3, 1, 0, 0, 0, 1, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 0, 0, 0, 1, 0}, + {0, 0, 0, 0, 0, 0, 0, 1, 2, 1}, + {0, 0, 0, 0, 0, 0, 1, 2, 1, 0}, + {0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 2, 1, 0, 0, 0, 0, 0, 0}, + {1, 2, 1, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 0, 0, 0, 0, 0, 0, 0, 0}}, + {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 1, 0}, + {0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}; + std::vector edgeSkipDistanceTable = {}; + Dijkstra::buildEdgeSkipTable(distanceTable, cm, edgeSkipDistanceTable); + EXPECT_EQ(edgeSkipDistanceTable, edgeSkipTargetTable); +} diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 7f747e7a3..ffc853666 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -4,58 +4,43 @@ // #include "heuristic/HeuristicMapper.hpp" +#include "nlohmann/json.hpp" #include "gtest/gtest.h" +#include +#include #include - -class HeuristicTest5Q : public testing::TestWithParam { -protected: - std::string testExampleDir = "../examples/"; - std::string testArchitectureDir = "../extern/architectures/"; - std::string testCalibrationDir = "../extern/calibration/"; - - qc::QuantumComputation qc{}; - Architecture ibmqYorktown{}; - Architecture ibmqLondon{}; - std::unique_ptr ibmqYorktownMapper; - std::unique_ptr ibmqLondonMapper; - Configuration settings{}; - - void SetUp() override { - qc.import(testExampleDir + GetParam() + ".qasm"); - ibmqYorktown.loadCouplingMap(AvailableArchitecture::IbmqYorktown); - ibmqLondon.loadCouplingMap(testArchitectureDir + "ibmq_london.arch"); - ibmqLondon.loadProperties(testCalibrationDir + "ibmq_london.csv"); - ibmqYorktownMapper = std::make_unique(qc, ibmqYorktown); - ibmqLondonMapper = std::make_unique(qc, ibmqLondon); - settings.debug = true; - } -}; +#include TEST(Functionality, NodeCostCalculation) { - const double tolerance = 1e-6; - const CouplingMap cm = {{0, 1}, {1, 2}, {3, 1}, {4, 3}}; - Architecture arch{5, cm}; - const TwoQubitMultiplicity multiplicity = {{{0, 1}, {5, 2}}, - {{2, 3}, {0, 1}}}; - const std::array qubits = {4, 3, 1, 2, 0}; + const double tolerance = 1e-6; + const CouplingMap cm = {{0, 1}, {1, 2}, {3, 1}, {4, 3}}; + Architecture arch{5, cm}; + const SingleQubitMultiplicity empty1Mult = {}; + const std::unordered_set& consideredQubits = {0, 1, 2, 3, 5}; + const TwoQubitMultiplicity multiplicity = {{{0, 1}, {5, 2}}, + {{2, 3}, {0, 1}}}; + const std::array qubits = {4, 3, 1, 2, 0}; const std::array locations = {4, 2, 3, 1, 0}; const std::vector> swaps = { {Exchange(0, 1, qc::OpType::Teleportation)}, {Exchange(1, 2, qc::OpType::SWAP)}}; - HeuristicMapper::Node node(qubits, locations, swaps, 5.); + HeuristicMapper::Node node(0, 0, qubits, locations, swaps, 5.); EXPECT_NEAR(node.costFixed, 5., tolerance); - node.updateHeuristicCost(arch, multiplicity, true); + node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits, + true, false); EXPECT_NEAR(node.costHeur, COST_UNIDIRECTIONAL_SWAP * 2 + COST_DIRECTION_REVERSE, tolerance); - node.updateHeuristicCost(arch, multiplicity, false); + node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits, + false, false); EXPECT_NEAR(node.costHeur, COST_UNIDIRECTIONAL_SWAP * 14 + COST_DIRECTION_REVERSE * 3, tolerance); - node.applySWAP({3, 4}, arch); - node.updateHeuristicCost(arch, multiplicity, true); + node.applySWAP({3, 4}, arch, empty1Mult, multiplicity, false); + node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits, + true, false); EXPECT_NEAR(node.costFixed, 5. + COST_UNIDIRECTIONAL_SWAP, tolerance); EXPECT_NEAR(node.costHeur, COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE, tolerance); @@ -71,7 +56,7 @@ TEST(Functionality, NodeCostCalculation) { tolerance); EXPECT_NEAR(node.getTotalFixedCost(), 7. + COST_UNIDIRECTIONAL_SWAP, tolerance); - node.recalculateFixedCost(arch); + node.recalculateFixedCost(arch, empty1Mult, multiplicity, false); EXPECT_NEAR(node.costFixed, COST_TELEPORTATION + COST_UNIDIRECTIONAL_SWAP * 2, tolerance); EXPECT_NEAR(node.costHeur, COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE, @@ -218,7 +203,10 @@ TEST(Functionality, HeuristicAdmissibility) { architecture.loadCouplingMap(6, cm); const std::vector perms{{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}}; - const TwoQubitMultiplicity multiplicity = { + const SingleQubitMultiplicity empty1Mult = {}; + const std::unordered_set& consideredQubits = {0, 1, 2, + 3, 4, 5}; + const TwoQubitMultiplicity multiplicity = { {{0, 4}, {1, 0}}, {{1, 3}, {1, 0}}, {{2, 5}, {1, 0}}}; // perform depth-limited depth first search @@ -227,9 +215,11 @@ TEST(Functionality, HeuristicAdmissibility) { nodeStack.reserve(depthLimit); std::stack permStack{}; - auto initNode = HeuristicMapper::Node({0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}); - initNode.recalculateFixedCost(architecture); - initNode.updateHeuristicCost(architecture, multiplicity, true); + auto initNode = + HeuristicMapper::Node(0, 0, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}); + initNode.recalculateFixedCost(architecture, empty1Mult, multiplicity, false); + initNode.updateHeuristicCost(architecture, empty1Mult, multiplicity, + consideredQubits, true, false); nodeStack.emplace_back(initNode); permStack.emplace(perms.size()); @@ -247,16 +237,392 @@ TEST(Functionality, HeuristicAdmissibility) { continue; } --permStack.top(); - const auto perm = perms[permStack.top()]; - auto newNode = HeuristicMapper::Node(node.qubits, node.locations, - node.swaps, node.costFixed); - newNode.applySWAP(perm, architecture); - newNode.updateHeuristicCost(architecture, multiplicity, true); + const auto perm = perms[permStack.top()]; + auto newNode = HeuristicMapper::Node(1, 0, node.qubits, node.locations, + node.swaps, node.costFixed); + newNode.applySWAP(perm, architecture, empty1Mult, multiplicity, false); + newNode.updateHeuristicCost(architecture, empty1Mult, multiplicity, + consideredQubits, true, false); nodeStack.emplace_back(newNode); permStack.emplace(perms.size()); } } +TEST(Functionality, DataLogger) { + Architecture architecture{}; + const CouplingMap cm = {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {1, 3}, {3, 1}}; + architecture.loadCouplingMap(4, cm); + + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", 0.1); + props.setSingleQubitErrorRate(1, "x", 0.2); + props.setSingleQubitErrorRate(2, "x", 0.3); + props.setSingleQubitErrorRate(3, "x", 0.4); + + props.setTwoQubitErrorRate(0, 1, 0.5); + props.setTwoQubitErrorRate(1, 0, 0.5); + props.setTwoQubitErrorRate(1, 2, 0.6); + props.setTwoQubitErrorRate(2, 1, 0.6); + props.setTwoQubitErrorRate(1, 3, 0.7); + props.setTwoQubitErrorRate(3, 1, 0.7); + + architecture.loadProperties(props); + architecture.setName("test_architecture"); + + qc::QuantumComputation qc{4, 4}; + qc.x(0, qc::Control{1}); + qc.x(2, qc::Control{3}); + qc.setName("test_circ"); + + Configuration settings{}; + settings.admissibleHeuristic = true; + settings.layering = Layering::IndividualGates; + settings.initialLayout = InitialLayout::Identity; + settings.preMappingOptimizations = false; + settings.postMappingOptimizations = false; + settings.lookahead = true; + settings.nrLookaheads = 1; + settings.firstLookaheadFactor = 0.5; + settings.lookaheadFactor = 0.9; + settings.debug = true; + settings.considerFidelity = false; + settings.useTeleportation = false; + settings.dataLoggingPath = "test_log/"; + + auto mapper = std::make_unique(qc, architecture); + mapper->map(settings); + mapper->printResult(std::cout); + MappingResults& results = mapper->getResults(); + + auto archFile = std::ifstream(settings.dataLoggingPath + "architecture.json"); + if (!archFile.is_open()) { + FAIL() << "Could not open file " << settings.dataLoggingPath + << "architecture.json"; + } else { + const auto archJson = nlohmann::json::parse(archFile); + EXPECT_EQ(archJson["name"], architecture.getName()); + EXPECT_EQ(archJson["nqubits"], architecture.getNqubits()); + EXPECT_EQ(archJson["distances"], architecture.getDistanceTable()); + EXPECT_EQ(archJson["coupling_map"], architecture.getCouplingMap()); + const auto fidelityJson = archJson["fidelity"]; + EXPECT_EQ(fidelityJson["fidelity_distances"], + architecture.getFidelityDistanceTables()); + EXPECT_EQ(fidelityJson["single_qubit_fidelities"], + architecture.getSingleQubitFidelities()); + EXPECT_EQ(fidelityJson["two_qubit_fidelities"], + architecture.getFidelityTable()); + auto& singleQubitFidelityCosts = architecture.getSingleQubitFidelityCosts(); + for (std::size_t i = 0; i < singleQubitFidelityCosts.size(); ++i) { + if (std::isinf(singleQubitFidelityCosts[i])) { + EXPECT_TRUE(fidelityJson["single_qubit_fidelity_costs"][i].is_null()); + } else { + EXPECT_EQ(fidelityJson["single_qubit_fidelity_costs"][i], + singleQubitFidelityCosts[i]); + } + } + auto& twoQubitFidelityCosts = architecture.getTwoQubitFidelityCosts(); + for (std::size_t i = 0; i < twoQubitFidelityCosts.size(); ++i) { + for (std::size_t j = 0; j < twoQubitFidelityCosts[i].size(); ++j) { + if (std::isinf(twoQubitFidelityCosts[i][j])) { + EXPECT_TRUE(fidelityJson["two_qubit_fidelity_costs"][i][j].is_null()); + } else { + EXPECT_EQ(fidelityJson["two_qubit_fidelity_costs"][i][j], + twoQubitFidelityCosts[i][j]); + } + } + } + auto& swapFidelityCosts = architecture.getSwapFidelityCosts(); + for (std::size_t i = 0; i < swapFidelityCosts.size(); ++i) { + for (std::size_t j = 0; j < swapFidelityCosts[i].size(); ++j) { + if (std::isinf(swapFidelityCosts[i][j])) { + EXPECT_TRUE(fidelityJson["swap_fidelity_costs"][i][j].is_null()); + } else { + EXPECT_EQ(fidelityJson["swap_fidelity_costs"][i][j], + swapFidelityCosts[i][j]); + } + } + } + } + + auto resultFile = + std::ifstream(settings.dataLoggingPath + "mapping_result.json"); + if (!resultFile.is_open()) { + FAIL() << "Could not open file " << settings.dataLoggingPath + << "mapping_result.json"; + } else { + const auto resultJson = nlohmann::json::parse(resultFile); + const auto configJson = resultJson["config"]; + EXPECT_EQ(configJson["add_measurements_to_mapped_circuit"], + settings.addMeasurementsToMappedCircuit); + EXPECT_EQ(configJson["admissible_heuristic"], settings.admissibleHeuristic); + EXPECT_EQ(configJson["consider_fidelity"], settings.considerFidelity); + EXPECT_EQ(configJson["initial_layout"], toString(settings.initialLayout)); + EXPECT_EQ(configJson["layering"], toString(settings.layering)); + EXPECT_EQ(configJson["method"], toString(settings.method)); + EXPECT_EQ(configJson["post_mapping_optimizations"], + settings.postMappingOptimizations); + EXPECT_EQ(configJson["pre_mapping_optimizations"], + settings.preMappingOptimizations); + EXPECT_EQ(configJson["teleportation"], settings.useTeleportation); + EXPECT_EQ(configJson["timeout"], settings.timeout); + const auto lookaheadJson = configJson["lookahead"]; + EXPECT_EQ(lookaheadJson["factor"], settings.lookaheadFactor); + EXPECT_EQ(lookaheadJson["first_factor"], settings.firstLookaheadFactor); + EXPECT_EQ(lookaheadJson["nr_lookaheads"], settings.nrLookaheads); + + const auto inCircJson = resultJson["input_circuit"]; + EXPECT_EQ(inCircJson["cnots"], results.input.cnots); + EXPECT_EQ(inCircJson["gates"], results.input.gates); + EXPECT_EQ(inCircJson["name"], results.input.name); + EXPECT_EQ(inCircJson["qubits"], results.input.qubits); + EXPECT_EQ(inCircJson["single_qubit_gates"], results.input.singleQubitGates); + + const auto outCircJson = resultJson["output_circuit"]; + EXPECT_EQ(outCircJson["cnots"], results.output.cnots); + EXPECT_EQ(outCircJson["gates"], results.output.gates); + EXPECT_EQ(outCircJson["name"], results.output.name); + EXPECT_EQ(outCircJson["qubits"], results.output.qubits); + EXPECT_EQ(outCircJson["single_qubit_gates"], + results.output.singleQubitGates); + + const auto statJson = resultJson["statistics"]; + EXPECT_EQ(statJson["additional_gates"], + (static_cast>( + results.output.gates) - + static_cast>( + results.input.gates))); + EXPECT_EQ(statJson["layers"], results.input.layers); + EXPECT_EQ(statJson["mapping_time"], results.time); + EXPECT_EQ(statJson["swaps"], results.output.swaps); + EXPECT_EQ(statJson["teleportations"], results.output.teleportations); + EXPECT_EQ(statJson["timeout"], results.timeout); + EXPECT_EQ(statJson["total_fidelity"], results.output.totalFidelity); + EXPECT_EQ(statJson["total_log_fidelity"], results.output.totalLogFidelity); + + const auto benchmarkJson = statJson["benchmark"]; + EXPECT_EQ(benchmarkJson["average_branching_factor"], + results.heuristicBenchmark.averageBranchingFactor); + EXPECT_EQ(benchmarkJson["effective_branching_factor"], + results.heuristicBenchmark.effectiveBranchingFactor); + EXPECT_EQ(benchmarkJson["expanded_nodes"], + results.heuristicBenchmark.expandedNodes); + EXPECT_EQ(benchmarkJson["generated_nodes"], + results.heuristicBenchmark.generatedNodes); + EXPECT_EQ(benchmarkJson["time_per_node"], + results.heuristicBenchmark.timePerNode); + const auto benchmarkLayersJson = benchmarkJson["layers"]; + EXPECT_EQ(benchmarkLayersJson.size(), + results.layerHeuristicBenchmark.size()); + for (std::size_t i = 0; i < results.layerHeuristicBenchmark.size(); ++i) { + EXPECT_EQ(benchmarkLayersJson[i]["average_branching_factor"], + results.layerHeuristicBenchmark.at(i).averageBranchingFactor); + EXPECT_EQ(benchmarkLayersJson[i]["effective_branching_factor"], + results.layerHeuristicBenchmark.at(i).effectiveBranchingFactor); + EXPECT_EQ(benchmarkLayersJson[i]["expanded_nodes"], + results.layerHeuristicBenchmark.at(i).expandedNodes); + EXPECT_EQ(benchmarkLayersJson[i]["generated_nodes"], + results.layerHeuristicBenchmark.at(i).generatedNodes); + EXPECT_EQ(benchmarkLayersJson[i]["solution_depth"], + results.layerHeuristicBenchmark.at(i).solutionDepth); + EXPECT_EQ(benchmarkLayersJson[i]["time_per_node"], + results.layerHeuristicBenchmark.at(i).timePerNode); + } + } + + auto inputQasmFile = std::ifstream(settings.dataLoggingPath + "input.qasm"); + if (!inputQasmFile.is_open()) { + FAIL() << "Could not open file " << settings.dataLoggingPath + << "input.qasm"; + } else { + std::stringstream fileBuffer; + fileBuffer << inputQasmFile.rdbuf(); + std::stringstream qasmBuffer; + qc.dumpOpenQASM(qasmBuffer); + EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); + } + + auto outputQasmFile = std::ifstream(settings.dataLoggingPath + "output.qasm"); + if (!outputQasmFile.is_open()) { + FAIL() << "Could not open file " << settings.dataLoggingPath + << "output.qasm"; + } else { + std::stringstream fileBuffer; + fileBuffer << outputQasmFile.rdbuf(); + std::stringstream qasmBuffer; + mapper->dumpResult(qasmBuffer, qc::Format::OpenQASM); + EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); + } + + for (std::size_t i = 0; i < results.input.layers; ++i) { + auto layerFile = std::ifstream(settings.dataLoggingPath + "layer_" + + std::to_string(i) + ".json"); + if (!layerFile.is_open()) { + FAIL() << "Could not open file " << settings.dataLoggingPath << "layer_" + << i << ".json"; + } else { + const auto layerJson = nlohmann::json::parse(layerFile); + std::size_t finalNodeId = layerJson["final_node_id"]; + EXPECT_EQ(layerJson["initial_layout"].size(), architecture.getNqubits()); + EXPECT_EQ(layerJson["single_qubit_multiplicity"].size(), + architecture.getNqubits()); + + auto layerNodeFile = + std::ifstream(settings.dataLoggingPath + "nodes_layer_" + + std::to_string(i) + ".csv"); + if (!layerNodeFile.is_open()) { + FAIL() << "Could not open file " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; + } else { + std::string line; + bool foundFinalNode = false; + std::set nodeIds; + while (std::getline(layerNodeFile, line)) { + if (line.empty()) { + continue; + } + std::string col; + std::size_t nodeId, parentId, depth, isValidMapping; + double costFixed, costHeur, lookaheadPenalty; + std::vector layout{}; + std::vector> swaps{}; + std::stringstream lineStream(line); + if (std::getline(lineStream, col, ';')) { + nodeId = std::stoull(col); + nodeIds.insert(nodeId); + } else { + FAIL() << "Missing value for node id in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + parentId = std::stoull(col); + if (nodeId != 0) { + EXPECT_TRUE(nodeIds.count(parentId) > 0); + } + } else { + FAIL() << "Missing value for parent node id in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + costFixed = std::stod(col); + } else { + FAIL() << "Missing value for fixed cost in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + costHeur = std::stod(col); + } else { + FAIL() << "Missing value for heuristic cost in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + lookaheadPenalty = std::stod(col); + } else { + FAIL() << "Missing value for lookahead penalty in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + isValidMapping = std::stoull(col); + if (isValidMapping > 1) { + FAIL() << "Non-boolean value " << isValidMapping + << " for isValidMapping in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; + } + } else { + FAIL() << "Missing value for isValidMapping in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + depth = std::stoull(col); + } else { + FAIL() << "Missing value for depth in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + std::stringstream qubitMapBuffer(col); + std::string entry; + while (std::getline(qubitMapBuffer, entry, ',')) { + std::int32_t qubit = std::stoi(entry); + layout.push_back(qubit); + EXPECT_TRUE(-1 <= qubit && qubit < architecture.getNqubits()); + } + EXPECT_EQ(layout.size(), architecture.getNqubits()); + } else { + FAIL() << "Missing value for layout in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + std::stringstream swapBuffer(col); + std::string entry; + while (std::getline(swapBuffer, entry, ',')) { + std::int32_t q1, q2; + std::stringstream(entry) >> q1 >> q2; + EXPECT_TRUE(0 <= q1 && q1 < architecture.getNqubits()); + EXPECT_TRUE(0 <= q2 && q2 < architecture.getNqubits()); + swaps.push_back(std::make_pair(q1, q2)); + } + } + + if (nodeId == finalNodeId) { + foundFinalNode = true; + EXPECT_EQ(layerJson["final_cost_fixed"], costFixed); + EXPECT_EQ(layerJson["final_cost_heur"], costHeur); + EXPECT_EQ(layerJson["final_layout"], layout); + EXPECT_EQ(layerJson["final_lookahead_penalty"], lookaheadPenalty); + EXPECT_EQ(layerJson["final_search_depth"], depth); + EXPECT_EQ(layerJson["final_swaps"], swaps); + EXPECT_EQ(isValidMapping, 1); + } + } + if (!foundFinalNode) { + FAIL() << "Could not find final node in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; + } + } + } + } + + auto afterLastLayerFile = + std::ifstream(settings.dataLoggingPath + "layer_" + + std::to_string(results.input.layers) + ".json"); + if (afterLastLayerFile.is_open()) { + FAIL() << "File " << settings.dataLoggingPath << "layer_" + << results.input.layers + << ".json should not exist, as there are not that many layers"; + } + auto afterLastLayerNodesFile = + std::ifstream(settings.dataLoggingPath + "nodes_layer_" + + std::to_string(results.input.layers) + ".csv"); + if (afterLastLayerNodesFile.is_open()) { + FAIL() << "File " << settings.dataLoggingPath << "nodes_layer_" + << results.input.layers + << ".csv should not exist, as there are not that many layers"; + } +} + +class HeuristicTest5Q : public testing::TestWithParam { +protected: + std::string testExampleDir = "../examples/"; + std::string testArchitectureDir = "../extern/architectures/"; + std::string testCalibrationDir = "../extern/calibration/"; + + qc::QuantumComputation qc{}; + Architecture ibmqYorktown{}; + Architecture ibmqLondon{}; + std::unique_ptr ibmqYorktownMapper; + std::unique_ptr ibmqLondonMapper; + Configuration settings{}; + + void SetUp() override { + qc.import(testExampleDir + GetParam() + ".qasm"); + ibmqYorktown.loadCouplingMap(AvailableArchitecture::IbmqYorktown); + ibmqLondon.loadCouplingMap(testArchitectureDir + "ibmq_london.arch"); + ibmqLondon.loadProperties(testCalibrationDir + "ibmq_london.csv"); + ibmqYorktownMapper = std::make_unique(qc, ibmqYorktown); + ibmqLondonMapper = std::make_unique(qc, ibmqLondon); + settings.debug = true; + } +}; + INSTANTIATE_TEST_SUITE_P( Heuristic, HeuristicTest5Q, testing::Values("3_17_13", "ex-1_166", "ham3_102", "miller_11", "4gt11_84", @@ -433,3 +799,426 @@ TEST_P(HeuristicTest20QTeleport, Teleportation) { tokyoMapper->printResult(std::cout); SUCCEED() << "Mapping successful"; } + +class HeuristicTestFidelity : public testing::TestWithParam { +protected: + std::string testExampleDir = "../examples/"; + std::string testArchitectureDir = "../extern/architectures/"; + std::string testCalibrationDir = "../extern/calibration/"; + + qc::QuantumComputation qc{}; + Architecture arch{}; + Architecture nonFidelityArch{}; + std::unique_ptr mapper; + std::unique_ptr nonFidelityMapper; + + void SetUp() override { + qc.import(testExampleDir + GetParam() + ".qasm"); + arch.loadCouplingMap(testArchitectureDir + "ibmq_london.arch"); + arch.loadProperties(testCalibrationDir + "ibmq_london.csv"); + mapper = std::make_unique(qc, arch); + nonFidelityArch.loadCouplingMap(AvailableArchitecture::IbmqYorktown); + nonFidelityMapper = std::make_unique(qc, nonFidelityArch); + } +}; + +INSTANTIATE_TEST_SUITE_P( + Heuristic, HeuristicTestFidelity, + testing::Values("3_17_13", "ex-1_166", "ham3_102", "miller_11", "4gt11_84", + "4mod5-v0_20", "mod5d1_63"), + [](const testing::TestParamInfo& inf) { + std::string name = inf.param; + std::replace(name.begin(), name.end(), '-', '_'); + return name; + }); + +TEST_P(HeuristicTestFidelity, Identity) { + Configuration settings{}; + settings.layering = Layering::DisjointQubits; + settings.initialLayout = InitialLayout::Identity; + settings.considerFidelity = true; + mapper->map(settings); + mapper->dumpResult(GetParam() + "_heuristic_london_fidelity_identity.qasm"); + mapper->printResult(std::cout); + SUCCEED() << "Mapping successful"; +} + +TEST_P(HeuristicTestFidelity, Static) { + Configuration settings{}; + settings.layering = Layering::DisjointQubits; + settings.initialLayout = InitialLayout::Static; + settings.considerFidelity = true; + mapper->map(settings); + mapper->dumpResult(GetParam() + "_heuristic_london_fidelity_static.qasm"); + mapper->printResult(std::cout); + SUCCEED() << "Mapping successful"; +} + +TEST_P(HeuristicTestFidelity, NoFidelity) { + Configuration settings{}; + settings.layering = Layering::DisjointQubits; + settings.initialLayout = InitialLayout::Static; + settings.considerFidelity = true; + nonFidelityMapper->map(settings); + nonFidelityMapper->dumpResult(GetParam() + + "_heuristic_london_nofidelity.qasm"); + nonFidelityMapper->printResult(std::cout); + SUCCEED() << "Mapping successful"; +} + +// TODO: deactivated because of long runtime, reactivate once premature +// search-termination for fidelity-aware mapping is implemented +// TEST(HeuristicTestFidelity, SimpleGrid) { +// Architecture architecture{}; +// const CouplingMap cm = { +// {0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 3}, {3, 2}, + +// {0, 4}, {4, 0}, {1, 5}, {5, 1}, {2, 6}, {6, 2}, {3, 7}, {7, 3}, + +// {4, 5}, {5, 4}, {5, 6}, {6, 5}, {6, 7}, {7, 6}, + +// {4, 8}, {8, 4}, {5, 9}, {9, 5}, {6, 10}, {10, 6}, {7, 11}, {11, 7}, + +// {8, 9}, {9, 8}, {9, 10}, {10, 9}, {10, 11}, {11, 10}}; +// architecture.loadCouplingMap(12, cm); + +// double e5 = 0.99; +// double e4 = 0.9; +// double e3 = 0.5; +// double e2 = 0.4; +// double e1 = 0.1; +// double e0 = 0.01; + +// auto props = Architecture::Properties(); +// props.setSingleQubitErrorRate(0, "x", e5); +// props.setSingleQubitErrorRate(1, "x", e5); +// props.setSingleQubitErrorRate(2, "x", e5); +// props.setSingleQubitErrorRate(3, "x", e5); +// props.setSingleQubitErrorRate(4, "x", e3); +// props.setSingleQubitErrorRate(5, "x", e3); +// props.setSingleQubitErrorRate(6, "x", e5); +// props.setSingleQubitErrorRate(7, "x", e3); +// props.setSingleQubitErrorRate(8, "x", e2); +// props.setSingleQubitErrorRate(9, "x", e1); +// props.setSingleQubitErrorRate(10, "x", e5); +// props.setSingleQubitErrorRate(11, "x", e2); + +// props.setTwoQubitErrorRate(0, 1, e4); +// props.setTwoQubitErrorRate(1, 0, e4); +// props.setTwoQubitErrorRate(1, 2, e4); +// props.setTwoQubitErrorRate(2, 1, e4); +// props.setTwoQubitErrorRate(2, 3, e1); +// props.setTwoQubitErrorRate(3, 2, e1); + +// props.setTwoQubitErrorRate(0, 4, e5); +// props.setTwoQubitErrorRate(4, 0, e5); +// props.setTwoQubitErrorRate(1, 5, e5); +// props.setTwoQubitErrorRate(5, 1, e5); +// props.setTwoQubitErrorRate(2, 6, e4); +// props.setTwoQubitErrorRate(6, 2, e4); +// props.setTwoQubitErrorRate(3, 7, e5); +// props.setTwoQubitErrorRate(7, 3, e5); + +// props.setTwoQubitErrorRate(4, 5, e3); +// props.setTwoQubitErrorRate(5, 4, e3); +// props.setTwoQubitErrorRate(5, 6, e5); +// props.setTwoQubitErrorRate(6, 5, e5); +// props.setTwoQubitErrorRate(6, 7, e5); +// props.setTwoQubitErrorRate(7, 6, e5); + +// props.setTwoQubitErrorRate(4, 8, e0); +// props.setTwoQubitErrorRate(8, 4, e0); +// props.setTwoQubitErrorRate(5, 9, e3); +// props.setTwoQubitErrorRate(9, 5, e3); +// props.setTwoQubitErrorRate(6, 10, e1); +// props.setTwoQubitErrorRate(10, 6, e1); +// props.setTwoQubitErrorRate(7, 11, e3); +// props.setTwoQubitErrorRate(11, 7, e3); + +// props.setTwoQubitErrorRate(8, 9, e5); +// props.setTwoQubitErrorRate(9, 8, e5); +// props.setTwoQubitErrorRate(9, 10, e4); +// props.setTwoQubitErrorRate(10, 9, e4); +// props.setTwoQubitErrorRate(10, 11, e5); +// props.setTwoQubitErrorRate(11, 10, e5); + +// architecture.loadProperties(props); + +// qc::QuantumComputation qc{12, 12}; + +// for (std::size_t i = 0; i < 50; ++i) { +// qc.x(4); +// } +// qc.x(5); +// qc.x(7); +// for (std::size_t i = 0; i < 5; ++i) { +// qc.x(0, qc::Control{3}); +// qc.x(2, qc::Control{9}); +// } + +// for (size_t i = 0; i < 12; ++i) { +// qc.measure(static_cast(i), i); +// } + +// auto mapper = std::make_unique(qc, architecture); + +// Configuration settings{}; +// settings.verbose = true; +// settings.admissibleHeuristic = true; +// settings.layering = Layering::Disjoint2qBlocks; +// settings.initialLayout = InitialLayout::Identity; +// settings.considerFidelity = true; +// settings.preMappingOptimizations = false; +// settings.postMappingOptimizations = false; +// settings.swapOnFirstLayer = true; +// mapper->map(settings); +// mapper->dumpResult("simple_grid_mapped.qasm"); +// mapper->printResult(std::cout); + +// auto& result = mapper->getResults(); +// EXPECT_EQ(result.input.layers, 1); +// /* +// expected output (order of gates may vary): +// SWAP(2,6) +// SWAP(9,10) +// SWAP(0,1) +// SWAP(1,2) +// SWAP(4,5) +// SWAP(5,9) +// SWAP(4,8) +// X(8) +// X(7) +// X(9) [x50] +// CX(2,3) [x5] +// CX(6,10) [x5] +// */ +// EXPECT_EQ(result.output.swaps, 7); + +// double c4 = -std::log2(1 - e4); +// double c3 = -std::log2(1 - e3); +// double c2 = -std::log2(1 - e2); +// double c1 = -std::log2(1 - e1); +// double c0 = -std::log2(1 - e0); + +// double expectedFidelity = 3 * c4 + 3 * c4 + 3 * c4 + 3 * c4 + 3 * c3 + +// 3 * c3 + 3 * c0 + // SWAPs +// c2 + c3 + 50 * c1 + // Xs +// 5 * c1 + 5 * c1; // CXs +// EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); +// } + +TEST(HeuristicTestFidelity, RemapSingleQubit) { + Architecture architecture{}; + const CouplingMap cm = { + {0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 3}, + {3, 2}, {3, 4}, {4, 3}, {4, 5}, {5, 4}, + }; + architecture.loadCouplingMap(6, cm); + + double e5 = 0.99; + double e4 = 0.9; + double e3 = 0.5; + double e1 = 0.1; + double e0 = 0.01; + + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", e5); + props.setSingleQubitErrorRate(1, "x", e5); + props.setSingleQubitErrorRate(2, "x", e5); + props.setSingleQubitErrorRate(3, "x", e4); + props.setSingleQubitErrorRate(4, "x", e4); + props.setSingleQubitErrorRate(5, "x", e1); + + props.setTwoQubitErrorRate(0, 1, e1); + props.setTwoQubitErrorRate(1, 0, e1); + props.setTwoQubitErrorRate(1, 2, e3); + props.setTwoQubitErrorRate(2, 1, e3); + props.setTwoQubitErrorRate(2, 3, e5); + props.setTwoQubitErrorRate(3, 2, e5); + props.setTwoQubitErrorRate(3, 4, e0); + props.setTwoQubitErrorRate(4, 3, e0); + props.setTwoQubitErrorRate(4, 5, e0); + props.setTwoQubitErrorRate(5, 4, e0); + + architecture.loadProperties(props); + + qc::QuantumComputation qc{6, 6}; + for (std::size_t i = 0; i < 5; ++i) { + qc.x(0, qc::Control{2}); + qc.x(3); + } + + for (size_t i = 0; i < 6; ++i) { + qc.measure(static_cast(i), i); + } + + auto mapper = std::make_unique(qc, architecture); + + Configuration settings{}; + settings.admissibleHeuristic = true; + settings.layering = Layering::Disjoint2qBlocks; + settings.initialLayout = InitialLayout::Identity; + settings.considerFidelity = true; + settings.preMappingOptimizations = false; + settings.postMappingOptimizations = false; + settings.verbose = true; + settings.swapOnFirstLayer = true; + mapper->map(settings); + mapper->dumpResult("remap_single_qubit_mapped.qasm"); + mapper->printResult(std::cout); + + // 0 --e1-- 1 --e3-- 2 --e5-- 3 --e0-- 4 --e0-- 5 + // e5 e5 e5 e4 e4 e1 + + auto& result = mapper->getResults(); + EXPECT_EQ(result.input.layers, 1); + /* + expected output (order of gates may vary): + SWAP(1,2) + SWAP(3,4) + SWAP(4,5) + CX(0,1) [x5] + X(5) [x5] + */ + EXPECT_EQ(result.output.swaps, 3); + + double c3 = -std::log2(1 - e3); + double c1 = -std::log2(1 - e1); + double c0 = -std::log2(1 - e0); + + double expectedFidelity = 3 * c3 + 3 * c0 + 3 * c0 + // SWAPs + 5 * c1 + // Xs + 5 * c1; // CXs + EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); +} + +TEST(HeuristicTestFidelity, QubitRideAlong) { + Architecture architecture{}; + const CouplingMap cm = {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 3}, {3, 2}, + {1, 4}, {4, 1}, {2, 5}, {5, 2}, {5, 6}, {6, 5}}; + architecture.loadCouplingMap(7, cm); + + double e5 = 0.99; + double e4 = 0.9; + double e3 = 0.5; + double e1 = 0.1; + + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", e5); + props.setSingleQubitErrorRate(1, "x", e5); + props.setSingleQubitErrorRate(2, "x", e5); + props.setSingleQubitErrorRate(3, "x", e5); + props.setSingleQubitErrorRate(4, "x", e5); + props.setSingleQubitErrorRate(5, "x", e5); + props.setSingleQubitErrorRate(6, "x", e5); + + props.setTwoQubitErrorRate(0, 1, e4); + props.setTwoQubitErrorRate(1, 0, e4); + props.setTwoQubitErrorRate(1, 2, e3); + props.setTwoQubitErrorRate(2, 1, e3); + props.setTwoQubitErrorRate(2, 3, e1); + props.setTwoQubitErrorRate(3, 2, e1); + props.setTwoQubitErrorRate(1, 4, e1); + props.setTwoQubitErrorRate(4, 1, e1); + props.setTwoQubitErrorRate(2, 5, e3); + props.setTwoQubitErrorRate(5, 2, e3); + props.setTwoQubitErrorRate(5, 6, e4); + props.setTwoQubitErrorRate(6, 5, e4); + + architecture.loadProperties(props); + + qc::QuantumComputation qc{7, 7}; + for (std::size_t i = 0; i < 5; ++i) { + qc.x(0, qc::Control{3}); + qc.x(4, qc::Control{6}); + } + + for (size_t i = 0; i < 7; ++i) { + qc.measure(static_cast(i), i); + } + + auto mapper = std::make_unique(qc, architecture); + + Configuration settings{}; + settings.admissibleHeuristic = true; + settings.layering = Layering::Disjoint2qBlocks; + settings.initialLayout = InitialLayout::Identity; + settings.considerFidelity = true; + settings.preMappingOptimizations = false; + settings.postMappingOptimizations = false; + settings.verbose = true; + settings.swapOnFirstLayer = true; + mapper->map(settings); + mapper->dumpResult("qubit_ride_along_mapped.qasm"); + mapper->printResult(std::cout); + + auto& result = mapper->getResults(); + EXPECT_EQ(result.input.layers, 1); + /* + expected output (order of gates may vary): + SWAP(5,6) + SWAP(2,5) + SWAP(0,1) + SWAP(1,2) + CX(2,3) [x5] + CX(4,1) [x5] + */ + EXPECT_EQ(result.output.swaps, 4); + + double c4 = -std::log2(1 - e4); + double c3 = -std::log2(1 - e3); + double c1 = -std::log2(1 - e1); + + double expectedFidelity = 3 * c4 + 3 * c3 + 3 * c4 + 3 * c3 + // SWAPs + 5 * c1 + 5 * c1; // CXs + EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); +} + +TEST(HeuristicTestFidelity, SingleQubitsCompete) { + Architecture architecture{}; + const CouplingMap cm = {{0, 1}, {1, 0}, {1, 2}, {2, 1}}; + architecture.loadCouplingMap(3, cm); + + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", 0.8); + props.setSingleQubitErrorRate(1, "x", 0.1); + props.setSingleQubitErrorRate(2, "x", 0.8); + + props.setTwoQubitErrorRate(0, 1, 0.1); + props.setTwoQubitErrorRate(1, 0, 0.1); + props.setTwoQubitErrorRate(1, 2, 0.1); + props.setTwoQubitErrorRate(2, 1, 0.1); + + architecture.loadProperties(props); + + qc::QuantumComputation qc{3, 3}; + qc.x(0); + qc.x(2); + + for (size_t i = 0; i < 3; ++i) { + qc.measure(static_cast(i), i); + } + + auto mapper = std::make_unique(qc, architecture); + + Configuration settings{}; + settings.admissibleHeuristic = true; + settings.layering = Layering::Disjoint2qBlocks; + settings.initialLayout = InitialLayout::Identity; + settings.considerFidelity = true; + settings.preMappingOptimizations = false; + settings.postMappingOptimizations = false; + settings.verbose = true; + settings.swapOnFirstLayer = true; + mapper->map(settings); + mapper->dumpResult("single_qubits_compete.qasm"); + mapper->printResult(std::cout); + + auto& result = mapper->getResults(); + EXPECT_EQ(result.input.layers, 1); + EXPECT_EQ(result.output.swaps, 1); + + double expectedFidelity = -3 * std::log2(1 - 0.1) // SWAPs + - std::log2(1 - 0.8) - std::log2(1 - 0.1); // Xs + EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); +} From 36d833f368fa98826ffdacc87e0fb959db1a4a23 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Mon, 23 Oct 2023 06:31:04 +0200 Subject: [PATCH 002/108] remove long running fidelity test case --- test/test_heuristic.cpp | 141 ---------------------------------------- 1 file changed, 141 deletions(-) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index ffc853666..bb517d50c 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -866,147 +866,6 @@ TEST_P(HeuristicTestFidelity, NoFidelity) { SUCCEED() << "Mapping successful"; } -// TODO: deactivated because of long runtime, reactivate once premature -// search-termination for fidelity-aware mapping is implemented -// TEST(HeuristicTestFidelity, SimpleGrid) { -// Architecture architecture{}; -// const CouplingMap cm = { -// {0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 3}, {3, 2}, - -// {0, 4}, {4, 0}, {1, 5}, {5, 1}, {2, 6}, {6, 2}, {3, 7}, {7, 3}, - -// {4, 5}, {5, 4}, {5, 6}, {6, 5}, {6, 7}, {7, 6}, - -// {4, 8}, {8, 4}, {5, 9}, {9, 5}, {6, 10}, {10, 6}, {7, 11}, {11, 7}, - -// {8, 9}, {9, 8}, {9, 10}, {10, 9}, {10, 11}, {11, 10}}; -// architecture.loadCouplingMap(12, cm); - -// double e5 = 0.99; -// double e4 = 0.9; -// double e3 = 0.5; -// double e2 = 0.4; -// double e1 = 0.1; -// double e0 = 0.01; - -// auto props = Architecture::Properties(); -// props.setSingleQubitErrorRate(0, "x", e5); -// props.setSingleQubitErrorRate(1, "x", e5); -// props.setSingleQubitErrorRate(2, "x", e5); -// props.setSingleQubitErrorRate(3, "x", e5); -// props.setSingleQubitErrorRate(4, "x", e3); -// props.setSingleQubitErrorRate(5, "x", e3); -// props.setSingleQubitErrorRate(6, "x", e5); -// props.setSingleQubitErrorRate(7, "x", e3); -// props.setSingleQubitErrorRate(8, "x", e2); -// props.setSingleQubitErrorRate(9, "x", e1); -// props.setSingleQubitErrorRate(10, "x", e5); -// props.setSingleQubitErrorRate(11, "x", e2); - -// props.setTwoQubitErrorRate(0, 1, e4); -// props.setTwoQubitErrorRate(1, 0, e4); -// props.setTwoQubitErrorRate(1, 2, e4); -// props.setTwoQubitErrorRate(2, 1, e4); -// props.setTwoQubitErrorRate(2, 3, e1); -// props.setTwoQubitErrorRate(3, 2, e1); - -// props.setTwoQubitErrorRate(0, 4, e5); -// props.setTwoQubitErrorRate(4, 0, e5); -// props.setTwoQubitErrorRate(1, 5, e5); -// props.setTwoQubitErrorRate(5, 1, e5); -// props.setTwoQubitErrorRate(2, 6, e4); -// props.setTwoQubitErrorRate(6, 2, e4); -// props.setTwoQubitErrorRate(3, 7, e5); -// props.setTwoQubitErrorRate(7, 3, e5); - -// props.setTwoQubitErrorRate(4, 5, e3); -// props.setTwoQubitErrorRate(5, 4, e3); -// props.setTwoQubitErrorRate(5, 6, e5); -// props.setTwoQubitErrorRate(6, 5, e5); -// props.setTwoQubitErrorRate(6, 7, e5); -// props.setTwoQubitErrorRate(7, 6, e5); - -// props.setTwoQubitErrorRate(4, 8, e0); -// props.setTwoQubitErrorRate(8, 4, e0); -// props.setTwoQubitErrorRate(5, 9, e3); -// props.setTwoQubitErrorRate(9, 5, e3); -// props.setTwoQubitErrorRate(6, 10, e1); -// props.setTwoQubitErrorRate(10, 6, e1); -// props.setTwoQubitErrorRate(7, 11, e3); -// props.setTwoQubitErrorRate(11, 7, e3); - -// props.setTwoQubitErrorRate(8, 9, e5); -// props.setTwoQubitErrorRate(9, 8, e5); -// props.setTwoQubitErrorRate(9, 10, e4); -// props.setTwoQubitErrorRate(10, 9, e4); -// props.setTwoQubitErrorRate(10, 11, e5); -// props.setTwoQubitErrorRate(11, 10, e5); - -// architecture.loadProperties(props); - -// qc::QuantumComputation qc{12, 12}; - -// for (std::size_t i = 0; i < 50; ++i) { -// qc.x(4); -// } -// qc.x(5); -// qc.x(7); -// for (std::size_t i = 0; i < 5; ++i) { -// qc.x(0, qc::Control{3}); -// qc.x(2, qc::Control{9}); -// } - -// for (size_t i = 0; i < 12; ++i) { -// qc.measure(static_cast(i), i); -// } - -// auto mapper = std::make_unique(qc, architecture); - -// Configuration settings{}; -// settings.verbose = true; -// settings.admissibleHeuristic = true; -// settings.layering = Layering::Disjoint2qBlocks; -// settings.initialLayout = InitialLayout::Identity; -// settings.considerFidelity = true; -// settings.preMappingOptimizations = false; -// settings.postMappingOptimizations = false; -// settings.swapOnFirstLayer = true; -// mapper->map(settings); -// mapper->dumpResult("simple_grid_mapped.qasm"); -// mapper->printResult(std::cout); - -// auto& result = mapper->getResults(); -// EXPECT_EQ(result.input.layers, 1); -// /* -// expected output (order of gates may vary): -// SWAP(2,6) -// SWAP(9,10) -// SWAP(0,1) -// SWAP(1,2) -// SWAP(4,5) -// SWAP(5,9) -// SWAP(4,8) -// X(8) -// X(7) -// X(9) [x50] -// CX(2,3) [x5] -// CX(6,10) [x5] -// */ -// EXPECT_EQ(result.output.swaps, 7); - -// double c4 = -std::log2(1 - e4); -// double c3 = -std::log2(1 - e3); -// double c2 = -std::log2(1 - e2); -// double c1 = -std::log2(1 - e1); -// double c0 = -std::log2(1 - e0); - -// double expectedFidelity = 3 * c4 + 3 * c4 + 3 * c4 + 3 * c4 + 3 * c3 + -// 3 * c3 + 3 * c0 + // SWAPs -// c2 + c3 + 50 * c1 + // Xs -// 5 * c1 + 5 * c1; // CXs -// EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); -// } - TEST(HeuristicTestFidelity, RemapSingleQubit) { Architecture architecture{}; const CouplingMap cm = { From 66972a2dfd013d66213ecf8ef9eacd6db9f4307f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 04:35:03 +0000 Subject: [PATCH 003/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_heuristic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index bb517d50c..003968705 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -428,7 +428,7 @@ TEST(Functionality, DataLogger) { results.layerHeuristicBenchmark.at(i).timePerNode); } } - + auto inputQasmFile = std::ifstream(settings.dataLoggingPath + "input.qasm"); if (!inputQasmFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath From 4aeac17743c84875814ba9a844665cc3897e62a9 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Tue, 24 Oct 2023 05:04:51 +0200 Subject: [PATCH 004/108] fixing linter warnings --- include/heuristic/HeuristicMapper.hpp | 6 +++--- src/DataLogger.cpp | 9 +++++++-- src/heuristic/HeuristicMapper.cpp | 5 ++--- test/test_heuristic.cpp | 21 ++++++++++++++++----- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index d7e730e6e..a15f89524 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -173,9 +173,9 @@ class HeuristicMapper : public Mapper { }; protected: - UniquePriorityQueue nodes{}; - DataLogger* dataLogger; - std::size_t nextNodeId = 0; + UniquePriorityQueue nodes{}; + std::unique_ptr dataLogger; + std::size_t nextNodeId = 0; /** * @brief creates an initial mapping of logical qubits to physical qubits with diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index 40921452d..dbc298f9a 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -207,14 +207,17 @@ void DataLogger::logMappingResult(MappingResults& result) { if (deactivated) { return; } - + + // load output file auto of = std::ofstream(dataLoggingPath + "mapping_result.json"); - if (!of.good()) { + if (!of.good()) { // if loading failed, output warning and deactivate logging deactivated = true; std::cerr << "[data-logging] Error opening file: " << dataLoggingPath << "mapping_result.json" << std::endl; return; } + + // prepare json data nlohmann::json json; auto& circuit = json["input_circuit"]; circuit["name"] = result.input.name; @@ -305,6 +308,8 @@ void DataLogger::logMappingResult(MappingResults& result) { layerBenchmark.effectiveBranchingFactor; } } + + // write json data to file of << json.dump(2); of.close(); }; diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 63658d515..a60e92629 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -9,8 +9,7 @@ void HeuristicMapper::map(const Configuration& configuration) { if (configuration.dataLoggingEnabled()) { - dataLogger = - new DataLogger(configuration.dataLoggingPath, architecture, qc); + dataLogger = std::make_unique(configuration.dataLoggingPath, architecture, qc); } results = MappingResults{}; results.config = configuration; @@ -380,7 +379,7 @@ void HeuristicMapper::mapUnmappedGates( const SingleQubitMultiplicity& singleQubitGateMultiplicity, const TwoQubitMultiplicity& twoQubitGateMultiplicity) { if (results.config.considerFidelity) { - for (std::uint16_t q = 0; q < singleQubitGateMultiplicity.size(); ++q) { + for (std::size_t q = 0; q < singleQubitGateMultiplicity.size(); ++q) { if (singleQubitGateMultiplicity.at(q) == 0) { continue; } diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 003968705..c601f38fb 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -249,6 +249,7 @@ TEST(Functionality, HeuristicAdmissibility) { } TEST(Functionality, DataLogger) { + // setting up example architecture and circuit Architecture architecture{}; const CouplingMap cm = {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {1, 3}, {3, 1}}; architecture.loadCouplingMap(4, cm); @@ -287,13 +288,15 @@ TEST(Functionality, DataLogger) { settings.debug = true; settings.considerFidelity = false; settings.useTeleportation = false; + // setting data logging path to enable data logging settings.dataLoggingPath = "test_log/"; auto mapper = std::make_unique(qc, architecture); mapper->map(settings); mapper->printResult(std::cout); MappingResults& results = mapper->getResults(); - + + // comparing logged architecture information with original architecture object auto archFile = std::ifstream(settings.dataLoggingPath + "architecture.json"); if (!archFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath @@ -311,6 +314,8 @@ TEST(Functionality, DataLogger) { architecture.getSingleQubitFidelities()); EXPECT_EQ(fidelityJson["two_qubit_fidelities"], architecture.getFidelityTable()); + // json does not support inf values, instead nlohmann::json replaces inf + // with null auto& singleQubitFidelityCosts = architecture.getSingleQubitFidelityCosts(); for (std::size_t i = 0; i < singleQubitFidelityCosts.size(); ++i) { if (std::isinf(singleQubitFidelityCosts[i])) { @@ -343,7 +348,8 @@ TEST(Functionality, DataLogger) { } } } - + + // comparing logged mapping result with mapping result object auto resultFile = std::ifstream(settings.dataLoggingPath + "mapping_result.json"); if (!resultFile.is_open()) { @@ -428,7 +434,9 @@ TEST(Functionality, DataLogger) { results.layerHeuristicBenchmark.at(i).timePerNode); } } - + + // comparing logged input and output circuits with input circuit object and + // mapped circuit object auto inputQasmFile = std::ifstream(settings.dataLoggingPath + "input.qasm"); if (!inputQasmFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath @@ -452,7 +460,9 @@ TEST(Functionality, DataLogger) { mapper->dumpResult(qasmBuffer, qc::Format::OpenQASM); EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); } - + + // checking logged search graph info against known values (correct qubit + // number, valid layouts, correct data types in all csv fields, etc.) for (std::size_t i = 0; i < results.input.layers; ++i) { auto layerFile = std::ifstream(settings.dataLoggingPath + "layer_" + std::to_string(i) + ".json"); @@ -580,7 +590,8 @@ TEST(Functionality, DataLogger) { } } } - + + // checking if files for non-existing layers are not created auto afterLastLayerFile = std::ifstream(settings.dataLoggingPath + "layer_" + std::to_string(results.input.layers) + ".json"); From 9f07e7f32004406c4ded16f5aad21655c03fa749 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 03:06:31 +0000 Subject: [PATCH 005/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DataLogger.cpp | 6 +++--- src/heuristic/HeuristicMapper.cpp | 3 ++- test/test_heuristic.cpp | 12 ++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index dbc298f9a..d0f1c248e 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -207,7 +207,7 @@ void DataLogger::logMappingResult(MappingResults& result) { if (deactivated) { return; } - + // load output file auto of = std::ofstream(dataLoggingPath + "mapping_result.json"); if (!of.good()) { // if loading failed, output warning and deactivate logging @@ -216,7 +216,7 @@ void DataLogger::logMappingResult(MappingResults& result) { << "mapping_result.json" << std::endl; return; } - + // prepare json data nlohmann::json json; auto& circuit = json["input_circuit"]; @@ -308,7 +308,7 @@ void DataLogger::logMappingResult(MappingResults& result) { layerBenchmark.effectiveBranchingFactor; } } - + // write json data to file of << json.dump(2); of.close(); diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index a60e92629..b56caccc7 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -9,7 +9,8 @@ void HeuristicMapper::map(const Configuration& configuration) { if (configuration.dataLoggingEnabled()) { - dataLogger = std::make_unique(configuration.dataLoggingPath, architecture, qc); + dataLogger = std::make_unique(configuration.dataLoggingPath, + architecture, qc); } results = MappingResults{}; results.config = configuration; diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index c601f38fb..370dc23aa 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -289,13 +289,13 @@ TEST(Functionality, DataLogger) { settings.considerFidelity = false; settings.useTeleportation = false; // setting data logging path to enable data logging - settings.dataLoggingPath = "test_log/"; + settings.dataLoggingPath = "test_log/"; auto mapper = std::make_unique(qc, architecture); mapper->map(settings); mapper->printResult(std::cout); MappingResults& results = mapper->getResults(); - + // comparing logged architecture information with original architecture object auto archFile = std::ifstream(settings.dataLoggingPath + "architecture.json"); if (!archFile.is_open()) { @@ -348,7 +348,7 @@ TEST(Functionality, DataLogger) { } } } - + // comparing logged mapping result with mapping result object auto resultFile = std::ifstream(settings.dataLoggingPath + "mapping_result.json"); @@ -434,7 +434,7 @@ TEST(Functionality, DataLogger) { results.layerHeuristicBenchmark.at(i).timePerNode); } } - + // comparing logged input and output circuits with input circuit object and // mapped circuit object auto inputQasmFile = std::ifstream(settings.dataLoggingPath + "input.qasm"); @@ -460,7 +460,7 @@ TEST(Functionality, DataLogger) { mapper->dumpResult(qasmBuffer, qc::Format::OpenQASM); EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); } - + // checking logged search graph info against known values (correct qubit // number, valid layouts, correct data types in all csv fields, etc.) for (std::size_t i = 0; i < results.input.layers; ++i) { @@ -590,7 +590,7 @@ TEST(Functionality, DataLogger) { } } } - + // checking if files for non-existing layers are not created auto afterLastLayerFile = std::ifstream(settings.dataLoggingPath + "layer_" + From cb41d5fa0979e263a1cf89dde63a9df1b741d9ee Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Tue, 24 Oct 2023 05:19:20 +0200 Subject: [PATCH 006/108] fixing wrong import path in compile.py --- src/mqt/qmap/compile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 252d7897f..be62e9595 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -10,7 +10,7 @@ from qiskit.providers import Backend from qiskit.providers.models import BackendProperties from qiskit.transpiler.target import Target - from src.mqt.qmap.visualization.search_visualizer import SearchVisualizer + from mqt.qmap.visualization.search_visualizer import SearchVisualizer from .load_architecture import load_architecture from .load_calibration import load_calibration From 7ced47264811269d57b2399b13fe1185dc270a33 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 03:19:52 +0000 Subject: [PATCH 007/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qmap/compile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index be62e9595..d8927794c 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -10,6 +10,7 @@ from qiskit.providers import Backend from qiskit.providers.models import BackendProperties from qiskit.transpiler.target import Target + from mqt.qmap.visualization.search_visualizer import SearchVisualizer from .load_architecture import load_architecture From 972ad92f53388bc3bc3ac8b7846a4bbf7ef634ca Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Sun, 29 Oct 2023 20:49:25 +0100 Subject: [PATCH 008/108] replacing treedraw with walkerlayout and fixing imports --- pyproject.toml | 3 +- src/heuristic/HeuristicMapper.cpp | 2 +- src/mqt/qmap/visualization/treedraw.py | 341 ------------------ .../visualization/visualize_search_graph.py | 33 +- 4 files changed, 9 insertions(+), 370 deletions(-) delete mode 100644 src/mqt/qmap/visualization/treedraw.py diff --git a/pyproject.toml b/pyproject.toml index 42ebf2ce4..d6d6d4c7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ dependencies = [ "distinctipy>=1.2.2", "plotly>=5.15.0", "networkx==2.5", + "walkerlayout==1.0.2", "ipywidgets>=7.6.5", ] dynamic = ["version"] @@ -164,7 +165,7 @@ explicit_package_bases = true pretty = true [[tool.mypy.overrides]] -module = ["qiskit.*", "matplotlib.*"] +module = ["qiskit.*", "matplotlib.*", "networkx.*", "plotly.*", "_plotly_utils.*", "distinctipy.*", "ipywidgets.*", "walkerlayout.*"] ignore_missing_imports = true diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index b56caccc7..d58552fb1 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -1009,7 +1009,7 @@ void HeuristicMapper::Node::recalculateFixedCost( validMappedTwoQubitGates.emplace(q1, q2); } } - // 2-qubit-gates not yet mapped next to eachother are handled in the + // 2-qubit-gates not yet mapped next to each other are handled in the // heuristic } else { for (auto& swapNode : swaps) { diff --git a/src/mqt/qmap/visualization/treedraw.py b/src/mqt/qmap/visualization/treedraw.py deleted file mode 100644 index 1d6b02376..000000000 --- a/src/mqt/qmap/visualization/treedraw.py +++ /dev/null @@ -1,341 +0,0 @@ -"""treedraw.py -===========. - -Adapted from: -https://github.com/cvzi/py_treedraw - -This module implements a layout algorithm for a tree. -Each node in the tree has a layout property that contains an x and y -coordinate. -The layout is calculated by calling the method "walker(distance)". -The distance property indicated the distance between nodes on the same level. -If the tree is build up using the addChild/removeChild methods, the layout -will be calculated in linear time. -The algoithm is a python implemenation of this publication - "Improving -Walker's Algorithm to Run in Linear Time" by Christoph Buchheim, Michael -Junger, Sebastian Leipert" -""" -from __future__ import annotations - -__all__ = ["Tree", "Node"] - -__version__ = "1.4" - -import math - -try: - xrange(5) - myrange = xrange -except NameError: - myrange = range - - -class Tree: - def __init__(self, data) -> None: - # Start a tree with a root node - self.root = Node(data) - self.root.tree = self - self.root.leftSibling = None - self.nodes = [self.root] - - def addNode(self, node): - # add a child to the root - return self.root.addNode(node) - - def addChild(self, data): - # add a child to the root - return self.root.addChild(data) - - def removeChild(self, node): - # remove a child from the root - return self.root.removeChild(node) - - def walker(self, distance=1.0): - # Init layout algorithm - self._firstWalk(self.root, distance) - self._secondWalk(self.root, -self.root.layout.prelim) - - def _add(self, node): - if node not in self.nodes: - self.nodes.append(node) - return node - - def _firstWalk(self, v, distance): - if v.children: - defaultAncestor = v.children[0] - for w in v.children: - self._firstWalk(w, distance) - self._apportion(w, defaultAncestor, distance) - self._executeShifts(v) - midpoint = 0.5 * (v.children[0].layout.prelim + v.children[-1].layout.prelim) - w = self._leftSibling(v) - if w is not None: - v.layout.prelim = w.layout.prelim + distance - v.layout.mod = v.layout.prelim - midpoint - else: - v.layout.prelim = midpoint - else: - ls = self._leftSibling(v) - if ls: - v.layout.prelim = ls.layout.prelim + distance - - def _secondWalk(self, v, m): - v.layout.x(v.layout.prelim + m) - for w in v.children: - self._secondWalk(w, m + v.layout.mod) - - def _apportion(self, v, defaultAncestor, distance): - w = self._leftSibling(v) - if w is not None: - v_p_o = v - v_p_i = v - v_m_i = w - v_m_o = v_p_i.parent.children[0] - s_p_i = v_p_i.layout.mod - s_p_o = v_p_o.layout.mod - s_m_i = v_m_i.layout.mod - s_m_o = v_m_o.layout.mod - while v_m_i.nextRight() and v_p_i.nextLeft(): - v_m_i = v_m_i.nextRight() - v_p_i = v_p_i.nextLeft() - v_m_o = v_m_o.nextLeft() - v_p_o = v_p_o.nextRight() - v_p_o.layout.ancestor = v - shift = v_m_i.layout.prelim + s_m_i - (v_p_i.layout.prelim + s_p_i) + distance - if shift > 0: - self._moveSubtree(self._ancestor(v_m_i, v, defaultAncestor), v, shift) - s_p_i = s_p_i + shift - s_p_o = s_p_o + shift - s_m_i = s_m_i + v_m_i.layout.mod - s_p_i = s_p_i + v_p_i.layout.mod - s_m_o = s_m_o + v_m_o.layout.mod - s_p_o = s_p_o + v_p_o.layout.mod - if v_m_i.nextRight() and v_p_o.nextRight() is None: - v_p_o.layout.thread = v_m_i.nextRight() - v_p_o.layout.mod = v_p_o.layout.mod + s_m_i - s_p_o - if v_p_i.nextLeft() and v_m_o.nextLeft() is None: - v_m_o.layout.thread = v_p_i.nextLeft() - v_m_o.layout.mod = v_m_o.layout.mod + s_p_i - s_m_o - defaultAncestor = v - return defaultAncestor - - @staticmethod - def _leftSibling(v): - if v.leftSibling != -1: - return v.leftSibling - else: - if v.parent is None or not v.parent.children: - return None - last = None - for w in v.parent.children: - if w == v: - if last is not None: - return last - return None - last = w - return None - - @staticmethod - def _moveSubtree(w_m, w_p, shift): - subtrees = w_p.number() - w_m.number() - if subtrees == 0: - subtrees = 0.0000001 - w_p.layout.change = w_p.layout.change - shift / subtrees - w_p.layout.shift = w_p.layout.shift + shift - w_m.layout.change = w_m.layout.change + shift / subtrees - w_p.layout.prelim = w_p.layout.prelim + shift - w_p.layout.mod = w_p.layout.mod + shift - - @staticmethod - def _executeShifts(v): - shift = 0 - change = 0 - i = len(v.children) - for i in myrange(len(v.children) - 1, -1, -1): - w = v.children[i] - w.layout.prelim = w.layout.prelim + shift - w.layout.mod = w.layout.mod + shift - change = change + w.layout.change - shift = shift + w.layout.shift + change - - @staticmethod - def _ancestor(v_i, v, defaultAncestor): - if v_i.layout.ancestor.parent == v.parent: - return v_i.layout.ancestor - return defaultAncestor - - -class Node: - class Layout: - def __init__(self, v) -> None: - self.node = v - self.mod = 0 - self.thread = None - self.ancestor = v - self.prelim = 0 - self.shift = 0 - self.change = 0 - self.pos = [None, None] - self.number = -1 # undefined - - def x(self, value=None): - if value is not None: - self.pos[0] = value - return None - else: - return self.pos[0] - - def y(self, value=None): - if value is not None: - self.pos[1] = value - return None - else: - if self.pos[1] is None: - self.pos[1] = self.node.level() - return self.pos[1] - - def __init__(self, data) -> None: - self.tree = None - self.data = data - self.leftSibling = -1 # undefined, outdated - self.children = [] - self.parent = None - self.layout = self.Layout(self) - - def addNode(self, node): - # Add an existing tree/node as a child - - # Set left sibling - if self.children: - node.leftSibling = self.children[-1] - node.layout.number = node.leftSibling.layout.number + 1 - else: - node.leftSibling = None - node.layout.number = 0 - - # Append to node - node.parent = self - self.children.append(node) - - # Add to tree - root = self - i = 0 - while root.parent is not None: - root = root.parent - i += 1 - root.tree._add(node) - node.tree = root.tree - self.layout.pos[1] = i # Level - return node - - def addChild(self, data): - # Create a new node and add it as a child - return self.addNode(Node(data)) - - def removeChild(self, v): - j = -1 - for i in myrange(len(self.children)): - if self.children[i] == v: - del self.children[i] - j = i - break - - for i in myrange(len(self.tree.nodes)): - if self.tree.nodes[i] == v: - del self.tree.nodes[i] - break - - # Update left sibling - if j == 0: - self.children[0].leftSibling = None - elif j > 0: - self.children[j].leftSibling = self.children[j - 1] - else: # j == -1 - return - - # Update numbers - for i in myrange(j, len(self.children)): - self.children[i].layout.number = i - - # Remove children of the deleted node - i = 0 - while i < len(self.tree.nodes): - if self.tree.nodes[i] in v.children: - del self.tree.nodes[i] - else: - i += 1 - - v.children = [] - - def nextLeft(self): - if self.children: - return self.children[0] - else: - return self.layout.thread - - def nextRight(self): - if self.children: - return self.children[-1] - else: - return self.layout.thread - - def level(self): - if self.layout.pos[1] is not None: - return self.layout.pos[1] - n = self.parent - i = 0 - while n is not None: - n = n.parent - i += 1 - return i - - def position(self, origin=(0, 0), scalex=1.0, scaley=None): - """Return position as integer - Examples: - position(origin) - position(origin, 10) - position(origin, 10, 15) - position(origin, (10, 15)). - """ - if scaley is None: - if hasattr(scalex, "__getitem__"): - scaley = scalex[1] - scalex = scalex[0] - else: - scaley = scalex - return ( - origin[0] + int(math.ceil(self.layout.x() * scalex)), - origin[1] + int(math.ceil(self.layout.y() * scaley)), - ) - - def positionf(self, origin=(0.0, 0.0), scalex=1.0, scaley=None): - """Return position as floating point - Examples: - position(origin) - position(origin, 10) - position(origin, 10, 15) - position(origin, (10, 15)). - """ - if scaley is None: - if hasattr(scalex, "__getitem__"): - scaley = scalex[1] - scalex = scalex[0] - else: - scaley = scalex - return (origin[0] + (self.layout.x() * scalex), origin[1] + (self.layout.y() * scaley)) - - def number(self): - if self.layout.number != -1: - return self.layout.number - else: - if self.parent is None: - return 0 - else: - i = 0 - for node in self.parent.children: - if node == self: - return i - i += 1 - msg = "Error in number(self)!" - raise Exception(msg) diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index ff6f7e24e..2153f515d 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -15,8 +15,7 @@ from ipywidgets import HBox, IntSlider, Layout, Play, VBox, Widget, interactive, jslink from networkx.drawing.nx_pydot import graphviz_layout from plotly.subplots import make_subplots - -from mqt.qmap.visualization.treedraw import Tree +from walkerlayout import WalkerLayouting @dataclass @@ -133,19 +132,10 @@ def _layout_search_graph( search_graph: nx.Graph, root: int, method: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], - walker_factor: float, # 0-1 tapered_layer_heights: bool, ) -> dict[int, Position]: if method == "walker": - tree = Tree(root) - nodes = {root: tree.root} - for node in search_graph.nodes: - if node != root: - nodes[node] = nodes[search_graph.nodes[node]["data"].parent].addChild(node) - tree.walker(walker_factor) - pos = {} - for node in tree.nodes: - pos[node.data] = node.position(origin=(0, 0), scalex=100, scaley=-1) + pos = WalkerLayouting.layout_networkx(search_graph, root, origin=(0, 0), scalex=60, scaley=-1) else: pos = graphviz_layout(search_graph, prog=method, root=search_graph.nodes[0]) @@ -481,7 +471,7 @@ def _parse_arch_graph(file_path: str) -> nx.Graph: # TODO: this is the cost of 1 swap for the non-noise-aware heuristic # mapper; depending on directionality this might be different; once # more dynamic swap cost system in Architecture.cpp is implemented - # replace wiht dynamic cost lookup + # replace with dynamic cost lookup edges.add(edge) nqbits = max(nqbits, q0, q1) nqbits += 1 @@ -731,7 +721,6 @@ def _load_layer_data( data_logging_path: str, layer: int, layout_method: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], - layout_walker_factor: float, # 0-1 tapered_layer_heights: bool, number_of_node_traces: int, use3d: bool, @@ -790,7 +779,7 @@ def _load_layer_data( graph, graph_root = _parse_search_graph(f"{data_logging_path}nodes_layer_{layer}.csv", final_node_id) - pos = _layout_search_graph(graph, graph_root, layout_method, layout_walker_factor, tapered_layer_heights) + pos = _layout_search_graph(graph, graph_root, layout_method, tapered_layer_heights) ( edge_x, @@ -971,7 +960,6 @@ def _visualize_search_graph_check_parameters( architecture_node_positions: dict[int, Position] | None, architecture_layout_method: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], search_node_layout_method: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], - search_node_layout_walker_factor: float, search_graph_border: float, architecture_border: float, swap_arrow_spacing: float, @@ -1067,12 +1055,6 @@ def _visualize_search_graph_check_parameters( msg = 'search_node_layout_method must be one of "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"' raise ValueError(msg) - if search_node_layout_method == "walker" and ( - not _is_number(search_node_layout_walker_factor) or search_node_layout_walker_factor < 0 - ): - msg = "search_node_layout_walker_factor must be a non-negative float" - raise ValueError(msg) - if not _is_number(search_graph_border) or search_graph_border < 0: msg = "search_graph_border must be a non-negative float" raise ValueError(msg) @@ -1417,7 +1399,7 @@ def _visualize_search_graph_check_parameters( if isinstance(c, str): l = _cost_string_to_lambda(c) if l is None: - msg = f"Unkown cost function preset search_node_height[{i}]: {c}" + msg = f"Unknown cost function preset search_node_height[{i}]: {c}" raise ValueError(msg) elif l in lambdas: msg = f"search_node_height must not contain the same cost function multiple times: {c}" @@ -1435,7 +1417,7 @@ def _visualize_search_graph_check_parameters( if l is not None: search_node_height = [l] else: - msg = f"Unkown cost function preset search_node_height: {search_node_height}" + msg = f"Unknown cost function preset search_node_height: {search_node_height}" raise ValueError(msg) else: msg = "search_node_height must be a list of cost functions or a single cost function." @@ -1466,7 +1448,6 @@ def visualize_search_graph( search_node_layout_method: Literal[ "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ] = "walker", - search_node_layout_walker_factor: float = 0.6, search_graph_border: float = 0.05, architecture_border: float = 0.05, swap_arrow_spacing: float = 0.05, @@ -1563,7 +1544,6 @@ def visualize_search_graph( architecture_node_positions, architecture_layout_method, search_node_layout_method, - search_node_layout_walker_factor, search_graph_border, architecture_border, swap_arrow_spacing, @@ -1901,7 +1881,6 @@ def update_layer(new_layer: int): data_logging_path, current_layer, search_node_layout_method, - search_node_layout_walker_factor, tapered_search_layer_heights, number_of_node_traces, use3d, From 90e9006c4f28a775473022c0514bbb92b6148e04 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Mon, 30 Oct 2023 22:55:24 +0100 Subject: [PATCH 009/108] fix type hinting in search graph visualization --- src/mqt/qmap/compile.py | 7 +- src/mqt/qmap/visualization/__init__.py | 12 +- .../qmap/visualization/search_visualizer.py | 118 +- .../visualization/visualize_search_graph.py | 1101 ++++++++++------- 4 files changed, 735 insertions(+), 503 deletions(-) diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index d8927794c..4bdfe93b6 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -61,7 +61,7 @@ def compile( # noqa: A001 method: str | Method = "heuristic", initial_layout: str | InitialLayout = "dynamic", layering: str | Layering = "individual_gates", - lookaheads: int | None = None, + lookaheads: int | None = 15, lookahead_factor: float = 0.5, use_teleportation: bool = False, teleportation_fake: bool = False, @@ -90,6 +90,8 @@ def compile( # noqa: A001 method: The mapping method to use. Either "heuristic" or "exact". Defaults to "heuristic". initial_layout: The initial layout to use. Defaults to "dynamic". layering: The layering strategy to use. Defaults to "individual_gates". + lookaheads: The number of lookaheads to be used or None if no lookahead should be used. Defaults to 15. + lookahead_factor: The rate at which the contribution of future layers to the lookahead decreases. Defaults to 0.5. encoding: The encoding to use for the AMO and exactly one constraints. Defaults to "naive". commander_grouping: The grouping strategy to use for the commander and bimander encoding. Defaults to "halves". use_bdd: Whether to use BDDs to limit the search space. Defaults to False. Use with caution. @@ -106,6 +108,7 @@ def compile( # noqa: A001 add_measurements_to_mapped_circuit: Whether to add measurements at the end of the mapped circuit. Defaults to True. verbose: Print more detailed information during the mapping process. Defaults to False. debug: Gather additional information during the mapping process (e.g. number of generated nodes, branching factors, ...). Defaults to False. + visualizer: A SearchVisualizer object to log the search process to. Defaults to None. Returns: The mapped circuit and the mapping results. @@ -140,7 +143,7 @@ def compile( # noqa: A001 config.add_measurements_to_mapped_circuit = add_measurements_to_mapped_circuit config.verbose = verbose config.debug = debug - if visualizer is not None: + if visualizer is not None and visualizer.data_logging_path is not None: config.data_logging_path = visualizer.data_logging_path if lookaheads is None: config.lookaheads = 0 diff --git a/src/mqt/qmap/visualization/__init__.py b/src/mqt/qmap/visualization/__init__.py index 50a647b17..98036d23b 100644 --- a/src/mqt/qmap/visualization/__init__.py +++ b/src/mqt/qmap/visualization/__init__.py @@ -1,9 +1,11 @@ +"""MQT QMAP visualization library. + +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. +""" from __future__ import annotations from mqt.qmap.visualization.search_visualizer import SearchVisualizer -from mqt.qmap.visualization.visualize_search_graph import visualize_search_graph +from mqt.qmap.visualization.visualize_search_graph import SearchNode, visualize_search_graph -__all__ = [ - "visualize_search_graph", - "SearchVisualizer", -] +__all__ = ["visualize_search_graph", "SearchVisualizer", "SearchNode"] diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py index 3bc6d3360..15e5a53e3 100644 --- a/src/mqt/qmap/visualization/search_visualizer.py +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -1,7 +1,13 @@ +"""A class handling data logging for a search process and providing methods to visualize that data.""" from __future__ import annotations from tempfile import TemporaryDirectory -from typing import TYPE_CHECKING, Any, Callable, Literal +from typing import TYPE_CHECKING, Callable, Literal, MutableMapping + +if TYPE_CHECKING: + import types + + from typing_extensions import Self from mqt.qmap.visualization.visualize_search_graph import Position, SearchNode, visualize_search_graph @@ -10,15 +16,37 @@ class SearchVisualizer: + """Handling data logging for a search process and providing methods to visualize that data.""" + def __init__(self, data_logging_path: str | None = None) -> None: + """Handling data logging for a search process and providing methods to visualize that data. + + Args: + data_logging_path: Path to an empty directory, in which the search process should log all data. + Defaults to None, in which case a temporary folder will be created. + """ if data_logging_path is not None: - self.data_logging_path = data_logging_path - self.data_logging_tmp_dir = None + self.data_logging_path: str | None = data_logging_path + self.data_logging_tmp_dir: TemporaryDirectory[str] | None = None else: self.data_logging_tmp_dir = TemporaryDirectory() self.data_logging_path = self.data_logging_tmp_dir.name - def close(self): + def __enter__(self) -> Self: + """Just enables the use of SearchVisualizer in a with statement.""" + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: + """Closes the SearchVisualizer after a with statement.""" + self.close() + + def close(self) -> None: + """Cleans up the data logging directory, if it is was temporarily created.""" if self.data_logging_tmp_dir is not None: self.data_logging_tmp_dir.cleanup() self.data_logging_path = None @@ -26,14 +54,11 @@ def close(self): def visualize_search_graph( self, layer: int | Literal["interactive"] = "interactive", # 'interactive' (slider menu) | index - architecture_node_positions: dict[int, Position] | None = None, - architecture_layout_method: Literal[ - "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" - ] = "sfdp", - search_node_layout_method: Literal[ + architecture_node_positions: MutableMapping[int, Position] | None = None, + architecture_layout: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"] = "sfdp", + search_node_layout: Literal[ "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ] = "walker", - search_node_layout_walker_factor: float = 0.6, search_graph_border: float = 0.05, architecture_border: float = 0.05, swap_arrow_spacing: float = 0.05, @@ -57,7 +82,7 @@ def visualize_search_graph( # 'total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty' | static HTML color (e.g. 'blue' or '#0000FF') | # function that takes a SearchNode and returns a float (e.g. lambda n: n.fixed_cost + n.heuristic_cost) # node fields: {"fixed_cost": float, "heuristic_cost": float, "lookahead_penalty": float, "is_valid_mapping": bool, - # "final": bool, "depth": int, "layout": Tuple[int, ...], "swaps": Tuple[Tuple[int, int], ...]} + # "final": bool, "depth": int, "layout": tuple[int, ...], "swaps": tuple[tuple[int, int], ...]} # or list of the above if 3d graph is used and multiple points per node are defined in search_node_height (lengths need to match) # if in that case no is list is provided all points per node will be the same color prioritize_search_node_color: bool @@ -91,7 +116,7 @@ def visualize_search_graph( show_search_progression: bool = True, search_progression_step: int = 10, search_progression_speed: float = 2, # steps per second - plotly_settings: dict[str, dict[str, Any]] | None = None, + plotly_settings: MutableMapping[str, MutableMapping[str, object]] | None = None, # { # 'layout': settings for plotly.graph_objects.Layout (of subplots figure) # 'arrows': settings for plotly.graph_objects.layout.Annotation @@ -108,6 +133,70 @@ def visualize_search_graph( # 'architecture_yaxis': settings for plotly.graph_objects.layout.YAxis # } ) -> Widget: + """Creates a widget to visualize the search graph. + + Args: + layer (int | Literal["interactive"]): Index of the circuit layer, of which the mapping should be visualized. Defaults to "interactive", in which case a slider menu will be created. + architecture_node_positions (MutableMapping[int, tuple[float, float]] | None): Mapping from physical qubits to (x, y) coordinates. Defaults to None, in which case architecture_layout will be used to generate a layout. + architecture_layout (Literal[ "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ]): The method to use when layouting the qubit connectivity graph. Defaults to "sfdp". + search_node_layout (Literal[ "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ]): The method to use when layouting the search graph. Defaults to "walker". + search_graph_border (float): Size of the border around the search graph. Defaults to 0.05. + architecture_border (float): Size of the border around the qubit connectivity graph. Defaults to 0.05. + swap_arrow_spacing (float): Lateral spacing between arrows indicating swaps on the qubit connectivity graph. Defaults to 0.05. + swap_arrow_offset (float): Offset of heads and shaft of swap arrows from qubits they are pointing to/from. Defaults to 0.05. + use3d (bool): If a 3D graph should be used for the search graph using the z-axis to plot data features. Defaults to True. + projection (Literal["orthographic", "perspective"]): Projection type to use in 3D graphs. Defaults to "perspective". + width (int): Pixel width of the widget. Defaults to 1400. + height (int): Pixel height of the widget. Defaults to 700. + draw_search_edges (bool): If edges between search nodes should be drawn. Defaults to True. + search_edges_width (float): Width of edges between search nodes. Defaults to 0.5. + search_edges_color (str): Color of edges between search nodes (in CSS format, i.e. "#rrggbb", "#rgb", "colorname", etc.). Defaults to "#888". + search_edges_dash (str): Dashing of search edges (in CSS format, i.e. "solid", "dot", "dash", "longdash", etc.). Defaults to "solid". + tapered_search_layer_heights (bool): If search graph tree should progressively reduce the height of each layer. Defaults to True. + show_layout (Literal["hover", "click"] | None): If the current qubit layout should be shown on the qubit connectivity graph, when clicking or hovering on a search node or not at all. Defaults to "hover". + show_swaps (bool): Showing swaps on the connectivity graph. Defaults to True. + show_shared_swaps (bool): Indicate a shared swap by 1 arrow with 2 heads, otherwise 2 arrows in opposite direction are drawn for the 1 shared swap. Defaults to True. + color_valid_mapping (str | None): Color to use for search nodes containing a valid qubit layout (in CSS format). Defaults to "green". + color_final_node (str | None): Color to use for the final solution search node (in CSS format). Defaults to "red". + search_node_color (str | Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by `search_node_color_scale`, or a preset data feature ("total_cost" | "fixed_cost" | "heuristic_cost" | "lookahead_penalty"). In case a 3D search graph is used with multiple point per search node, each point"s color can be controlled individually via a list. Defaults to "total_cost". + prioritize_search_node_color (bool | list[ bool ]): If search_node_color should be prioritized over color_valid_mapping and color_final_node. Defaults to False. + search_node_color_scale (str | list[str]): Color scale to be used for converting float data features to search node colors. (See https://plotly.com/python/builtin-colorscales/ for valid values). Defaults to "YlGnBu". + search_node_invert_color_scale (bool | list[bool]): If the color scale should be inverted. Defaults to True. + search_node_colorbar_title (str | list[str | None] | None): Title(s) to be shown next to the colorbar(s). Defaults to None. + search_node_colorbar_spacing (float): Spacing between multiple colorbars. Defaults to 0.06. + search_node_height (str | Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]): Function mapping a mqt.qmap.visualization.SearchNode to a float value to be used as z-value in 3D search graphs or a preset data feature ("total_cost" | "fixed_cost" | "heuristic_cost" | "lookahead_penalty"). Or a list any of such functions/data features, to draw multiple points per search node. Defaults to "total_cost". + draw_stems (bool): If a vertical stem should be drawn in 3D search graphs to each search node. Defaults to False. + stems_width (float): Width of stems in 3D search graphs. Defaults to 0.7. + stems_color (str): Color of stems in 3D search graphs (in CSS format). Defaults to "#444". + stems_dash (str): Dashing of stems in 3D search graphs (in CSS format). Defaults to "solid". + show_search_progression (bool): If the search progression should be animated. Defaults to True. + search_progression_step (int): Step size (in number of nodes added) of search progression animation. Defaults to 10. + search_progression_speed (float): Speed of the search progression animation. Defaults to 2. + plotly_settings (MutableMapping[str, MutableMapping[str, any]] | None): Direct plotly configuration dictionaries to be passed through. Defaults to None. + ``` + { + "layout": settings for plotly.graph_objects.Layout (of subplots figure) + "arrows": settings for plotly.graph_objects.layout.Annotation + "stats_legend": settings for plotly.graph_objects.layout.Annotation + "search_nodes": settings for plotly.graph_objects.Scatter resp. ...Scatter3d + "search_edges": settings for plotly.graph_objects.Scatter resp. ...Scatter3d + "architecture_nodes": settings for plotly.graph_objects.Scatter + "architecture_edges": settings for plotly.graph_objects.Scatter + "architecture_edge_labels": settings for plotly.graph_objects.Scatter + "search_xaxis": settings for plotly.graph_objects.layout.XAxis resp. ...layout.scene.XAxis + "search_yaxis": settings for plotly.graph_objects.layout.YAxis resp. ...layout.scene.YAxis + "search_zaxis": settings for plotly.graph_objects.layout.scene.ZAxis + "architecture_xaxis": settings for plotly.graph_objects.layout.XAxis + "architecture_yaxis": settings for plotly.graph_objects.layout.YAxis + } + ``` + + Raises: + TypeError: If any of the arguments are invalid. + + Returns: + Widget: An interactive IPython widget to visualize the search graph. + """ if plotly_settings is None: plotly_settings = {} if self.data_logging_path is None: @@ -117,9 +206,8 @@ def visualize_search_graph( data_logging_path=self.data_logging_path, layer=layer, architecture_node_positions=architecture_node_positions, - architecture_layout_method=architecture_layout_method, - search_node_layout_method=search_node_layout_method, - search_node_layout_walker_factor=search_node_layout_walker_factor, + architecture_layout=architecture_layout, + search_node_layout=search_node_layout, search_graph_border=search_graph_border, architecture_border=architecture_border, swap_arrow_spacing=swap_arrow_spacing, diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index 2153f515d..6bc069153 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -1,14 +1,52 @@ +"""Function for visualization of search graphs.""" from __future__ import annotations import json -import os import re from copy import deepcopy from dataclasses import dataclass +from pathlib import Path from random import shuffle -from typing import Any, Callable, List, Literal, Tuple, Union +from typing import TYPE_CHECKING, Callable, Iterable, Literal, MutableMapping, Sequence, Tuple, TypedDict, Union + +if TYPE_CHECKING: + Position = Tuple[float, float] + Colorscale = Union[str, Sequence[str], Sequence[Tuple[float, str]]] + + class _ActiveTraceIndices(TypedDict): + search_edges: list[int] + search_nodes: list[int] + search_node_stems: int + arch_edges: int + arch_edge_labels: int + arch_nodes: int + + _PlotlySubsettings = MutableMapping[str, object] + + class _PlotlySettings(TypedDict): + layout: _PlotlySubsettings + arrows: _PlotlySubsettings + stats_legend: _PlotlySubsettings + search_nodes: _PlotlySubsettings + search_node_stems: _PlotlySubsettings + search_edges: _PlotlySubsettings + architecture_nodes: _PlotlySubsettings + architecture_edges: _PlotlySubsettings + architecture_edge_labels: _PlotlySubsettings + search_xaxis: _PlotlySubsettings + search_yaxis: _PlotlySubsettings + search_zaxis: _PlotlySubsettings + architecture_xaxis: _PlotlySubsettings + architecture_yaxis: _PlotlySubsettings + + class _SwapArrowProps(TypedDict): + color: str + straight: bool + color2: str | None + import networkx as nx +import plotly import plotly.graph_objects as go from _plotly_utils.basevalidators import ColorscaleValidator, ColorValidator from distinctipy import distinctipy @@ -17,9 +55,11 @@ from plotly.subplots import make_subplots from walkerlayout import WalkerLayouting +# TODO: show_swaps not working! + @dataclass -class TwoQbitMultiplicity: +class _TwoQbitMultiplicity: q0: int q1: int forward: int @@ -28,7 +68,9 @@ class TwoQbitMultiplicity: @dataclass class SearchNode: - id: int + """Represents a node in the search graph.""" + + nodeid: int parent: int | None fixed_cost: float heuristic_cost: float @@ -36,74 +78,65 @@ class SearchNode: is_valid_mapping: bool final: bool depth: int - layout: tuple[int, ...] - swaps: tuple[tuple[int, int], ...] + layout: Sequence[int] + swaps: Sequence[tuple[int, int]] - def total_cost(self): + def total_cost(self) -> float: + """Returns the total cost of the node, i.e. fixed cost + heuristic cost + lookahead penalty.""" return self.fixed_cost + self.heuristic_cost + self.lookahead_penalty - def total_fixed_cost(self): + def total_fixed_cost(self) -> float: + """Returns the total fixed cost of the node, i.e. fixed cost + lookahead penalty.""" return self.fixed_cost + self.lookahead_penalty -Position = Tuple[float, float] -Colorscale = Union[str, List[str], List[Tuple[float, str]]] - - -def _is_len_iterable(obj) -> bool: - if isinstance(obj, str): - return False - # this is actually the safest way to do this: - # https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable - try: - iter(obj) - len(obj) - return True - except TypeError: - return False - - -def _is_number(x): - return type(x) is float or type(x) is int +def _is_number(x: object) -> bool: + return isinstance(x, (float, int)) def _remove_first_lines(string: str, n: int) -> str: return string.split("\n", n)[n] -def _get_avg_min_distance(seq): +def _get_avg_min_distance(seq: Iterable[float | int]) -> float: arr = sorted(seq) - sum_dist = 0 + sum_dist = 0.0 for i in range(1, len(arr)): - sum_dist += arr[i] - arr[i - 1] + sum_dist += float(arr[i] - arr[i - 1]) return sum_dist / (len(arr) - 1) -def _reverse_layout(seq): +def _reverse_layout(seq: Sequence[int]) -> Sequence[int]: r = [-1] * len(seq) for i, v in enumerate(seq): r[v] = i return r -def _copy_to_dict(target: dict[Any, Any], source: dict[Any, Any], recursive: bool = True): +def _copy_to_dict( + target: MutableMapping[str, object], source: MutableMapping[str, object], recursive: bool = True +) -> MutableMapping[str, object]: for key, value in source.items(): if recursive and isinstance(value, dict): if key not in target or not isinstance(target[key], dict): target[key] = value else: - _copy_to_dict(target[key], value) + _copy_to_dict(target[key], value) # type: ignore[arg-type] else: target[key] = value return target +class RootNodeNotFoundError(Exception): + """Raised when the root node of a search graph could not be found.""" + + def _parse_search_graph(file_path: str, final_node_id: int) -> tuple[nx.Graph, int]: graph = nx.Graph() root = None - with open(file_path) as file: - for line in file: - line = line.strip().split(";") + with Path(file_path).open() as file: + for linestr in file: + line = linestr.strip().split(";") node = int(line[0]) parent = int(line[1]) data = SearchNode( @@ -118,13 +151,15 @@ def _parse_search_graph(file_path: str, final_node_id: int) -> tuple[nx.Graph, i tuple(int(q) for q in line[7].strip().split(",")), () if len(line[8].strip()) == 0 - else tuple(tuple(int(q) for q in swap.split(" ")) for swap in line[8].strip().split(",")), + else tuple((int(swap.split(" ")[0]), int(swap.split(" ")[1])) for swap in line[8].strip().split(",")), ) graph.add_node(node, data=data) if parent == node: root = node else: graph.add_edge(node, parent) + if root is None: + raise RootNodeNotFoundError return graph, root @@ -133,11 +168,13 @@ def _layout_search_graph( root: int, method: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], tapered_layer_heights: bool, -) -> dict[int, Position]: +) -> MutableMapping[int, Position]: if method == "walker": - pos = WalkerLayouting.layout_networkx(search_graph, root, origin=(0, 0), scalex=60, scaley=-1) + pos: MutableMapping[int, Position] = WalkerLayouting.layout_networkx( + search_graph, root, origin=(0, 0), scalex=60, scaley=-1 + ) else: - pos = graphviz_layout(search_graph, prog=method, root=search_graph.nodes[0]) + pos = graphviz_layout(search_graph, prog=method, root=search_graph.nodes[0]) # t if not tapered_layer_heights: return pos @@ -152,7 +189,7 @@ def _layout_search_graph( if len(layers_x) <= len(search_graph.nodes): return pos - avg_spacing = 0 + avg_spacing = 0.0 for y in layers_x: layer_spacings[y] = _get_avg_min_distance(layers_x[y]) avg_spacing += layer_spacings[y] @@ -168,7 +205,7 @@ def _layout_search_graph( layer_spacings[y] = (before + after) / 2 if layer_spacings[y] == 0: layer_spacings[y] = avg_spacing - current_y = 0 + current_y = 0.0 layers_new_y = {} for old_y in sorted(layers_x.keys()): layers_new_y[old_y] = current_y @@ -182,16 +219,16 @@ def _layout_search_graph( def _prepare_search_graph_scatters( number_of_scatters: int, - color_scale: list[Colorscale], - invert_color_scale: list[bool], - search_node_colorbar_title: list[str | None], + color_scale: Sequence[Colorscale], + invert_color_scale: Sequence[bool], + search_node_colorbar_title: Sequence[str | None], search_node_colorbar_spacing: float, use3d: bool, draw_stems: bool, draw_edges: bool, - plotly_settings: dict[str, dict[str, Any]], + plotly_settings: _PlotlySettings, ) -> tuple[ - list[go.Scatter | go.Scatter3d], list[go.Scatter | go.Scatter3d], go.Scatter3d | None + Sequence[go.Scatter | go.Scatter3d], Sequence[go.Scatter | go.Scatter3d], go.Scatter3d | None ]: # nodes, edges, stems if not use3d: _copy_to_dict( @@ -209,7 +246,7 @@ def _prepare_search_graph_scatters( ps = plotly_settings["search_nodes"] if search_node_colorbar_title[0] is None: ps = deepcopy(ps) - del ps["marker"]["colorbar"] + del ps["marker"]["colorbar"] # type: ignore[attr-defined] node_scatter = go.Scatter(**ps) node_scatter.x = [] node_scatter.y = [] @@ -221,82 +258,81 @@ def _prepare_search_graph_scatters( else: edge_scatters = [] return [node_scatter], edge_scatters, None - else: - node_scatters = [] - n_colorbars = 0 - for i in range(number_of_scatters): - _copy_to_dict( - plotly_settings["search_nodes"], - { - "marker": { - "colorscale": color_scale[i], - "reversescale": invert_color_scale[i], - "colorbar": { - "y": 1 - search_node_colorbar_spacing * n_colorbars, - "title": search_node_colorbar_title[i], - }, - } - }, - ) - ps = plotly_settings["search_nodes"] - if search_node_colorbar_title[i] is None: - ps = deepcopy(ps) - del ps["marker"]["colorbar"] - else: - n_colorbars += 1 - scatter = go.Scatter3d(**ps) + node_scatters = [] + n_colorbars = 0 + for i in range(number_of_scatters): + _copy_to_dict( + plotly_settings["search_nodes"], + { + "marker": { + "colorscale": color_scale[i], + "reversescale": invert_color_scale[i], + "colorbar": { + "y": 1 - search_node_colorbar_spacing * n_colorbars, + "title": search_node_colorbar_title[i], + }, + } + }, + ) + ps = plotly_settings["search_nodes"] + if search_node_colorbar_title[i] is None: + ps = deepcopy(ps) + del ps["marker"]["colorbar"] # type: ignore[attr-defined] + else: + n_colorbars += 1 + scatter = go.Scatter3d(**ps) + scatter.x = [] + scatter.y = [] + scatter.z = [] + node_scatters.append(scatter) + if draw_edges: + edge_scatters = [] + for _ in range(number_of_scatters): + scatter = go.Scatter3d(**plotly_settings["search_edges"]) scatter.x = [] scatter.y = [] scatter.z = [] - node_scatters.append(scatter) - if draw_edges: - edge_scatters = [] - for _ in range(number_of_scatters): - scatter = go.Scatter3d(**plotly_settings["search_edges"]) - scatter.x = [] - scatter.y = [] - scatter.z = [] - edge_scatters.append(scatter) - else: - edge_scatters = [] - stem_scatter = None - if draw_stems: - stem_scatter = go.Scatter3d(**plotly_settings["search_node_stems"]) - stem_scatter.x = [] - stem_scatter.y = [] - stem_scatter.z = [] - return node_scatters, edge_scatters, stem_scatter + edge_scatters.append(scatter) + else: + edge_scatters = [] + stem_scatter = None + if draw_stems: + stem_scatter = go.Scatter3d(**plotly_settings["search_node_stems"]) + stem_scatter.x = [] + stem_scatter.y = [] + stem_scatter.z = [] + return node_scatters, edge_scatters, stem_scatter def _prepare_search_graph_scatter_data( number_of_scatters: int, search_graph: nx.Graph, - search_pos: dict[int, Position], + search_pos: MutableMapping[int, Position], use3d: bool, - search_node_color: list[str | Callable[[SearchNode], float]], + search_node_color: Sequence[str | Callable[[SearchNode], float]], # 'total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty' | static HTML color (e.g. 'blue' or '#0000FF') | # function that takes a node parameter dict and returns a float (e.g. lambda node: node['total_cost'] + node['fixed_cost']) # node parameter dict: {"fixed_cost": float, "heuristic_cost": float, "lookahead_penalty": float, "is_valid_mapping": bool, - # "final": bool, "depth": int, "layout": Tuple[int, ...], "swaps": Tuple[Tuple[int, int], ...]} + # "final": bool, "depth": int, "layout": Sequence[int], "swaps": Sequence[tuple[int, int]]} # or list of the above if 3d graph is used and multiple points per node are defined in search_node_height (lengths need to match) # if in that case no is list is provided all points per node will be the same color - prioritize_search_node_color: list[bool], - search_node_height: list[Callable[[SearchNode], float]], + prioritize_search_node_color: Sequence[bool], + search_node_height: Sequence[Callable[[SearchNode], float]], color_valid_mapping: str | None, # static HTML color (e.g. 'blue' or '#0000FF') color_final_node: str | None, # static HTML color (e.g. 'blue' or '#0000FF') draw_stems: bool, # only applicable for 3D plots draw_edges: bool, ) -> tuple[ - list[float | None], - list[float | None], - tuple[list[float | None], ...], - list[float], - list[float], - tuple[list[float], ...], - list[float | str], - list[float | None], - list[float | None], - list[float | None], + Sequence[float | None], + Sequence[float | None], + Sequence[Sequence[float | None]], + Sequence[float], + Sequence[float], + Sequence[Sequence[float]], + Sequence[Sequence[float | str]], + Sequence[float | None], + Sequence[float | None], + Sequence[float | None], float, float, float, @@ -304,28 +340,28 @@ def _prepare_search_graph_scatter_data( float, float, ]: # edge_x, edge_y, edge_z, node_x, node_y, node_z, node_color, stem_x, stem_y, stem_z, min_x, max_x, min_y, max_y, min_z, max_z - edge_x = [] - edge_y = [] - edge_z = tuple([] for _ in range(number_of_scatters)) - node_x = [] - node_y = [] - node_z = tuple([] for _ in range(number_of_scatters)) - node_color = tuple([] for _ in range(number_of_scatters)) - stem_x = [] - stem_y = [] - stem_z = [] - min_x = None - max_x = None - min_y = None - max_y = None - min_z = None - max_z = None + edge_x: list[float | None] = [] + edge_y: list[float | None] = [] + edge_z: tuple[list[float | None], ...] = tuple([] for _ in range(number_of_scatters)) + node_x: list[float] = [] + node_y: list[float] = [] + node_z: tuple[list[float], ...] = tuple([] for _ in range(number_of_scatters)) + node_color: tuple[list[float | str], ...] = tuple([] for _ in range(number_of_scatters)) + stem_x: list[float | None] = [] + stem_y: list[float | None] = [] + stem_z: list[float | None] = [] + min_x: float | None = None + max_x: float | None = None + min_y: float | None = None + max_y: float | None = None + min_z: float | None = None + max_z: float | None = None for node in search_graph.nodes(): node_params = search_graph.nodes[node]["data"] nx, ny = search_pos[node] - min_nz = 0 - max_nz = 0 + min_nz = 0.0 + max_nz = 0.0 node_x.append(nx) node_y.append(ny) @@ -345,7 +381,7 @@ def _prepare_search_graph_scatter_data( stem_x.append(None) stem_y.append(None) stem_z.append(None) - if min_x is None: + if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: min_x = nx max_x = nx min_y = ny @@ -361,10 +397,11 @@ def _prepare_search_graph_scatter_data( min_z = min(min_z, min_nz) max_z = max(max_z, max_nz) - ncolor = None + ncolor: float | str | None = None if len(search_node_color) == 1: ncolor = search_node_color[0](node_params) if callable(search_node_color[0]) else search_node_color[0] for i in range(number_of_scatters): + curr_color = search_node_color[i] prio_color = ( prioritize_search_node_color[i] if len(prioritize_search_node_color) > 1 @@ -376,10 +413,14 @@ def _prepare_search_graph_scatter_data( node_color[i].append(color_valid_mapping) elif ncolor is not None: node_color[i].append(ncolor) - elif callable(search_node_color[i]): - node_color[i].append(search_node_color[i](node_params)) + elif callable(curr_color): + node_color[i].append(curr_color(node_params)) else: - node_color[i].append(search_node_color[i]) + node_color[i].append(curr_color) + + if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: + msg = "No nodes in search graph." + raise ValueError(msg) if draw_edges: nodes_indices = {} @@ -421,13 +462,13 @@ def _prepare_search_graph_scatter_data( def _draw_search_graph_nodes( - scatters: list[go.Scatter | go.Scatter3d], - x: list[float], - y: list[float], - z: tuple[list[float], ...], - color: list[float | str], + scatters: Sequence[go.Scatter | go.Scatter3d], + x: Sequence[float], + y: Sequence[float], + z: Sequence[Sequence[float]], + color: Sequence[Sequence[float | str]], use3d: bool, -): +) -> None: for i in range(len(scatters)): scatters[i].x = x scatters[i].y = y @@ -436,15 +477,19 @@ def _draw_search_graph_nodes( scatters[i].marker.color = color[i] if len(color) > 1 else color[0] -def _draw_search_graph_stems(scatter: go.Scatter3d, x: list[float], y: list[float], z: list[float]): +def _draw_search_graph_stems(scatter: go.Scatter3d, x: Sequence[float], y: Sequence[float], z: Sequence[float]) -> None: scatter.x = x scatter.y = y scatter.z = z def _draw_search_graph_edges( - scatters: list[go.Scatter | go.Scatter3d], x: list[float], y: list[float], z: tuple[list[float], ...], use3d: bool -): + scatters: Sequence[go.Scatter | go.Scatter3d], + x: Sequence[float], + y: Sequence[float], + z: Sequence[Sequence[float]], + use3d: bool, +) -> None: for i in range(len(scatters)): scatters[i].x = x scatters[i].y = y @@ -454,25 +499,25 @@ def _draw_search_graph_edges( def _parse_arch_graph(file_path: str) -> nx.Graph: arch = None - with open(file_path) as file: + with Path(file_path).open() as file: arch = json.load(file) fidelity = None if "fidelity" in arch: fidelity = arch["fidelity"] - edges = set() - nqbits = 0 + edges: set[tuple[int, int, float]] = set() + nqbits: int = 0 for q0, q1 in arch["coupling_map"]: - edge = (q0, q1) if q0 < q1 else (q1, q0) + edge: tuple[int, int] = (q0, q1) if q0 < q1 else (q1, q0) if fidelity is not None: - edge += (fidelity["swap_fidelity_costs"][q0][q1],) + cost_edge: tuple[int, int, float] = (*edge, fidelity["swap_fidelity_costs"][q0][q1]) else: - edge += (float(30),) + cost_edge = (*edge, float(30)) # TODO: this is the cost of 1 swap for the non-noise-aware heuristic # mapper; depending on directionality this might be different; once # more dynamic swap cost system in Architecture.cpp is implemented # replace with dynamic cost lookup - edges.add(edge) + edges.add(cost_edge) nqbits = max(nqbits, q0, q1) nqbits += 1 @@ -483,13 +528,13 @@ def _parse_arch_graph(file_path: str) -> nx.Graph: def _draw_architecture_edges( - arch_graph: nx.Graph, arch_pos: dict[int, Position], plotly_settings: dict[str, dict[str, Any]] + arch_graph: nx.Graph, arch_pos: MutableMapping[int, Position], plotly_settings: _PlotlySettings ) -> tuple[go.Scatter, go.Scatter]: - edge_x = [] - edge_y = [] - edge_label_x = [] - edge_label_y = [] - edge_label_text = [] + edge_x: list[float | None] = [] + edge_y: list[float | None] = [] + edge_label_x: list[float] = [] + edge_label_y: list[float] = [] + edge_label_text: list[str] = [] for n1, n2 in arch_graph.edges(): x0, y0 = arch_pos[n1] x1, y1 = arch_pos[n2] @@ -517,12 +562,12 @@ def _draw_architecture_edges( def _draw_architecture_nodes( scatter: go.Scatter, - arch_pos: dict[int, Position], - considered_qubit_colors: dict[int, str], - initial_qubit_position: list[int], - single_qubit_multiplicity: list[int], - two_qubit_individual_multiplicity: list[int], -): + arch_pos: MutableMapping[int, Position], + considered_qubit_colors: MutableMapping[int, str], + initial_qubit_position: Sequence[int], + single_qubit_multiplicity: Sequence[int], + two_qubit_individual_multiplicity: Sequence[int], +) -> None: x = [] y = [] color = [] @@ -547,21 +592,21 @@ def _draw_architecture_nodes( def _draw_swap_arrows( - arch_pos: dict[int, Position], + arch_pos: MutableMapping[int, Position], initial_layout: list[int], - swaps: tuple[tuple[int, int], ...], - considered_qubit_colors: dict[int, str], + swaps: Sequence[tuple[int, int]], + considered_qubit_colors: MutableMapping[int, str], arrow_offset: float, arrow_spacing_x: float, arrow_spacing_y: float, shared_swaps: bool, - plotly_settings: dict[str, dict[str, Any]], + plotly_settings: _PlotlySettings, ) -> list[go.layout.Annotation]: layout = initial_layout.copy() - swap_arrow_props = ( - {} - ) # (q0, q1) -> [{color: str, straight: bool, color2: Optional[str]}, ...] \\ q0 < q1, color2 is color at shaft side of arrow for a shared swap + swap_arrow_props: dict[tuple[int, int], list[_SwapArrowProps]] = {} + # (q0, q1) -> [{color: str, straight: bool, color2: Optional[str]}, ...] + # q0 < q1, color2 is color at shaft side of arrow for a shared swap for sw in swaps: edge = (sw[0], sw[1]) if sw[0] < sw[1] else (sw[1], sw[0]) if edge not in swap_arrow_props: @@ -657,31 +702,31 @@ def _visualize_layout( fig: go.Figure, search_node: SearchNode, arch_node_trace: go.Scatter, - arch_node_positions: dict[int, Position], + arch_node_positions: MutableMapping[int, Position], initial_layout: list[int], - considered_qubit_colors: dict[int, str], + considered_qubit_colors: MutableMapping[int, str], swap_arrow_offset: float, arch_x_arrow_spacing: float, arch_y_arrow_spacing: float, show_shared_swaps: bool, layout_node_trace_index: int, # current_node_layout_visualized search_node_trace: go.Scatter | None, - plotly_settings: dict[str, dict[str, Any]], -): + plotly_settings: _PlotlySettings, +) -> None: layout = search_node.layout swaps = search_node.swaps _copy_to_dict( plotly_settings["stats_legend"], { - "text": f"Node: {search_node.id}
" - + f"Cost: {search_node.total_cost():.3f} = {search_node.fixed_cost:.3f} + " - + f"{search_node.heuristic_cost:.3f} + {search_node.lookahead_penalty:.3f} (fixed + heuristic + lookahead)
" - + f"Depth: {search_node.depth}
" - + f'Valid / Final: {"yes" if search_node.is_valid_mapping else "no"} / {"yes" if search_node.final else "no"}', + "text": f"Node: {search_node.nodeid}
" + f"Cost: {search_node.total_cost():.3f} = {search_node.fixed_cost:.3f} + " + f"{search_node.heuristic_cost:.3f} + {search_node.lookahead_penalty:.3f} (fixed + heuristic + lookahead)
" + f"Depth: {search_node.depth}
" + f'Valid / Final: {"yes" if search_node.is_valid_mapping else "no"} / {"yes" if search_node.final else "no"}', }, ) stats = go.layout.Annotation(**plotly_settings["stats_legend"]) - annotations = [] + annotations: list[go.layout.Annotation] = [] if len(swaps) > 0: annotations = _draw_swap_arrows( arch_node_positions, @@ -720,13 +765,13 @@ def _visualize_layout( def _load_layer_data( data_logging_path: str, layer: int, - layout_method: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], + layout: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], tapered_layer_heights: bool, number_of_node_traces: int, use3d: bool, - node_color: list[str | Callable[[SearchNode], float]], - prioritize_node_color: list[bool], - node_height: list[Callable[[SearchNode], float]], + node_color: Sequence[str | Callable[[SearchNode], float]], + prioritize_node_color: Sequence[bool], + node_height: Sequence[Callable[[SearchNode], float]], color_valid_mapping: str | None, color_final_node: str | None, draw_stems: bool, @@ -735,21 +780,21 @@ def _load_layer_data( nx.Graph, # search_graph list[int], # initial_layout list[int], # initial_qbit_positions - dict[int, str], # considered_qubit_colors - list[int], # single_qbit_multiplicities - list[TwoQbitMultiplicity], # two_qbit_multiplicities - list[int], # individual_two_qbit_multiplicities + MutableMapping[int, str], # considered_qubit_colors + Sequence[int], # single_qbit_multiplicities + Sequence[_TwoQbitMultiplicity], # two_qbit_multiplicities + Sequence[int], # individual_two_qbit_multiplicities int, # final_node_id - list[float], # search_node_scatter_data_x - list[float], # search_node_scatter_data_y - list[list[float]], # search_node_scatter_data_z - list[list[float | str]], # search_node_scatter_data_color - list[float], # search_node_stem_scatter_data_x - list[float], # search_node_stem_scatter_data_y - list[float], # search_node_stem_scatter_data_z - list[float], # search_edge_scatter_data_x - list[float], # search_edge_scatter_data_y - list[list[float]], # search_edge_scatter_data_z + Sequence[float], # search_node_scatter_data_x + Sequence[float], # search_node_scatter_data_y + Sequence[Sequence[float]], # search_node_scatter_data_z + Sequence[Sequence[float | str]], # search_node_scatter_data_color + Sequence[float], # search_node_stem_scatter_data_x + Sequence[float], # search_node_stem_scatter_data_y + Sequence[float], # search_node_stem_scatter_data_z + Sequence[float], # search_edge_scatter_data_x + Sequence[float], # search_edge_scatter_data_y + Sequence[Sequence[float]], # search_edge_scatter_data_z float, # search_min_x float, # search_max_x float, # search_min_y @@ -757,29 +802,29 @@ def _load_layer_data( float, # search_min_z float, # search_max_z ]: - if not os.path.exists(f"{data_logging_path}layer_{layer}.json"): + if not Path(f"{data_logging_path}layer_{layer}.json").exists(): msg = f"No data at {data_logging_path}layer_{layer}.json" raise FileNotFoundError(msg) - if not os.path.exists(f"{data_logging_path}nodes_layer_{layer}.csv"): + if not Path(f"{data_logging_path}nodes_layer_{layer}.csv").exists(): msg = f"No data at {data_logging_path}nodes_layer_{layer}.csv" raise FileNotFoundError(msg) circuit_layer = None - with open(f"{data_logging_path}layer_{layer}.json") as circuit_layer_file: + with Path(f"{data_logging_path}layer_{layer}.json").open() as circuit_layer_file: circuit_layer = json.load(circuit_layer_file) single_q_mult = circuit_layer["single_qubit_multiplicity"] two_q_mult_raw = circuit_layer["two_qubit_multiplicity"] - two_q_mult: list[TwoQbitMultiplicity] = [] - for mult in two_q_mult_raw: - two_q_mult.append(TwoQbitMultiplicity(mult["q1"], mult["q2"], mult["backward"], mult["forward"])) + two_q_mult = [ + _TwoQbitMultiplicity(mult["q1"], mult["q2"], mult["backward"], mult["forward"]) for mult in two_q_mult_raw + ] initial_layout = circuit_layer["initial_layout"] initial_positions = _reverse_layout(initial_layout) final_node_id = circuit_layer["final_node_id"] graph, graph_root = _parse_search_graph(f"{data_logging_path}nodes_layer_{layer}.csv", final_node_id) - pos = _layout_search_graph(graph, graph_root, layout_method, tapered_layer_heights) + pos = _layout_search_graph(graph, graph_root, layout, tapered_layer_heights) ( edge_x, @@ -812,27 +857,28 @@ def _load_layer_data( draw_edges, ) - considered_qubit_colors = {} + considered_qubit_color_groups: dict[int, int] = {} considered_qubit_ngroups = 0 two_q_mult_individual = [0] * len(single_q_mult) for mult in two_q_mult: two_q_mult_individual[mult.q0] += mult.backward + mult.forward two_q_mult_individual[mult.q1] += mult.backward + mult.forward - considered_qubit_colors[mult.q0] = considered_qubit_ngroups - considered_qubit_colors[mult.q1] = considered_qubit_ngroups + considered_qubit_color_groups[mult.q0] = considered_qubit_ngroups + considered_qubit_color_groups[mult.q1] = considered_qubit_ngroups considered_qubit_ngroups += 1 for i, q in enumerate(single_q_mult): - if q != 0 and i not in considered_qubit_colors: - considered_qubit_colors[i] = considered_qubit_ngroups + if q != 0 and i not in considered_qubit_color_groups: + considered_qubit_color_groups[i] = considered_qubit_ngroups considered_qubit_ngroups += 1 considered_qubits_color_codes = [ distinctipy.get_hex(c) for c in distinctipy.get_colors(max(10, considered_qubit_ngroups)) ] shuffle(considered_qubits_color_codes) + considered_qubit_colors: dict[int, str] = {} for q in considered_qubit_colors: - considered_qubit_colors[q] = considered_qubits_color_codes[considered_qubit_colors[q]] + considered_qubit_colors[q] = considered_qubits_color_codes[considered_qubit_color_groups[q]] - return ( + return ( # type: ignore[return-value] graph, initial_layout, initial_positions, @@ -880,22 +926,20 @@ def _lookahead_penalty_lambda(n: SearchNode) -> float: return n.lookahead_penalty +_cost_string_lambdas = { + "total_cost": _total_cost_lambda, + "total_fixed_cost": _total_fixed_cost_lambda, + "fixed_cost": _fixed_cost_lambda, + "heuristic_cost": _heuristic_cost_lambda, + "lookahead_penalty": _lookahead_penalty_lambda, +} + + def _cost_string_to_lambda(cost_string: str) -> Callable[[SearchNode], float] | None: - if cost_string == "total_cost": - return _total_cost_lambda - elif cost_string == "total_fixed_cost": - return _total_fixed_cost_lambda - elif cost_string == "fixed_cost": - return _fixed_cost_lambda - elif cost_string == "heuristic_cost": - return _heuristic_cost_lambda - elif cost_string == "lookahead_penalty": - return _lookahead_penalty_lambda - else: - return None + return _cost_string_lambdas.get(cost_string, None) -default_plotly_settings = { +default_plotly_settings: _PlotlySettings = { "layout": { "autosize": False, "showlegend": False, @@ -957,9 +1001,9 @@ def _cost_string_to_lambda(cost_string: str) -> Callable[[SearchNode], float] | def _visualize_search_graph_check_parameters( data_logging_path: str, layer: int | Literal["interactive"], - architecture_node_positions: dict[int, Position] | None, - architecture_layout_method: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], - search_node_layout_method: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], + architecture_node_positions: MutableMapping[int, Position] | None, + architecture_layout: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], + search_node_layout: Literal["walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"], search_graph_border: float, architecture_border: float, swap_arrow_spacing: float, @@ -978,13 +1022,13 @@ def _visualize_search_graph_check_parameters( show_shared_swaps: bool, color_valid_mapping: str | None, color_final_node: str | None, - search_node_color: str | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]), - prioritize_search_node_color: bool | list[bool], - search_node_color_scale: Colorscale | list[Colorscale], - search_node_invert_color_scale: bool | list[bool], - search_node_colorbar_title: str | list[str | None] | None, + search_node_color: str | (Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]), + prioritize_search_node_color: bool | Sequence[bool], + search_node_color_scale: Colorscale | Sequence[Colorscale], + search_node_invert_color_scale: bool | Sequence[bool], + search_node_colorbar_title: str | Sequence[str | None] | None, search_node_colorbar_spacing: float, - search_node_height: str | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]), + search_node_height: str | (Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]), draw_stems: bool, stems_width: float, stems_color: str, @@ -992,56 +1036,56 @@ def _visualize_search_graph_check_parameters( show_search_progression: bool, search_progression_step: int, search_progression_speed: float, - plotly_settings: dict[str, dict[str, Any]], + plotly_settings: MutableMapping[str, MutableMapping[str, object]], ) -> tuple[ str, # data_logging_path bool, # hide_layout bool, # draw_stems int, # number_of_node_traces - list[Callable[[SearchNode], float]], # search_node_height - list[str | Callable[[SearchNode], float]], # search_node_color - list[Colorscale], # search_node_color_scale - list[bool], # search_node_invert_color_scale - list[bool], # prioritize_search_node_color - list[str], # search_node_colorbar_title - dict[str, dict[str, Any]], # plotly_settings + Sequence[Callable[[SearchNode], float]], # search_node_height + Sequence[str | Callable[[SearchNode], float]], # search_node_color + Sequence[Colorscale], # search_node_color_scale + Sequence[bool], # search_node_invert_color_scale + Sequence[bool], # prioritize_search_node_color + Sequence[str], # search_node_colorbar_title + _PlotlySettings, # plotly_settings ]: - if type(data_logging_path) is not str: - msg = "data_logging_path must be a string" - raise ValueError(msg) + if not isinstance(data_logging_path, str): + msg = "data_logging_path must be a string" # type: ignore[unreachable] + raise TypeError(msg) if data_logging_path[-1] != "/": data_logging_path += "/" - if not os.path.exists(data_logging_path): + if not Path(data_logging_path).exists(): msg = f"Path {data_logging_path} does not exist." raise FileNotFoundError(msg) - if type(layer) is not int and layer != "interactive": - msg = 'layer must be an integer or string literal "interactive"' - raise ValueError(msg) + if not isinstance(layer, int) and layer != "interactive": + msg = 'layer must be an integer or string literal "interactive"' # type: ignore[unreachable] + raise TypeError(msg) if architecture_node_positions is not None: - if type(architecture_node_positions) is not dict: + if not isinstance(architecture_node_positions, dict): msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" - raise ValueError(msg) + raise TypeError(msg) for i in architecture_node_positions: - if type(i) is not int: + if not isinstance(i, int): msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" - raise ValueError(msg) - if type(architecture_node_positions[i]) is not tuple: + raise TypeError(msg) + if not isinstance(architecture_node_positions[i], tuple): msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" - raise ValueError(msg) + raise TypeError(msg) if len(architecture_node_positions[i]) != 2: msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" - raise ValueError(msg) + raise TypeError(msg) if not _is_number(architecture_node_positions[i][0]) or not _is_number(architecture_node_positions[i][1]): msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" - raise ValueError(msg) + raise TypeError(msg) - if architecture_layout_method not in ["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"]: - msg = 'architecture_layout_method must be one of "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"' - raise ValueError(msg) + if architecture_layout not in ["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"]: + msg = 'architecture_layout must be one of "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"' + raise TypeError(msg) - if search_node_layout_method not in [ + if search_node_layout not in [ "walker", "dot", "neato", @@ -1052,82 +1096,83 @@ def _visualize_search_graph_check_parameters( "osage", "patchwork", ]: - msg = 'search_node_layout_method must be one of "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"' - raise ValueError(msg) + msg = 'search_node_layout must be one of "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"' + raise TypeError(msg) if not _is_number(search_graph_border) or search_graph_border < 0: msg = "search_graph_border must be a non-negative float" - raise ValueError(msg) + raise TypeError(msg) if not _is_number(architecture_border) or architecture_border < 0: msg = "architecture_border must be a non-negative float" - raise ValueError(msg) + raise TypeError(msg) if not _is_number(swap_arrow_spacing) or swap_arrow_spacing < 0: msg = "swap_arrow_spacing must be a non-negative float" - raise ValueError(msg) + raise TypeError(msg) if not _is_number(swap_arrow_offset) or swap_arrow_offset < 0 or swap_arrow_offset >= 0.5: msg = "swap_arrow_offset must be a float between 0 and 0.5" - raise ValueError(msg) + raise TypeError(msg) - if type(use3d) is not bool: - msg = "use3d must be a boolean" - raise ValueError(msg) + if not isinstance(use3d, bool): + msg = "use3d must be a boolean" # type: ignore[unreachable] + raise TypeError(msg) if projection != "orthographic" and projection != "perspective": - msg = 'projection must be either "orthographic" or "perspective"' - raise ValueError(msg) + msg = 'projection must be either "orthographic" or "perspective"' # type: ignore[unreachable] + raise TypeError(msg) - if type(width) is not int or width < 1: + if not isinstance(width, int) or width < 1: # type: ignore[redundant-expr] msg = "width must be a positive integer" - raise ValueError(msg) + raise TypeError(msg) - if type(height) is not int or height < 1: + if not isinstance(height, int) or height < 1: # type: ignore[redundant-expr] msg = "height must be a positive integer" - raise ValueError(msg) + raise TypeError(msg) - if type(draw_search_edges) is not bool: - msg = "draw_search_edges must be a boolean" - raise ValueError(msg) + if not isinstance(draw_search_edges, bool): + msg = "draw_search_edges must be a boolean" # type: ignore[unreachable] + raise TypeError(msg) - if type(search_edges_width) is not float or search_edges_width <= 0: + if not isinstance(search_edges_width, float) or search_edges_width <= 0: # type: ignore[redundant-expr] msg = "search_edges_width must be a positive float" - raise ValueError(msg) + raise TypeError(msg) if ColorValidator.perform_validate_coerce(search_edges_color, allow_number=False) is None: - raise ValueError(ColorValidator("search_edges_color", "visualize_search_graph").description()) + raise TypeError(ColorValidator("search_edges_color", "visualize_search_graph").description()) if search_edges_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not re.match( r"^(\d+(px|%)?(\s*\d+(px|%)?)*)$", search_edges_dash ): - raise ValueError( + msg = ( 'search_edges_dash must be one of "solid", "dot", "dash", "longdash", "dashdot", "longdashdot" or a string containing a dash length list in ' - + r'pixels or percentages (e.g. "5px 10px 2px 2px", "5, 10, 2, 2", "10\% 20\% 40\%")' + r'pixels or percentages (e.g. "5px 10px 2px 2px", "5, 10, 2, 2", "10\% 20\% 40\%")' ) + raise TypeError(msg) - if type(tapered_search_layer_heights) is not bool: - msg = "tapered_search_layer_heights must be a boolean" - raise ValueError(msg) + if not isinstance(tapered_search_layer_heights, bool): + msg = "tapered_search_layer_heights must be a boolean" # type: ignore[unreachable] + raise TypeError(msg) if show_layout not in ["hover", "click"] and show_layout is not None: msg = 'show_layout must be one of "hover", "click" or None' - raise ValueError(msg) + raise TypeError(msg) hide_layout = show_layout is None - if type(show_swaps) is not bool: - msg = "show_swaps must be a boolean" - raise ValueError(msg) + if not isinstance(show_swaps, bool): + msg = "show_swaps must be a boolean" # type: ignore[unreachable] + raise TypeError(msg) - if type(show_shared_swaps) is not bool: - msg = "show_shared_swaps must be a boolean" - raise ValueError(msg) + if not isinstance(show_shared_swaps, bool): + msg = "show_shared_swaps must be a boolean" # type: ignore[unreachable] + raise TypeError(msg) if ( color_valid_mapping is not None and ColorValidator.perform_validate_coerce(color_valid_mapping, allow_number=False) is None ): - raise ValueError( + raise TypeError( "color_valid_mapping must be None or a color specified as:\n" + _remove_first_lines(ColorValidator("color_valid_mapping", "visualize_search_graph").description(), 1) ) @@ -1136,45 +1181,46 @@ def _visualize_search_graph_check_parameters( color_final_node is not None and ColorValidator.perform_validate_coerce(color_final_node, allow_number=False) is None ): - raise ValueError( + raise TypeError( "color_final_node must be None or a color specified as:\n" + _remove_first_lines(ColorValidator("color_final_node", "visualize_search_graph").description(), 1) ) - if type(draw_stems) is not bool: - msg = "draw_stems must be a boolean" - raise ValueError(msg) + if not isinstance(draw_stems, bool): + msg = "draw_stems must be a boolean" # type: ignore[unreachable] + raise TypeError(msg) - if type(stems_width) is not float or stems_width <= 0: + if not isinstance(stems_width, float) or stems_width <= 0: # type: ignore[redundant-expr] msg = "stems_width must be a positive float" - raise ValueError(msg) + raise TypeError(msg) if ColorValidator.perform_validate_coerce(stems_color, allow_number=False) is None: - raise ValueError(ColorValidator("stems_color", "visualize_search_graph").description()) + raise TypeError(ColorValidator("stems_color", "visualize_search_graph").description()) if stems_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not re.match( r"^(\d+(px|%)?(\s*\d+(px|%)?)*)$", stems_dash ): - raise ValueError( + msg = ( 'stems_dash must be one of "solid", "dot", "dash", "longdash", "dashdot", "longdashdot" or a string containing a dash length list in ' - + r'pixels or percentages (e.g. "5px 10px 2px 2px", "5, 10, 2, 2", "10\% 20\% 40\%")' + r'pixels or percentages (e.g. "5px 10px 2px 2px", "5, 10, 2, 2", "10\% 20\% 40\%")' ) + raise TypeError(msg) - if type(show_search_progression) is not bool: - msg = "show_search_progression must be a boolean" - raise ValueError(msg) + if not isinstance(show_search_progression, bool): + msg = "show_search_progression must be a boolean" # type: ignore[unreachable] + raise TypeError(msg) - if type(search_progression_step) is not int or search_progression_step < 1: + if not isinstance(search_progression_step, int) or search_progression_step < 1: # type: ignore[redundant-expr] msg = "search_porgression_step must be a positive integer" - raise ValueError(msg) + raise TypeError(msg) if not _is_number(search_progression_speed) or search_progression_speed <= 0: msg = "search_progression_speed must be a positive float" - raise ValueError(msg) + raise TypeError(msg) - if type(plotly_settings) is not dict and any( + if not isinstance(plotly_settings, dict) or any( ( - type(plotly_settings[key]) is not dict + not isinstance(plotly_settings[key], dict) or key not in [ "layout", @@ -1194,24 +1240,25 @@ def _visualize_search_graph_check_parameters( ) for key in plotly_settings ): - raise ValueError( + msg = ( "plotly_settings must be a dict with any of these entries:" - + "{\n" - + " 'layout': settings for plotly.graph_objects.Layout (of subplots figure)\n" - + " 'arrows': settings for plotly.graph_objects.layout.Annotation\n" - + " 'stats_legend': settings for plotly.graph_objects.layout.Annotation\n" - + " 'search_nodes': settings for plotly.graph_objects.Scatter resp. ...Scatter3d\n" - + " 'search_edges': settings for plotly.graph_objects.Scatter resp. ...Scatter3d\n" - + " 'architecture_nodes': settings for plotly.graph_objects.Scatter\n" - + " 'architecture_edges': settings for plotly.graph_objects.Scatter\n" - + " 'architecture_edge_labels': settings for plotly.graph_objects.Scatter\n" - + " 'search_xaxis': settings for plotly.graph_objects.layout.XAxis resp. ...layout.scene.XAxis\n" - + " 'search_yaxis': settings for plotly.graph_objects.layout.YAxis resp. ...layout.scene.YAxis\n" - + " 'search_zaxis': settings for plotly.graph_objects.layout.scene.ZAxis\n" - + " 'architecture_xaxis': settings for plotly.graph_objects.layout.XAxis\n" - + " 'architecture_yaxis': settings for plotly.graph_objects.layout.YAxis\n" - + "}" + "{\n" + " 'layout': settings for plotly.graph_objects.Layout (of subplots figure)\n" + " 'arrows': settings for plotly.graph_objects.layout.Annotation\n" + " 'stats_legend': settings for plotly.graph_objects.layout.Annotation\n" + " 'search_nodes': settings for plotly.graph_objects.Scatter resp. ...Scatter3d\n" + " 'search_edges': settings for plotly.graph_objects.Scatter resp. ...Scatter3d\n" + " 'architecture_nodes': settings for plotly.graph_objects.Scatter\n" + " 'architecture_edges': settings for plotly.graph_objects.Scatter\n" + " 'architecture_edge_labels': settings for plotly.graph_objects.Scatter\n" + " 'search_xaxis': settings for plotly.graph_objects.layout.XAxis resp. ...layout.scene.XAxis\n" + " 'search_yaxis': settings for plotly.graph_objects.layout.YAxis resp. ...layout.scene.YAxis\n" + " 'search_zaxis': settings for plotly.graph_objects.layout.scene.ZAxis\n" + " 'architecture_xaxis': settings for plotly.graph_objects.layout.XAxis\n" + " 'architecture_yaxis': settings for plotly.graph_objects.layout.YAxis\n" + "}" ) + raise TypeError(msg) plotly_set = deepcopy(default_plotly_settings) plotly_set["layout"]["width"] = width @@ -1234,9 +1281,13 @@ def _visualize_search_graph_check_parameters( plotly_set["arrows"]["yref"] = "y2" plotly_set["arrows"]["axref"] = "x2" plotly_set["arrows"]["ayref"] = "y2" - _copy_to_dict(plotly_set, plotly_settings) + _copy_to_dict(plotly_set, plotly_settings) # type: ignore[arg-type] - number_of_node_traces = len(search_node_height) if use3d and _is_len_iterable(search_node_height) else 1 + number_of_node_traces = ( + len(search_node_height) + if use3d and isinstance(search_node_height, Sequence) and not isinstance(search_node_height, str) + else 1 + ) if ( not _is_number(search_node_colorbar_spacing) @@ -1244,29 +1295,30 @@ def _visualize_search_graph_check_parameters( or search_node_colorbar_spacing >= 1 ): msg = "search_node_colorbar_spacing must be a float between 0 and 1" - raise ValueError(msg) + raise TypeError(msg) if search_node_colorbar_title is None: search_node_colorbar_title = [None] * number_of_node_traces - if _is_len_iterable(search_node_colorbar_title): + if isinstance(search_node_colorbar_title, Sequence) and not isinstance(search_node_colorbar_title, str): # type: ignore[redundant-expr] + search_node_colorbar_title = list(search_node_colorbar_title) if len(search_node_colorbar_title) > 1 and not use3d: msg = "search_node_colorbar_title can only be a list in a 3D plot." - raise ValueError(msg) + raise TypeError(msg) if len(search_node_colorbar_title) != number_of_node_traces: msg = f"Length of search_node_colorbar_title ({len(search_node_colorbar_title)}) does not match length of search_node_height ({number_of_node_traces})." - raise ValueError(msg) + raise TypeError(msg) untitled = 1 for i, title in enumerate(search_node_colorbar_title): if title is None: color = None - if _is_len_iterable(search_node_color): + if isinstance(search_node_color, Sequence) and not isinstance(search_node_color, str): if len(search_node_color) == len(search_node_colorbar_title): color = search_node_color[i] else: color = search_node_color[0] else: color = search_node_color - if type(color) is not str: + if not isinstance(color, str): search_node_colorbar_title[i] = f"Untitled{untitled}" untitled += 1 elif color == "total_cost": @@ -1281,151 +1333,158 @@ def _visualize_search_graph_check_parameters( search_node_colorbar_title[i] = "Lookahead penalty" else: search_node_colorbar_title[i] = color - elif type(title) is not str: - msg = "search_node_colorbar_title must be None, a string, or list of strings and None." - raise ValueError(msg) - elif type(search_node_colorbar_title) is str: + elif not isinstance(title, str): + msg = "search_node_colorbar_title must be None, a string, or list of strings and None." # type: ignore[unreachable] + raise TypeError(msg) + elif isinstance(search_node_colorbar_title, str): search_node_colorbar_title = [search_node_colorbar_title] * number_of_node_traces else: - msg = "search_node_colorbar_title must be None, a string, or list of strings and None." - raise ValueError(msg) + msg = "search_node_colorbar_title must be None, a string, or list of strings and None." # type: ignore[unreachable] + raise TypeError(msg) - if _is_len_iterable(search_node_color_scale): + if isinstance(search_node_color_scale, Sequence) and not isinstance(search_node_color_scale, str): # type: ignore[redundant-expr] if len(search_node_color_scale) > 1 and not use3d: msg = "search_node_color_scale can only be a list in a 3D plot." - raise ValueError(msg) + raise TypeError(msg) if len(search_node_color_scale) != number_of_node_traces: msg = f"Length of search_node_color_scale ({len(search_node_color_scale)}) does not match length of search_node_height ({number_of_node_traces})." - raise ValueError(msg) + raise TypeError(msg) cs_validator = ColorscaleValidator("search_node_color_scale", "visualize_search_graph") - for cs in search_node_color_scale: - try: + try: + for cs in search_node_color_scale: cs_validator.validate_coerce(cs) - except: - raise ValueError( - "search_node_color_scale must be a list of colorscales or a colorscale, specified as:\n" - + _remove_first_lines(cs_validator.description(), 1) - ) + except ValueError as err: + msg = ( + "search_node_color_scale must be a list of colorscales or a colorscale, specified as:\n" + + _remove_first_lines(cs_validator.description(), 1) + ) + raise TypeError(msg) from err else: cs_validator = ColorscaleValidator("search_node_color_scale", "visualize_search_graph") try: cs_validator.validate_coerce(search_node_color_scale) - except: - raise ValueError( + except ValueError as err: + msg = ( "search_node_color_scale must be a list of colorscales or a colorscale, specified as:\n" + _remove_first_lines(cs_validator.description(), 1) ) + raise TypeError(msg) from err search_node_color_scale = [search_node_color_scale] * number_of_node_traces - if _is_len_iterable(search_node_invert_color_scale): + if isinstance(search_node_invert_color_scale, Sequence) and not isinstance(search_node_invert_color_scale, str): # type: ignore[unreachable] if len(search_node_invert_color_scale) > 1 and not use3d: msg = "search_node_invert_color_scale can only be a list in a 3D plot." - raise ValueError(msg) + raise TypeError(msg) if len(search_node_invert_color_scale) != number_of_node_traces: msg = f"Length of search_node_invert_color_scale ({len(search_node_invert_color_scale)}) does not match length of search_node_height ({number_of_node_traces})." - raise ValueError(msg) - for i, invert in enumerate(search_node_invert_color_scale): - if type(invert) is not bool: - msg = "search_node_invert_color_scale must be a boolean or list of booleans." - raise ValueError(msg) - elif type(search_node_invert_color_scale) is not bool: - msg = "search_node_invert_color_scale must be a boolean or list of booleans." - raise ValueError(msg) + raise TypeError(msg) + for invert in search_node_invert_color_scale: + if not isinstance(invert, bool): + msg = "search_node_invert_color_scale must be a boolean or list of booleans." # type: ignore[unreachable] + raise TypeError(msg) + elif not isinstance(search_node_invert_color_scale, bool): + msg = "search_node_invert_color_scale must be a boolean or list of booleans." # type: ignore[unreachable] + raise TypeError(msg) else: search_node_invert_color_scale = [search_node_invert_color_scale] * number_of_node_traces - if _is_len_iterable(prioritize_search_node_color): + if isinstance(prioritize_search_node_color, Sequence) and not isinstance(prioritize_search_node_color, str): # type: ignore[unreachable] if len(prioritize_search_node_color) > 1 and not use3d: msg = "prioritize_search_node_color can only be a list in a 3D plot." - raise ValueError(msg) + raise TypeError(msg) if len(prioritize_search_node_color) != number_of_node_traces: msg = f"Length of prioritize_search_node_color ({len(prioritize_search_node_color)}) does not match length of search_node_height ({number_of_node_traces})." - raise ValueError(msg) - for i, invert in enumerate(prioritize_search_node_color): - if type(invert) is not bool: - msg = "prioritize_search_node_color must be a boolean or list of booleans." - raise ValueError(msg) - elif type(prioritize_search_node_color) is not bool: - msg = "prioritize_search_node_color must be a boolean or list of booleans." - raise ValueError(msg) + raise TypeError(msg) + for prioritize in prioritize_search_node_color: + if not isinstance(prioritize, bool): + msg = "prioritize_search_node_color must be a boolean or list of booleans." # type: ignore[unreachable] + raise TypeError(msg) + elif not isinstance(prioritize_search_node_color, bool): + msg = "prioritize_search_node_color must be a boolean or list of booleans." # type: ignore[unreachable] + raise TypeError(msg) else: prioritize_search_node_color = [prioritize_search_node_color] * number_of_node_traces - if _is_len_iterable(search_node_color): + if isinstance(search_node_color, Sequence) and not isinstance(search_node_color, str): + search_node_color = list(search_node_color) if len(search_node_color) > 1 and not use3d: msg = "search_node_color can only be a list in a 3D plot." - raise ValueError(msg) + raise TypeError(msg) if len(search_node_color) != number_of_node_traces: msg = f"Length of search_node_color ({len(search_node_color)}) does not match length of search_node_height ({number_of_node_traces})." - raise ValueError(msg) + raise TypeError(msg) for i, c in enumerate(search_node_color): if isinstance(c, str): - l = _cost_string_to_lambda(c) - if l is not None: - search_node_color[i] = l + cost_lambda = _cost_string_to_lambda(c) + if cost_lambda is not None: + search_node_color[i] = cost_lambda elif ColorValidator.perform_validate_coerce(c, allow_number=False) is None: - raise ValueError( + msg = ( f'search_node_color[{i}] is neither a valid cost function preset ("total_cost", "total_fixed_cost", "fixed_cost", ' - + '"heuristic_cost", "lookahead_penalty") nor a respective callable, nor a valid color string specified as:\n' + '"heuristic_cost", "lookahead_penalty") nor a respective callable, nor a valid color string specified as:\n' + _remove_first_lines( ColorValidator("search_node_color", "visualize_search_graph").description(), 1 ) ) + raise TypeError(msg) else: # static color search_node_colorbar_title[i] = None elif isinstance(search_node_color, str): - l = _cost_string_to_lambda(search_node_color) - if l is not None: - search_node_color = [l] + cost_lambda = _cost_string_to_lambda(search_node_color) + if cost_lambda is not None: + search_node_color = [cost_lambda] elif ColorValidator.perform_validate_coerce(search_node_color, allow_number=False) is None: - raise ValueError( + msg = ( 'search_node_color is neither a list nor a valid cost function preset ("total_cost", "total_fixed_cost", "fixed_cost", ' - + '"heuristic_cost", "lookahead_penalty") nor a respective callable, nor a valid color string specified as:\n' + '"heuristic_cost", "lookahead_penalty") nor a respective callable, nor a valid color string specified as:\n' + _remove_first_lines(ColorValidator("search_node_color", "visualize_search_graph").description(), 1) ) + raise TypeError(msg) else: # static color search_node_color = [search_node_color] search_node_colorbar_title = [None] * number_of_node_traces else: - raise ValueError( + msg = ( 'search_node_color must be a cost function preset ("total_cost", "total_fixed_cost", "fixed_cost", "heuristic_cost", ' - + '"lookahead_penalty") or a respective callable, or a valid color string, or a list of the above.' + '"lookahead_penalty") or a respective callable, or a valid color string, or a list of the above.' ) + raise TypeError(msg) if use3d: - if _is_len_iterable(search_node_height): + if isinstance(search_node_height, Sequence) and not isinstance(search_node_height, str): + search_node_height = list(search_node_height) lambdas = set() for i, c in enumerate(search_node_height): if isinstance(c, str): - l = _cost_string_to_lambda(c) - if l is None: + cost_lambda = _cost_string_to_lambda(c) + if cost_lambda is None: msg = f"Unknown cost function preset search_node_height[{i}]: {c}" - raise ValueError(msg) - elif l in lambdas: + raise TypeError(msg) + if cost_lambda in lambdas: msg = f"search_node_height must not contain the same cost function multiple times: {c}" - raise ValueError(msg) - else: - search_node_height[i] = l - lambdas.add(l) + raise TypeError(msg) + search_node_height[i] = cost_lambda + lambdas.add(cost_lambda) elif not callable(c): - raise ValueError( + msg = ( # type: ignore[unreachable] "search_node_height must be a cost function preset ('total_cost', 'total_fixed_cost', 'fixed_cost', 'heuristic_cost', " - + "'lookahead_penalty') or a respective callable, or a list of the above." + "'lookahead_penalty') or a respective callable, or a list of the above." ) + raise TypeError(msg) elif isinstance(search_node_height, str): - l = _cost_string_to_lambda(search_node_height) - if l is not None: - search_node_height = [l] + cost_lambda = _cost_string_to_lambda(search_node_height) + if cost_lambda is not None: + search_node_height = [cost_lambda] else: msg = f"Unknown cost function preset search_node_height: {search_node_height}" - raise ValueError(msg) + raise TypeError(msg) else: msg = "search_node_height must be a list of cost functions or a single cost function." - raise ValueError(msg) + raise TypeError(msg) else: search_node_height = [] - return ( + return ( # type: ignore[return-value] data_logging_path, hide_layout, draw_stems, @@ -1443,9 +1502,9 @@ def _visualize_search_graph_check_parameters( def visualize_search_graph( data_logging_path: str, layer: int | Literal["interactive"] = "interactive", # 'interactive' (slider menu) | index - architecture_node_positions: dict[int, Position] | None = None, - architecture_layout_method: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"] = "sfdp", - search_node_layout_method: Literal[ + architecture_node_positions: MutableMapping[int, Position] | None = None, + architecture_layout: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"] = "sfdp", + search_node_layout: Literal[ "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ] = "walker", search_graph_border: float = 0.05, @@ -1466,18 +1525,20 @@ def visualize_search_graph( show_shared_swaps: bool = True, # if one swap moves 2 considered qubits -> combine into one two-colored and two-headed arrow color_valid_mapping: str | None = "green", # static HTML color (e.g. 'blue' or '#0000FF') color_final_node: str | None = "red", # static HTML color (e.g. 'blue' or '#0000FF') - search_node_color: str | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]) = "total_cost", + search_node_color: str + | (Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]) = "total_cost", # 'total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty' | static HTML color (e.g. 'blue' or '#0000FF') | # function that takes a SearchNode and returns a float (e.g. lambda n: n.fixed_cost + n.heuristic_cost) # node fields: {"fixed_cost": float, "heuristic_cost": float, "lookahead_penalty": float, "is_valid_mapping": bool, - # "final": bool, "depth": int, "layout": Tuple[int, ...], "swaps": Tuple[Tuple[int, int], ...]} + # "final": bool, "depth": int, "layout": Sequence[int], "swaps": Sequence[tuple[int, int]]} # or list of the above if 3d graph is used and multiple points per node are defined in search_node_height (lengths need to match) # if in that case no is list is provided all points per node will be the same color prioritize_search_node_color: bool - | list[ + | Sequence[ bool ] = False, # if True, search_node_color will be prioritized over color_valid_mapping and color_final_node - search_node_color_scale: str | list[str] = "YlGnBu", # https://plotly.com/python/builtin-colorscales/ + search_node_color_scale: Colorscale + | Sequence[Colorscale] = "YlGnBu", # https://plotly.com/python/builtin-colorscales/ # aggrnyl agsunset blackbody bluered blues blugrn bluyl brwnyl # bugn bupu burg burgyl cividis darkmint electric emrld # gnbu greens greys hot inferno jet magenta magma @@ -1490,11 +1551,11 @@ def visualize_search_graph( # piyg picnic portland puor rdgy rdylbu rdylgn spectral # tealrose temps tropic balance curl delta oxy edge # hsv icefire phase twilight mrybm mygbm armylg falllg - search_node_invert_color_scale: bool | list[bool] = True, - search_node_colorbar_title: str | list[str | None] | None = None, + search_node_invert_color_scale: bool | Sequence[bool] = True, + search_node_colorbar_title: str | Sequence[str | None] | None = None, search_node_colorbar_spacing: float = 0.06, search_node_height: str - | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]) = "total_cost", + | (Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]) = "total_cost", # just as with search_node_color (without color strings), but possible to specify a list to draw multiple point per node on different heights # only applicable if use3d is True draw_stems: bool = False, @@ -1504,7 +1565,7 @@ def visualize_search_graph( show_search_progression: bool = True, search_progression_step: int = 10, search_progression_speed: float = 2, # steps per second - plotly_settings: dict[str, dict[str, Any]] | None = None, + plotly_settings: MutableMapping[str, MutableMapping[str, object]] | None = None, # { # 'layout': settings for plotly.graph_objects.Layout (of subplots figure) # 'arrows': settings for plotly.graph_objects.layout.Annotation @@ -1523,6 +1584,71 @@ def visualize_search_graph( # TODO: show archticture edge labels (and control text?) # TODO: control hover text of search (especially for multiple points per node!) and architecture nodes? ) -> Widget: + """Creates a widget to visualize a search graph. + + Args: + data_logging_path (str): Path to the data logging directory of the search process to be visualized. + layer (int | Literal["interactive"]): Index of the circuit layer, of which the mapping should be visualized. Defaults to "interactive", in which case a slider menu will be created. + architecture_node_positions (MutableMapping[int, tuple[float, float]] | None): MutableMapping from physical qubits to (x, y) coordinates. Defaults to None, in which case architecture_layout will be used to generate a layout. + architecture_layout (Literal[ "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ]): The method to use when layouting the qubit connectivity graph. Defaults to "sfdp". + search_node_layout (Literal[ "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ]): The method to use when layouting the search graph. Defaults to "walker". + search_graph_border (float): Size of the border around the search graph. Defaults to 0.05. + architecture_border (float): Size of the border around the qubit connectivity graph. Defaults to 0.05. + swap_arrow_spacing (float): Lateral spacing between arrows indicating swaps on the qubit connectivity graph. Defaults to 0.05. + swap_arrow_offset (float): Offset of heads and shaft of swap arrows from qubits they are pointing to/from. Defaults to 0.05. + use3d (bool): If a 3D graph should be used for the search graph using the z-axis to plot data features. Defaults to True. + projection (Literal["orthographic", "perspective"]): Projection type to use in 3D graphs. Defaults to "perspective". + width (int): Pixel width of the widget. Defaults to 1400. + height (int): Pixel height of the widget. Defaults to 700. + draw_search_edges (bool): If edges between search nodes should be drawn. Defaults to True. + search_edges_width (float): Width of edges between search nodes. Defaults to 0.5. + search_edges_color (str): Color of edges between search nodes (in CSS format, i.e. "#rrggbb", "#rgb", "colorname", etc.). Defaults to "#888". + search_edges_dash (str): Dashing of search edges (in CSS format, i.e. "solid", "dot", "dash", "longdash", etc.). Defaults to "solid". + tapered_search_layer_heights (bool): If search graph tree should progressively reduce the height of each layer. Defaults to True. + show_layout (Literal["hover", "click"] | None): If the current qubit layout should be shown on the qubit connectivity graph, when clicking or hovering on a search node or not at all. Defaults to "hover". + show_swaps (bool): Showing swaps on the connectivity graph. Defaults to True. + show_shared_swaps (bool): Indicate a shared swap by 1 arrow with 2 heads, otherwise 2 arrows in opposite direction are drawn for the 1 shared swap. Defaults to True. + color_valid_mapping (str | None): Color to use for search nodes containing a valid qubit layout (in CSS format). Defaults to "green". + color_final_node (str | None): Color to use for the final solution search node (in CSS format). Defaults to "red". + search_node_color (str | Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by `search_node_color_scale`, or a preset data feature ("total_cost" | "fixed_cost" | "heuristic_cost" | "lookahead_penalty"). In case a 3D search graph is used with multiple point per search node, each point"s color can be controlled individually via a list. Defaults to "total_cost". + prioritize_search_node_color (bool | Sequence[ bool ]): If search_node_color should be prioritized over color_valid_mapping and color_final_node. Defaults to False. + search_node_color_scale (str | Sequence[str]): Color scale to be used for converting float data features to search node colors. (See https://plotly.com/python/builtin-colorscales/ for valid values). Defaults to "YlGnBu". + search_node_invert_color_scale (bool | Sequence[bool]): If the color scale should be inverted. Defaults to True. + search_node_colorbar_title (str | Sequence[str | None] | None): Title(s) to be shown next to the colorbar(s). Defaults to None. + search_node_colorbar_spacing (float): Spacing between multiple colorbars. Defaults to 0.06. + search_node_height (str | Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]): Function mapping a mqt.qmap.visualization.SearchNode to a float value to be used as z-value in 3D search graphs or a preset data feature ("total_cost" | "fixed_cost" | "heuristic_cost" | "lookahead_penalty"). Or a list any of such functions/data features, to draw multiple points per search node. Defaults to "total_cost". + draw_stems (bool): If a vertical stem should be drawn in 3D search graphs to each search node. Defaults to False. + stems_width (float): Width of stems in 3D search graphs. Defaults to 0.7. + stems_color (str): Color of stems in 3D search graphs (in CSS format). Defaults to "#444". + stems_dash (str): Dashing of stems in 3D search graphs (in CSS format). Defaults to "solid". + show_search_progression (bool): If the search progression should be animated. Defaults to True. + search_progression_step (int): Step size (in number of nodes added) of search progression animation. Defaults to 10. + search_progression_speed (float): Speed of the search progression animation. Defaults to 2. + plotly_settings (MutableMapping[str, MutableMapping[str, any]] | None): Direct plotly configuration dictionaries to be passed through. Defaults to None. + ``` + { + "layout": settings for plotly.graph_objects.Layout (of subplots figure) + "arrows": settings for plotly.graph_objects.layout.Annotation + "stats_legend": settings for plotly.graph_objects.layout.Annotation + "search_nodes": settings for plotly.graph_objects.Scatter resp. ...Scatter3d + "search_edges": settings for plotly.graph_objects.Scatter resp. ...Scatter3d + "architecture_nodes": settings for plotly.graph_objects.Scatter + "architecture_edges": settings for plotly.graph_objects.Scatter + "architecture_edge_labels": settings for plotly.graph_objects.Scatter + "search_xaxis": settings for plotly.graph_objects.layout.XAxis resp. ...layout.scene.XAxis + "search_yaxis": settings for plotly.graph_objects.layout.YAxis resp. ...layout.scene.YAxis + "search_zaxis": settings for plotly.graph_objects.layout.scene.ZAxis + "architecture_xaxis": settings for plotly.graph_objects.layout.XAxis + "architecture_yaxis": settings for plotly.graph_objects.layout.YAxis + } + ``` + + Raises: + TypeError: If any of the arguments are invalid. + + Returns: + Widget: An interactive IPython widget to visualize the search graph. + """ # check and process all parameters if plotly_settings is None: plotly_settings = {} @@ -1537,13 +1663,13 @@ def visualize_search_graph( search_node_invert_color_scale, prioritize_search_node_color, search_node_colorbar_title, - plotly_settings, + full_plotly_settings, ) = _visualize_search_graph_check_parameters( data_logging_path, layer, architecture_node_positions, - architecture_layout_method, - search_node_layout_method, + architecture_layout, + search_node_layout, search_graph_border, architecture_border, swap_arrow_spacing, @@ -1583,7 +1709,7 @@ def visualize_search_graph( search_graph: nx.Graph | None = None arch_graph: nx.Graph | None = None initial_layout: list[int] = [] - considered_qubit_colors: dict[int, str] = {} + considered_qubit_colors: MutableMapping[int, str] = {} current_node_layout_visualized: int | None = None current_layer: int | None = None @@ -1596,48 +1722,48 @@ def visualize_search_graph( arch_y_min: float | None = None arch_y_max: float | None = None - search_node_traces: list[go.Scatter | go.Scatter3d] = [] - search_edge_traces: list[go.Scatter | go.Scatter3d] = [] + search_node_traces: Sequence[go.Scatter | go.Scatter3d] = [] + search_edge_traces: Sequence[go.Scatter | go.Scatter3d] = [] search_node_stem_trace: go.Scatter3d | None = None arch_node_trace: go.Scatter | None = None arch_edge_trace: go.Scatter | None = None arch_edge_label_trace: go.Scatter | None = None # one possible entry per layer (if not present, layer was not loaded yet) - search_graphs: dict[int, nx.Graph] = {} - initial_layouts: dict[int, list[int]] = {} - initial_qbit_positions: dict[int, list[int]] = {} # reverses of initial_layouts - layers_considered_qubit_colors: dict[int, dict[int, str]] = {} - single_qbit_multiplicities: dict[int, list[int]] = {} - individual_two_qbit_multiplicities: dict[int, list[int]] = {} - two_qbit_multiplicities: dict[int, list[TwoQbitMultiplicity]] = {} - final_node_ids: dict[int, int] = {} - search_node_scatter_data_x: dict[int, list[float]] = {} - search_node_scatter_data_y: dict[int, list[float]] = {} - search_node_scatter_data_z: dict[int, list[list[float]]] = {} - search_node_scatter_data_color: dict[int, list[list[float | str]]] = {} - search_node_stem_scatter_data_x: dict[int, list[float]] = {} - search_node_stem_scatter_data_y: dict[int, list[float]] = {} - search_node_stem_scatter_data_z: dict[int, list[float]] = {} - search_edge_scatter_data_x: dict[int, list[float]] = {} - search_edge_scatter_data_y: dict[int, list[float]] = {} - search_edge_scatter_data_z: dict[int, list[list[float]]] = {} - search_min_x: dict[int, float] = {} - search_max_x: dict[int, float] = {} - search_min_y: dict[int, float] = {} - search_max_y: dict[int, float] = {} - search_min_z: dict[int, float] = {} - search_max_z: dict[int, float] = {} + search_graphs: MutableMapping[int, nx.Graph] = {} + initial_layouts: MutableMapping[int, list[int]] = {} + initial_qbit_positions: MutableMapping[int, list[int]] = {} # reverses of initial_layouts + layers_considered_qubit_colors: MutableMapping[int, MutableMapping[int, str]] = {} + single_qbit_multiplicities: MutableMapping[int, Sequence[int]] = {} + individual_two_qbit_multiplicities: MutableMapping[int, Sequence[int]] = {} + two_qbit_multiplicities: MutableMapping[int, Sequence[_TwoQbitMultiplicity]] = {} + final_node_ids: MutableMapping[int, int] = {} + search_node_scatter_data_x: MutableMapping[int, Sequence[float]] = {} + search_node_scatter_data_y: MutableMapping[int, Sequence[float]] = {} + search_node_scatter_data_z: MutableMapping[int, Sequence[Sequence[float]]] = {} + search_node_scatter_data_color: MutableMapping[int, Sequence[Sequence[float | str]]] = {} + search_node_stem_scatter_data_x: MutableMapping[int, Sequence[float]] = {} + search_node_stem_scatter_data_y: MutableMapping[int, Sequence[float]] = {} + search_node_stem_scatter_data_z: MutableMapping[int, Sequence[float]] = {} + search_edge_scatter_data_x: MutableMapping[int, Sequence[float]] = {} + search_edge_scatter_data_y: MutableMapping[int, Sequence[float]] = {} + search_edge_scatter_data_z: MutableMapping[int, Sequence[Sequence[float]]] = {} + search_min_x: MutableMapping[int, float] = {} + search_max_x: MutableMapping[int, float] = {} + search_min_y: MutableMapping[int, float] = {} + search_max_y: MutableMapping[int, float] = {} + search_min_z: MutableMapping[int, float] = {} + search_max_z: MutableMapping[int, float] = {} sub_plots: go.Figure | None = None number_of_layers = 0 # parse general mapping info - with open(f"{data_logging_path}mapping_result.json") as result_file: + with Path(f"{data_logging_path}mapping_result.json").open() as result_file: number_of_layers = json.load(result_file)["statistics"]["layers"] - if type(layer) is int and layer >= number_of_layers: + if isinstance(layer, int) and layer >= number_of_layers: msg = f"Invalid layer {layer}. There are only {number_of_layers} layers in the data log." raise ValueError(msg) @@ -1651,7 +1777,7 @@ def visualize_search_graph( use3d, draw_stems, draw_search_edges, - plotly_settings, + full_plotly_settings, ) # parse architecture info and prepare respective traces @@ -1663,7 +1789,7 @@ def visualize_search_graph( print(node, arch_graph.nodes[node]) for edge in arch_graph.edges: print(edge, arch_graph.edges[edge]) - architecture_node_positions = graphviz_layout(arch_graph, prog=architecture_layout_method) + architecture_node_positions = graphviz_layout(arch_graph, prog=architecture_layout) elif len(architecture_node_positions) != len(arch_graph.nodes): msg = f"architecture_node_positions must contain positions for all {len(arch_graph.nodes)} architecture nodes." raise ValueError(msg) @@ -1678,10 +1804,10 @@ def visualize_search_graph( arch_y_max = max(architecture_node_positions.values(), key=lambda p: p[1])[1] arch_edge_trace, arch_edge_label_trace = _draw_architecture_edges( - arch_graph, architecture_node_positions, plotly_settings + arch_graph, architecture_node_positions, full_plotly_settings ) - arch_node_trace = go.Scatter(**plotly_settings["architecture_nodes"]) + arch_node_trace = go.Scatter(**full_plotly_settings["architecture_nodes"]) arch_node_trace.x = [] arch_node_trace.y = [] @@ -1690,9 +1816,16 @@ def visualize_search_graph( sub_plots = make_subplots(rows=1, cols=2, specs=[[{"is_3d": use3d}, {"is_3d": False}]]) else: sub_plots = go.Figure() - sub_plots.update_layout(plotly_settings["layout"]) - - active_trace_indices = {} + sub_plots.update_layout(full_plotly_settings["layout"]) + + active_trace_indices: _ActiveTraceIndices = { + "search_nodes": [], + "search_edges": [], + "search_node_stems": 0, + "arch_nodes": 0, + "arch_edges": 0, + "arch_edge_labels": 0, + } active_trace_indices["search_edges"] = [] for trace in search_edge_traces: @@ -1716,10 +1849,10 @@ def visualize_search_graph( yaxis1 = ( sub_plots.layout.scene.yaxis if use3d else (sub_plots.layout.yaxis if hide_layout else sub_plots.layout.yaxis1) ) - xaxis1.update(**plotly_settings["search_xaxis"]) - yaxis1.update(**plotly_settings["search_yaxis"]) + xaxis1.update(**full_plotly_settings["search_xaxis"]) + yaxis1.update(**full_plotly_settings["search_yaxis"]) if use3d: - sub_plots.layout.scene.zaxis.update(**plotly_settings["search_zaxis"]) + sub_plots.layout.scene.zaxis.update(**full_plotly_settings["search_zaxis"]) if not hide_layout: active_trace_indices["arch_edges"] = len(sub_plots.data) @@ -1733,25 +1866,25 @@ def visualize_search_graph( xaxis2 = sub_plots["layout"]["xaxis"] if use3d else sub_plots["layout"]["xaxis2"] yaxis2 = sub_plots["layout"]["yaxis"] if use3d else sub_plots["layout"]["yaxis2"] - plotly_settings["architecture_xaxis"]["range"] = [ - arch_x_min - abs(arch_x_max - arch_x_min) * architecture_border, - arch_x_max + abs(arch_x_max - arch_x_min) * architecture_border, + full_plotly_settings["architecture_xaxis"]["range"] = [ + arch_x_min - abs(arch_x_max - arch_x_min) * architecture_border, # type: ignore[operator] + arch_x_max + abs(arch_x_max - arch_x_min) * architecture_border, # type: ignore[operator] ] - plotly_settings["architecture_yaxis"]["range"] = [ - arch_y_min - abs(arch_y_max - arch_y_min) * architecture_border, - arch_y_max + abs(arch_y_max - arch_y_min) * architecture_border, + full_plotly_settings["architecture_yaxis"]["range"] = [ + arch_y_min - abs(arch_y_max - arch_y_min) * architecture_border, # type: ignore[operator] + arch_y_max + abs(arch_y_max - arch_y_min) * architecture_border, # type: ignore[operator] ] - x_diff = plotly_settings["architecture_xaxis"]["range"][1] - plotly_settings["architecture_xaxis"]["range"][0] - y_diff = plotly_settings["architecture_yaxis"]["range"][1] - plotly_settings["architecture_yaxis"]["range"][0] + x_diff = full_plotly_settings["architecture_xaxis"]["range"][1] - full_plotly_settings["architecture_xaxis"]["range"][0] # type: ignore[index] + y_diff = full_plotly_settings["architecture_yaxis"]["range"][1] - full_plotly_settings["architecture_yaxis"]["range"][0] # type: ignore[index] if x_diff == 0: - mid = plotly_settings["architecture_xaxis"]["range"][0] - plotly_settings["architecture_xaxis"]["range"] = [mid - y_diff / 2, mid + y_diff / 2] + mid = full_plotly_settings["architecture_xaxis"]["range"][0] # type: ignore[index] + full_plotly_settings["architecture_xaxis"]["range"] = [mid - y_diff / 2, mid + y_diff / 2] if y_diff == 0: - mid = plotly_settings["architecture_yaxis"]["range"][0] - plotly_settings["architecture_yaxis"]["range"] = [mid - x_diff / 2, mid + x_diff / 2] + mid = full_plotly_settings["architecture_yaxis"]["range"][0] # type: ignore[index] + full_plotly_settings["architecture_yaxis"]["range"] = [mid - x_diff / 2, mid + x_diff / 2] - xaxis2.update(**plotly_settings["architecture_xaxis"]) - yaxis2.update(**plotly_settings["architecture_yaxis"]) + xaxis2.update(**full_plotly_settings["architecture_xaxis"]) + yaxis2.update(**full_plotly_settings["architecture_yaxis"]) fig = go.FigureWidget(sub_plots) @@ -1771,7 +1904,11 @@ def visualize_search_graph( arch_edge_label_trace = fig.data[active_trace_indices["arch_edge_labels"]] # define interactive callbacks - def visualize_search_node_layout(trace, points, selector): + def visualize_search_node_layout( + trace: plotly.basedatatypes.BaseTraceType, # noqa: ARG001 + points: plotly.callbacks.Points, + selector: plotly.callbacks.InputDeviceState, # noqa: ARG001 + ) -> None: nonlocal current_node_layout_visualized if current_layer is None: @@ -1779,29 +1916,31 @@ def visualize_search_node_layout(trace, points, selector): point_inds = [] try: point_inds = points.point_inds - except: + except AttributeError: point_inds = points if len(point_inds) == 0: return if current_node_layout_visualized == point_inds[0]: return current_node_layout_visualized = point_inds[0] - node_index = list(search_graph.nodes())[current_node_layout_visualized] + node_index = list(search_graph.nodes())[current_node_layout_visualized] # type: ignore[index, union-attr] + # this function is only called if hide_layout==False, therefore all variables are defined + # since mypy's type inference fails in this regard, we need to use "type: ignore[arg-type]" here _visualize_layout( fig, - search_graph.nodes[node_index]["data"], + search_graph.nodes[node_index]["data"], # type: ignore[union-attr] arch_node_trace, - architecture_node_positions, + architecture_node_positions, # type: ignore[arg-type] initial_layout, considered_qubit_colors, swap_arrow_offset, - arch_x_arrow_spacing, - arch_y_arrow_spacing, + arch_x_arrow_spacing, # type: ignore[arg-type] + arch_y_arrow_spacing, # type: ignore[arg-type] show_shared_swaps, - current_node_layout_visualized, + current_node_layout_visualized, # type: ignore[arg-type] search_node_traces[0] if not use3d else None, - plotly_settings, + full_plotly_settings, ) if not hide_layout: @@ -1811,8 +1950,8 @@ def visualize_search_node_layout(trace, points, selector): elif show_layout == "click": trace.on_click(visualize_search_node_layout) - def update_timestep(timestep): - timestep = timestep["new"] + def update_timestep(change: MutableMapping[str, int]) -> None: + timestep = change["new"] if current_layer is None: return with fig.batch_update(): @@ -1844,7 +1983,7 @@ def update_timestep(timestep): timestep_play.observe(update_timestep, names="value") timestep = HBox([timestep_play, timestep_slider]) - def update_layer(new_layer: int): + def update_layer(new_layer: int) -> None: nonlocal current_layer, search_graph, initial_layout, considered_qubit_colors, current_node_layout_visualized if current_layer == new_layer: @@ -1880,7 +2019,7 @@ def update_layer(new_layer: int): ) = _load_layer_data( data_logging_path, current_layer, - search_node_layout_method, + search_node_layout, tapered_search_layer_heights, number_of_node_traces, use3d, @@ -1945,7 +2084,7 @@ def update_layer(new_layer: int): if not hide_layout: _draw_architecture_nodes( arch_node_trace, - architecture_node_positions, + architecture_node_positions, # type: ignore[arg-type] considered_qubit_colors, initial_qbit_positions[current_layer], single_qbit_multiplicities[current_layer], @@ -1962,7 +2101,7 @@ def update_layer(new_layer: int): timestep_slider.value = len(search_graph.nodes) current_layer = cl - if type(layer) is int: + if isinstance(layer, int): update_layer(layer) else: update_layer(0) From cb6627a6da24861f6bfb9ec304d37b32f02ad235 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:57:04 +0000 Subject: [PATCH 010/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qmap/visualization/visualize_search_graph.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index 6bc069153..5e89345fb 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -1874,8 +1874,14 @@ def visualize_search_graph( arch_y_min - abs(arch_y_max - arch_y_min) * architecture_border, # type: ignore[operator] arch_y_max + abs(arch_y_max - arch_y_min) * architecture_border, # type: ignore[operator] ] - x_diff = full_plotly_settings["architecture_xaxis"]["range"][1] - full_plotly_settings["architecture_xaxis"]["range"][0] # type: ignore[index] - y_diff = full_plotly_settings["architecture_yaxis"]["range"][1] - full_plotly_settings["architecture_yaxis"]["range"][0] # type: ignore[index] + x_diff = ( + full_plotly_settings["architecture_xaxis"]["range"][1] + - full_plotly_settings["architecture_xaxis"]["range"][0] + ) # type: ignore[index] + y_diff = ( + full_plotly_settings["architecture_yaxis"]["range"][1] + - full_plotly_settings["architecture_yaxis"]["range"][0] + ) # type: ignore[index] if x_diff == 0: mid = full_plotly_settings["architecture_xaxis"]["range"][0] # type: ignore[index] full_plotly_settings["architecture_xaxis"]["range"] = [mid - y_diff / 2, mid + y_diff / 2] From c67df3998d455b96c8115d6a1a02806b95db463a Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Mon, 30 Oct 2023 23:32:30 +0100 Subject: [PATCH 011/108] fix python linter errors --- src/mqt/qmap/visualization/visualize_search_graph.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index 5e89345fb..a2980da0b 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -1875,13 +1875,13 @@ def visualize_search_graph( arch_y_max + abs(arch_y_max - arch_y_min) * architecture_border, # type: ignore[operator] ] x_diff = ( - full_plotly_settings["architecture_xaxis"]["range"][1] - - full_plotly_settings["architecture_xaxis"]["range"][0] - ) # type: ignore[index] + full_plotly_settings["architecture_xaxis"]["range"][1] # type: ignore[index] + - full_plotly_settings["architecture_xaxis"]["range"][0] # type: ignore[index] + ) y_diff = ( - full_plotly_settings["architecture_yaxis"]["range"][1] - - full_plotly_settings["architecture_yaxis"]["range"][0] - ) # type: ignore[index] + full_plotly_settings["architecture_yaxis"]["range"][1] # type: ignore[index] + - full_plotly_settings["architecture_yaxis"]["range"][0] # type: ignore[index] + ) if x_diff == 0: mid = full_plotly_settings["architecture_xaxis"]["range"][0] # type: ignore[index] full_plotly_settings["architecture_xaxis"]["range"] = [mid - y_diff / 2, mid + y_diff / 2] From 06efabc67528afb777d42e5add3af67cb5369a20 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Tue, 31 Oct 2023 02:43:45 +0100 Subject: [PATCH 012/108] fixing c linter errors --- include/Architecture.hpp | 2 +- include/DataLogger.hpp | 17 +- include/configuration/Configuration.hpp | 4 +- include/heuristic/HeuristicMapper.hpp | 2 +- src/DataLogger.cpp | 17 +- src/heuristic/HeuristicMapper.cpp | 40 +- src/utils.cpp | 5 +- test/test_architecture.cpp | 28 +- test/test_heuristic.cpp | 528 ++++++++++++------------ 9 files changed, 318 insertions(+), 325 deletions(-) diff --git a/include/Architecture.hpp b/include/Architecture.hpp index e9ad70ad5..0d4652ce8 100644 --- a/include/Architecture.hpp +++ b/include/Architecture.hpp @@ -240,7 +240,7 @@ class Architecture { createDistanceTable(); } - bool isEdgeConnected(const Edge& edge) const { + [[nodiscard]] bool isEdgeConnected(const Edge& edge) const { return couplingMap.find(edge) != couplingMap.end(); } diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index 115c818ca..19c3afb5d 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -15,18 +15,17 @@ class DataLogger { public: - DataLogger(const std::string& path, Architecture& arch, + DataLogger(std::string path, Architecture& arch, qc::QuantumComputation& qc) - : dataLoggingPath(path), architecture(arch), inputCircuit(qc) { - nqubits = architecture.getNqubits(); + : dataLoggingPath(std::move(path)), architecture(std::move(arch)), inputCircuit(qc), nqubits(arch.getNqubits()) { initLog(); logArchitecture(architecture); logInputCircuit(inputCircuit); for (std::size_t i = 0; i < qc.getNqubits(); ++i) { - qregs.push_back(std::make_pair("q", "q[" + std::to_string(i) + "]")); + qregs.emplace_back("q", "q[" + std::to_string(i) + "]"); } for (std::size_t i = 0; i < qc.getNcbits(); ++i) { - cregs.push_back(std::make_pair("c", "c[" + std::to_string(i) + "]")); + cregs.emplace_back("c", "c[" + std::to_string(i) + "]"); } } @@ -42,10 +41,8 @@ class DataLogger { std::size_t depth); void logFinalizeLayer( std::size_t layer, const qc::CompoundOperation& ops, - std::vector singleQubitMultiplicity, - std::map, - std::pair> - twoQubitMultiplicity, + const std::vector& singleQubitMultiplicity, + const std::map, std::pair>& twoQubitMultiplicity, const std::array& initialLayout, std::size_t finalNodeId, double finalCostFixed, double finalCostHeur, double finalLookaheadPenalty, @@ -63,7 +60,7 @@ class DataLogger { void close(); protected: - std::string dataLoggingPath; + std::string& dataLoggingPath; Architecture& architecture; std::uint16_t nqubits; qc::QuantumComputation& inputCircuit; diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index abfdbea7b..4eda21ffe 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -29,7 +29,7 @@ struct Configuration { bool verbose = false; bool debug = false; - std::string dataLoggingPath = ""; + std::string dataLoggingPath; // map to particular subgraph of architecture (in exact mapper) std::set subgraph{}; @@ -76,7 +76,7 @@ struct Configuration { [[nodiscard]] nlohmann::json json() const; [[nodiscard]] std::string toString() const { return json().dump(2); } - bool dataLoggingEnabled() const { return !dataLoggingPath.empty(); } + [[nodiscard]] bool dataLoggingEnabled() const { return !dataLoggingPath.empty(); } void setTimeout(const std::size_t sec) { timeout = sec; } [[nodiscard]] bool swapLimitsEnabled() const { diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index a15f89524..0e6fd586d 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -83,7 +83,7 @@ class HeuristicMapper : public Mapper { std::size_t parent = 0; std::size_t id; - Node(std::size_t nodeId) : id(nodeId){}; + explicit Node(std::size_t nodeId) : id(nodeId){}; Node(std::size_t nodeId, std::size_t parentId, const std::array& q, const std::array& loc, diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index d0f1c248e..bae62f20d 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -13,7 +13,7 @@ void DataLogger::initLog() { if (dataLoggingPath.back() != '/') { dataLoggingPath += '/'; } - std::filesystem::path dirPath(dataLoggingPath); + const std::filesystem::path dirPath(dataLoggingPath); if (!std::filesystem::exists(dirPath)) { std::filesystem::create_directory(dirPath); } @@ -64,8 +64,7 @@ void DataLogger::openNewLayer(std::size_t layer) { } for (std::size_t i = searchNodesLogFiles.size(); i <= layer; ++i) { - searchNodesLogFiles.push_back(std::ofstream( - dataLoggingPath + "nodes_layer_" + std::to_string(i) + ".csv")); + searchNodesLogFiles.emplace_back(dataLoggingPath + "nodes_layer_" + std::to_string(i) + ".csv"); if (!searchNodesLogFiles.at(i).good()) { deactivated = true; std::cerr << "[data-logging] Error opening file: " << dataLoggingPath @@ -77,10 +76,8 @@ void DataLogger::openNewLayer(std::size_t layer) { void DataLogger::logFinalizeLayer( std::size_t layer, const qc::CompoundOperation& ops, - std::vector singleQubitMultiplicity, - std::map, - std::pair> - twoQubitMultiplicity, + const std::vector& singleQubitMultiplicity, + const std::map, std::pair>& twoQubitMultiplicity, const std::array& initialLayout, std::size_t finalNodeId, double finalCostFixed, double finalCostHeur, double finalLookaheadPenalty, @@ -181,7 +178,7 @@ void DataLogger::logSearchNode( of << qubits.at(i) << ","; } if (nqubits > 0) { - of.seekp(-1, of.cur); // remove last comma + of.seekp(-1, std::ios_base::cur); // remove last comma } of << ";"; for (const auto& sw : swaps) { @@ -197,8 +194,8 @@ void DataLogger::logSearchNode( of << ","; } } - if (swaps.size() > 0) { - of.seekp(-1, of.cur); // remove last comma + if (!swaps.empty()) { + of.seekp(-1, std::ios_base::cur); // remove last comma } of << std::endl; }; diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index d58552fb1..d8c0fe29b 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -387,11 +387,11 @@ void HeuristicMapper::mapUnmappedGates( if (locations.at(q) == DEFAULT_POSITION) { // TODO: consider fidelity // map to first free physical qubit - for (std::uint16_t phys_q = 0; phys_q < architecture.getNqubits(); - ++phys_q) { - if (qubits.at(phys_q) == -1) { - locations.at(q) = static_cast(phys_q); - qubits.at(phys_q) = static_cast(q); + for (std::uint16_t physQbit = 0; physQbit < architecture.getNqubits(); + ++physQbit) { + if (qubits.at(physQbit) == -1) { + locations.at(q) = static_cast(physQbit); + qubits.at(physQbit) = static_cast(q); break; } } @@ -495,7 +495,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { TwoQubitMultiplicity twoQubitGateMultiplicity{}; Node bestDoneNode(0); bool done = false; - bool considerFidelity = results.config.considerFidelity; + const bool considerFidelity = results.config.considerFidelity; for (const auto& gate : layers.at(layer)) { if (gate.singleQubit()) { @@ -1041,24 +1041,24 @@ void HeuristicMapper::Node::updateHeuristicCost( // qubits with higher fidelity double savingsPotential = 0.; if (considerFidelity) { - for (std::uint16_t log_qbit = 0U; log_qbit < arch.getNqubits(); - ++log_qbit) { - if (singleQubitGateMultiplicity.at(log_qbit) == 0) { + for (std::uint16_t logQbit = 0U; logQbit < arch.getNqubits(); + ++logQbit) { + if (singleQubitGateMultiplicity.at(logQbit) == 0) { continue; } double qbitSavings = 0; - double currFidelity = arch.getSingleQubitFidelityCost( - static_cast(locations.at(log_qbit))); - for (std::uint16_t phys_qbit = 0U; phys_qbit < arch.getNqubits(); - ++phys_qbit) { - if (arch.getSingleQubitFidelityCost(phys_qbit) >= currFidelity) { + const double currFidelity = arch.getSingleQubitFidelityCost( + static_cast(locations.at(logQbit))); + for (std::uint16_t physQbit = 0U; physQbit < arch.getNqubits(); + ++physQbit) { + if (arch.getSingleQubitFidelityCost(physQbit) >= currFidelity) { continue; } - double curSavings = - singleQubitGateMultiplicity.at(log_qbit) * - (currFidelity - arch.getSingleQubitFidelityCost(phys_qbit)) - + const double curSavings = + singleQubitGateMultiplicity.at(logQbit) * + (currFidelity - arch.getSingleQubitFidelityCost(physQbit)) - arch.fidelityDistance( - static_cast(locations.at(log_qbit)), phys_qbit, + static_cast(locations.at(logQbit)), physQbit, consideredQubits.size()); qbitSavings = std::max(qbitSavings, curSavings); } @@ -1073,7 +1073,7 @@ void HeuristicMapper::Node::updateHeuristicCost( const auto& [straightMultiplicity, reverseMultiplicity] = multiplicity; - bool edgeDone = + const bool edgeDone = (arch.isEdgeConnected({static_cast(locations.at(q1)), static_cast(locations.at(q2))}) || arch.isEdgeConnected({static_cast(locations.at(q2)), @@ -1114,7 +1114,7 @@ void HeuristicMapper::Node::updateHeuristicCost( } if (edgeDone) { - double currEdgeCost = + const double currEdgeCost = (straightMultiplicity * arch.getTwoQubitFidelityCost( static_cast(locations.at(q1)), diff --git a/src/utils.cpp b/src/utils.cpp index 16b0b9ef5..825bde82b 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -109,11 +109,10 @@ void Dijkstra::buildEdgeSkipTable(const Matrix& distanceTable, */ edgeSkipDistanceTable.clear(); edgeSkipDistanceTable.emplace_back(distanceTable); - std::size_t n = distanceTable.size(); + const std::size_t n = distanceTable.size(); for (std::size_t k = 1; k <= n; ++k) { // k...number of edges to be skipped along each path - edgeSkipDistanceTable.emplace_back( - Matrix(n, std::vector(n, std::numeric_limits::max()))); + edgeSkipDistanceTable.emplace_back(n, std::vector(n, std::numeric_limits::max())); Matrix* currentTable = &edgeSkipDistanceTable.back(); for (std::size_t q = 0; q < n; ++q) { currentTable->at(q).at(q) = 0.; diff --git a/test/test_architecture.cpp b/test/test_architecture.cpp index 8e47f1b90..985ddec8a 100644 --- a/test/test_architecture.cpp +++ b/test/test_architecture.cpp @@ -8,7 +8,7 @@ #include "gtest/gtest.h" #include -::testing::AssertionResult MatrixNear(const Matrix& a, const Matrix& b, +::testing::AssertionResult matrixNear(const Matrix& a, const Matrix& b, double delta) { if (a.size() != b.size()) { return ::testing::AssertionFailure() @@ -358,7 +358,7 @@ TEST(TestArchitecture, FidelityDistanceBidirectionalTest) { 0., }}; EXPECT_TRUE( - MatrixNear(architecture.getFidelityDistanceTable(), targetTable, 1e-6)); + matrixNear(architecture.getFidelityDistanceTable(), targetTable, 1e-6)); const Matrix targetTableSkip1Edge = { {// distance from 0 to i @@ -408,7 +408,7 @@ TEST(TestArchitecture, FidelityDistanceBidirectionalTest) { 0., 0., }}; - EXPECT_TRUE(MatrixNear(architecture.getFidelityDistanceTable(1), + EXPECT_TRUE(matrixNear(architecture.getFidelityDistanceTable(1), targetTableSkip1Edge, 1e-6)); const Matrix targetTableSkip3Edges = { @@ -466,7 +466,7 @@ TEST(TestArchitecture, FidelityDistanceBidirectionalTest) { 0., 0., }}; - EXPECT_TRUE(MatrixNear(architecture.getFidelityDistanceTable(3), + EXPECT_TRUE(matrixNear(architecture.getFidelityDistanceTable(3), targetTableSkip3Edges, 1e-6)); const Matrix zeroMatrix = { @@ -475,11 +475,11 @@ TEST(TestArchitecture, FidelityDistanceBidirectionalTest) { {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}}; EXPECT_TRUE( - MatrixNear(architecture.getFidelityDistanceTable(4), zeroMatrix, 1e-6)); + matrixNear(architecture.getFidelityDistanceTable(4), zeroMatrix, 1e-6)); EXPECT_TRUE( - MatrixNear(architecture.getFidelityDistanceTable(5), zeroMatrix, 1e-6)); + matrixNear(architecture.getFidelityDistanceTable(5), zeroMatrix, 1e-6)); EXPECT_TRUE( - MatrixNear(architecture.getFidelityDistanceTable(6), zeroMatrix, 1e-6)); + matrixNear(architecture.getFidelityDistanceTable(6), zeroMatrix, 1e-6)); } TEST(TestArchitecture, FidelityDistanceSemiBidirectionalTest) { @@ -609,7 +609,7 @@ TEST(TestArchitecture, FidelityDistanceSemiBidirectionalTest) { 2 * (std::log2(1 - 0.02) + std::log2(1 - 0.03)), 0.}}; EXPECT_TRUE( - MatrixNear(architecture.getFidelityDistanceTable(), targetTable, 1e-6)); + matrixNear(architecture.getFidelityDistanceTable(), targetTable, 1e-6)); const Matrix targetTableSkip1Edge = { {// distance from 0 to i @@ -664,7 +664,7 @@ TEST(TestArchitecture, FidelityDistanceSemiBidirectionalTest) { -3 * (std::log2(1 - 0.5) + std::log2(1 - 0.5) + std::log2(1 - 0.1)) - 2 * (std::log2(1 - 0.03) + std::log2(1 - 0.03)), 0., 0.}}; - EXPECT_TRUE(MatrixNear(architecture.getFidelityDistanceTable(1), + EXPECT_TRUE(matrixNear(architecture.getFidelityDistanceTable(1), targetTableSkip1Edge, 1e-6)); const Matrix targetTableSkip3Edges = { @@ -725,7 +725,7 @@ TEST(TestArchitecture, FidelityDistanceSemiBidirectionalTest) { 0., 0., }}; - EXPECT_TRUE(MatrixNear(architecture.getFidelityDistanceTable(3), + EXPECT_TRUE(matrixNear(architecture.getFidelityDistanceTable(3), targetTableSkip3Edges, 1e-6)); const Matrix zeroMatrix = { @@ -734,11 +734,11 @@ TEST(TestArchitecture, FidelityDistanceSemiBidirectionalTest) { {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}, {0., 0., 0., 0., 0., 0., 0.}}; EXPECT_TRUE( - MatrixNear(architecture.getFidelityDistanceTable(4), zeroMatrix, 1e-6)); + matrixNear(architecture.getFidelityDistanceTable(4), zeroMatrix, 1e-6)); EXPECT_TRUE( - MatrixNear(architecture.getFidelityDistanceTable(5), zeroMatrix, 1e-6)); + matrixNear(architecture.getFidelityDistanceTable(5), zeroMatrix, 1e-6)); EXPECT_TRUE( - MatrixNear(architecture.getFidelityDistanceTable(6), zeroMatrix, 1e-6)); + matrixNear(architecture.getFidelityDistanceTable(6), zeroMatrix, 1e-6)); } TEST(TestArchitecture, FidelitySwapCostTest) { @@ -856,7 +856,7 @@ TEST(TestArchitecture, DistanceCheapestPathTest) { // minimum number of unidirectional edges on a path where the same path with // bidirectional edges can afford at least 1 more edge and still be cheaper - std::uint8_t nrEdges = + const std::uint8_t nrEdges = 1 + static_cast( std::ceil(static_cast(COST_BIDIRECTIONAL_SWAP) / (static_cast(COST_UNIDIRECTIONAL_SWAP) - diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 370dc23aa..983fe0817 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -301,50 +301,49 @@ TEST(Functionality, DataLogger) { if (!archFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << "architecture.json"; - } else { - const auto archJson = nlohmann::json::parse(archFile); - EXPECT_EQ(archJson["name"], architecture.getName()); - EXPECT_EQ(archJson["nqubits"], architecture.getNqubits()); - EXPECT_EQ(archJson["distances"], architecture.getDistanceTable()); - EXPECT_EQ(archJson["coupling_map"], architecture.getCouplingMap()); - const auto fidelityJson = archJson["fidelity"]; - EXPECT_EQ(fidelityJson["fidelity_distances"], - architecture.getFidelityDistanceTables()); - EXPECT_EQ(fidelityJson["single_qubit_fidelities"], - architecture.getSingleQubitFidelities()); - EXPECT_EQ(fidelityJson["two_qubit_fidelities"], - architecture.getFidelityTable()); - // json does not support inf values, instead nlohmann::json replaces inf - // with null - auto& singleQubitFidelityCosts = architecture.getSingleQubitFidelityCosts(); - for (std::size_t i = 0; i < singleQubitFidelityCosts.size(); ++i) { - if (std::isinf(singleQubitFidelityCosts[i])) { - EXPECT_TRUE(fidelityJson["single_qubit_fidelity_costs"][i].is_null()); - } else { - EXPECT_EQ(fidelityJson["single_qubit_fidelity_costs"][i], - singleQubitFidelityCosts[i]); - } + } + const auto archJson = nlohmann::json::parse(archFile); + EXPECT_EQ(archJson["name"], architecture.getName()); + EXPECT_EQ(archJson["nqubits"], architecture.getNqubits()); + EXPECT_EQ(archJson["distances"], architecture.getDistanceTable()); + EXPECT_EQ(archJson["coupling_map"], architecture.getCouplingMap()); + const auto& fidelityJson = archJson["fidelity"]; + EXPECT_EQ(fidelityJson["fidelity_distances"], + architecture.getFidelityDistanceTables()); + EXPECT_EQ(fidelityJson["single_qubit_fidelities"], + architecture.getSingleQubitFidelities()); + EXPECT_EQ(fidelityJson["two_qubit_fidelities"], + architecture.getFidelityTable()); + // json does not support inf values, instead nlohmann::json replaces inf + // with null + const auto& singleQubitFidelityCosts = architecture.getSingleQubitFidelityCosts(); + for (std::size_t i = 0; i < singleQubitFidelityCosts.size(); ++i) { + if (std::isinf(singleQubitFidelityCosts[i])) { + EXPECT_TRUE(fidelityJson["single_qubit_fidelity_costs"][i].is_null()); + } else { + EXPECT_EQ(fidelityJson["single_qubit_fidelity_costs"][i], + singleQubitFidelityCosts[i]); } - auto& twoQubitFidelityCosts = architecture.getTwoQubitFidelityCosts(); - for (std::size_t i = 0; i < twoQubitFidelityCosts.size(); ++i) { - for (std::size_t j = 0; j < twoQubitFidelityCosts[i].size(); ++j) { - if (std::isinf(twoQubitFidelityCosts[i][j])) { - EXPECT_TRUE(fidelityJson["two_qubit_fidelity_costs"][i][j].is_null()); - } else { - EXPECT_EQ(fidelityJson["two_qubit_fidelity_costs"][i][j], - twoQubitFidelityCosts[i][j]); - } + } + const auto& twoQubitFidelityCosts = architecture.getTwoQubitFidelityCosts(); + for (std::size_t i = 0; i < twoQubitFidelityCosts.size(); ++i) { + for (std::size_t j = 0; j < twoQubitFidelityCosts[i].size(); ++j) { + if (std::isinf(twoQubitFidelityCosts[i][j])) { + EXPECT_TRUE(fidelityJson["two_qubit_fidelity_costs"][i][j].is_null()); + } else { + EXPECT_EQ(fidelityJson["two_qubit_fidelity_costs"][i][j], + twoQubitFidelityCosts[i][j]); } } - auto& swapFidelityCosts = architecture.getSwapFidelityCosts(); - for (std::size_t i = 0; i < swapFidelityCosts.size(); ++i) { - for (std::size_t j = 0; j < swapFidelityCosts[i].size(); ++j) { - if (std::isinf(swapFidelityCosts[i][j])) { - EXPECT_TRUE(fidelityJson["swap_fidelity_costs"][i][j].is_null()); - } else { - EXPECT_EQ(fidelityJson["swap_fidelity_costs"][i][j], - swapFidelityCosts[i][j]); - } + } + const auto& swapFidelityCosts = architecture.getSwapFidelityCosts(); + for (std::size_t i = 0; i < swapFidelityCosts.size(); ++i) { + for (std::size_t j = 0; j < swapFidelityCosts[i].size(); ++j) { + if (std::isinf(swapFidelityCosts[i][j])) { + EXPECT_TRUE(fidelityJson["swap_fidelity_costs"][i][j].is_null()); + } else { + EXPECT_EQ(fidelityJson["swap_fidelity_costs"][i][j], + swapFidelityCosts[i][j]); } } } @@ -355,84 +354,83 @@ TEST(Functionality, DataLogger) { if (!resultFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << "mapping_result.json"; - } else { - const auto resultJson = nlohmann::json::parse(resultFile); - const auto configJson = resultJson["config"]; - EXPECT_EQ(configJson["add_measurements_to_mapped_circuit"], - settings.addMeasurementsToMappedCircuit); - EXPECT_EQ(configJson["admissible_heuristic"], settings.admissibleHeuristic); - EXPECT_EQ(configJson["consider_fidelity"], settings.considerFidelity); - EXPECT_EQ(configJson["initial_layout"], toString(settings.initialLayout)); - EXPECT_EQ(configJson["layering"], toString(settings.layering)); - EXPECT_EQ(configJson["method"], toString(settings.method)); - EXPECT_EQ(configJson["post_mapping_optimizations"], - settings.postMappingOptimizations); - EXPECT_EQ(configJson["pre_mapping_optimizations"], - settings.preMappingOptimizations); - EXPECT_EQ(configJson["teleportation"], settings.useTeleportation); - EXPECT_EQ(configJson["timeout"], settings.timeout); - const auto lookaheadJson = configJson["lookahead"]; - EXPECT_EQ(lookaheadJson["factor"], settings.lookaheadFactor); - EXPECT_EQ(lookaheadJson["first_factor"], settings.firstLookaheadFactor); - EXPECT_EQ(lookaheadJson["nr_lookaheads"], settings.nrLookaheads); - - const auto inCircJson = resultJson["input_circuit"]; - EXPECT_EQ(inCircJson["cnots"], results.input.cnots); - EXPECT_EQ(inCircJson["gates"], results.input.gates); - EXPECT_EQ(inCircJson["name"], results.input.name); - EXPECT_EQ(inCircJson["qubits"], results.input.qubits); - EXPECT_EQ(inCircJson["single_qubit_gates"], results.input.singleQubitGates); - - const auto outCircJson = resultJson["output_circuit"]; - EXPECT_EQ(outCircJson["cnots"], results.output.cnots); - EXPECT_EQ(outCircJson["gates"], results.output.gates); - EXPECT_EQ(outCircJson["name"], results.output.name); - EXPECT_EQ(outCircJson["qubits"], results.output.qubits); - EXPECT_EQ(outCircJson["single_qubit_gates"], - results.output.singleQubitGates); - - const auto statJson = resultJson["statistics"]; - EXPECT_EQ(statJson["additional_gates"], - (static_cast>( - results.output.gates) - - static_cast>( - results.input.gates))); - EXPECT_EQ(statJson["layers"], results.input.layers); - EXPECT_EQ(statJson["mapping_time"], results.time); - EXPECT_EQ(statJson["swaps"], results.output.swaps); - EXPECT_EQ(statJson["teleportations"], results.output.teleportations); - EXPECT_EQ(statJson["timeout"], results.timeout); - EXPECT_EQ(statJson["total_fidelity"], results.output.totalFidelity); - EXPECT_EQ(statJson["total_log_fidelity"], results.output.totalLogFidelity); - - const auto benchmarkJson = statJson["benchmark"]; - EXPECT_EQ(benchmarkJson["average_branching_factor"], - results.heuristicBenchmark.averageBranchingFactor); - EXPECT_EQ(benchmarkJson["effective_branching_factor"], - results.heuristicBenchmark.effectiveBranchingFactor); - EXPECT_EQ(benchmarkJson["expanded_nodes"], - results.heuristicBenchmark.expandedNodes); - EXPECT_EQ(benchmarkJson["generated_nodes"], - results.heuristicBenchmark.generatedNodes); - EXPECT_EQ(benchmarkJson["time_per_node"], - results.heuristicBenchmark.timePerNode); - const auto benchmarkLayersJson = benchmarkJson["layers"]; - EXPECT_EQ(benchmarkLayersJson.size(), - results.layerHeuristicBenchmark.size()); - for (std::size_t i = 0; i < results.layerHeuristicBenchmark.size(); ++i) { - EXPECT_EQ(benchmarkLayersJson[i]["average_branching_factor"], - results.layerHeuristicBenchmark.at(i).averageBranchingFactor); - EXPECT_EQ(benchmarkLayersJson[i]["effective_branching_factor"], - results.layerHeuristicBenchmark.at(i).effectiveBranchingFactor); - EXPECT_EQ(benchmarkLayersJson[i]["expanded_nodes"], - results.layerHeuristicBenchmark.at(i).expandedNodes); - EXPECT_EQ(benchmarkLayersJson[i]["generated_nodes"], - results.layerHeuristicBenchmark.at(i).generatedNodes); - EXPECT_EQ(benchmarkLayersJson[i]["solution_depth"], - results.layerHeuristicBenchmark.at(i).solutionDepth); - EXPECT_EQ(benchmarkLayersJson[i]["time_per_node"], - results.layerHeuristicBenchmark.at(i).timePerNode); - } + } + const auto resultJson = nlohmann::json::parse(resultFile); + const auto& configJson = resultJson["config"]; + EXPECT_EQ(configJson["add_measurements_to_mapped_circuit"], + settings.addMeasurementsToMappedCircuit); + EXPECT_EQ(configJson["admissible_heuristic"], settings.admissibleHeuristic); + EXPECT_EQ(configJson["consider_fidelity"], settings.considerFidelity); + EXPECT_EQ(configJson["initial_layout"], toString(settings.initialLayout)); + EXPECT_EQ(configJson["layering"], toString(settings.layering)); + EXPECT_EQ(configJson["method"], toString(settings.method)); + EXPECT_EQ(configJson["post_mapping_optimizations"], + settings.postMappingOptimizations); + EXPECT_EQ(configJson["pre_mapping_optimizations"], + settings.preMappingOptimizations); + EXPECT_EQ(configJson["teleportation"], settings.useTeleportation); + EXPECT_EQ(configJson["timeout"], settings.timeout); + const auto& lookaheadJson = configJson["lookahead"]; + EXPECT_EQ(lookaheadJson["factor"], settings.lookaheadFactor); + EXPECT_EQ(lookaheadJson["first_factor"], settings.firstLookaheadFactor); + EXPECT_EQ(lookaheadJson["nr_lookaheads"], settings.nrLookaheads); + + const auto& inCircJson = resultJson["input_circuit"]; + EXPECT_EQ(inCircJson["cnots"], results.input.cnots); + EXPECT_EQ(inCircJson["gates"], results.input.gates); + EXPECT_EQ(inCircJson["name"], results.input.name); + EXPECT_EQ(inCircJson["qubits"], results.input.qubits); + EXPECT_EQ(inCircJson["single_qubit_gates"], results.input.singleQubitGates); + + const auto& outCircJson = resultJson["output_circuit"]; + EXPECT_EQ(outCircJson["cnots"], results.output.cnots); + EXPECT_EQ(outCircJson["gates"], results.output.gates); + EXPECT_EQ(outCircJson["name"], results.output.name); + EXPECT_EQ(outCircJson["qubits"], results.output.qubits); + EXPECT_EQ(outCircJson["single_qubit_gates"], + results.output.singleQubitGates); + + const auto& statJson = resultJson["statistics"]; + EXPECT_EQ(statJson["additional_gates"], + (static_cast>( + results.output.gates) - + static_cast>( + results.input.gates))); + EXPECT_EQ(statJson["layers"], results.input.layers); + EXPECT_EQ(statJson["mapping_time"], results.time); + EXPECT_EQ(statJson["swaps"], results.output.swaps); + EXPECT_EQ(statJson["teleportations"], results.output.teleportations); + EXPECT_EQ(statJson["timeout"], results.timeout); + EXPECT_EQ(statJson["total_fidelity"], results.output.totalFidelity); + EXPECT_EQ(statJson["total_log_fidelity"], results.output.totalLogFidelity); + + const auto& benchmarkJson = statJson["benchmark"]; + EXPECT_EQ(benchmarkJson["average_branching_factor"], + results.heuristicBenchmark.averageBranchingFactor); + EXPECT_EQ(benchmarkJson["effective_branching_factor"], + results.heuristicBenchmark.effectiveBranchingFactor); + EXPECT_EQ(benchmarkJson["expanded_nodes"], + results.heuristicBenchmark.expandedNodes); + EXPECT_EQ(benchmarkJson["generated_nodes"], + results.heuristicBenchmark.generatedNodes); + EXPECT_EQ(benchmarkJson["time_per_node"], + results.heuristicBenchmark.timePerNode); + const auto& benchmarkLayersJson = benchmarkJson["layers"]; + EXPECT_EQ(benchmarkLayersJson.size(), + results.layerHeuristicBenchmark.size()); + for (std::size_t i = 0; i < results.layerHeuristicBenchmark.size(); ++i) { + EXPECT_EQ(benchmarkLayersJson[i]["average_branching_factor"], + results.layerHeuristicBenchmark.at(i).averageBranchingFactor); + EXPECT_EQ(benchmarkLayersJson[i]["effective_branching_factor"], + results.layerHeuristicBenchmark.at(i).effectiveBranchingFactor); + EXPECT_EQ(benchmarkLayersJson[i]["expanded_nodes"], + results.layerHeuristicBenchmark.at(i).expandedNodes); + EXPECT_EQ(benchmarkLayersJson[i]["generated_nodes"], + results.layerHeuristicBenchmark.at(i).generatedNodes); + EXPECT_EQ(benchmarkLayersJson[i]["solution_depth"], + results.layerHeuristicBenchmark.at(i).solutionDepth); + EXPECT_EQ(benchmarkLayersJson[i]["time_per_node"], + results.layerHeuristicBenchmark.at(i).timePerNode); } // comparing logged input and output circuits with input circuit object and @@ -441,25 +439,23 @@ TEST(Functionality, DataLogger) { if (!inputQasmFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << "input.qasm"; - } else { - std::stringstream fileBuffer; - fileBuffer << inputQasmFile.rdbuf(); - std::stringstream qasmBuffer; - qc.dumpOpenQASM(qasmBuffer); - EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); } + std::stringstream fileBuffer; + fileBuffer << inputQasmFile.rdbuf(); + std::stringstream qasmBuffer; + qc.dumpOpenQASM(qasmBuffer); + EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); auto outputQasmFile = std::ifstream(settings.dataLoggingPath + "output.qasm"); if (!outputQasmFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << "output.qasm"; - } else { - std::stringstream fileBuffer; - fileBuffer << outputQasmFile.rdbuf(); - std::stringstream qasmBuffer; - mapper->dumpResult(qasmBuffer, qc::Format::OpenQASM); - EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); } + std::stringstream fileBuffer; + fileBuffer << outputQasmFile.rdbuf(); + std::stringstream qasmBuffer; + mapper->dumpResult(qasmBuffer, qc::Format::OpenQASM); + EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); // checking logged search graph info against known values (correct qubit // number, valid layouts, correct data types in all csv fields, etc.) @@ -469,125 +465,129 @@ TEST(Functionality, DataLogger) { if (!layerFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << "layer_" << i << ".json"; - } else { - const auto layerJson = nlohmann::json::parse(layerFile); - std::size_t finalNodeId = layerJson["final_node_id"]; - EXPECT_EQ(layerJson["initial_layout"].size(), architecture.getNqubits()); - EXPECT_EQ(layerJson["single_qubit_multiplicity"].size(), - architecture.getNqubits()); - - auto layerNodeFile = - std::ifstream(settings.dataLoggingPath + "nodes_layer_" + - std::to_string(i) + ".csv"); - if (!layerNodeFile.is_open()) { - FAIL() << "Could not open file " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + } + const auto layerJson = nlohmann::json::parse(layerFile); + const std::size_t finalNodeId = layerJson["final_node_id"]; + EXPECT_EQ(layerJson["initial_layout"].size(), architecture.getNqubits()); + EXPECT_EQ(layerJson["single_qubit_multiplicity"].size(), + architecture.getNqubits()); + + auto layerNodeFile = + std::ifstream(settings.dataLoggingPath + "nodes_layer_" + + std::to_string(i) + ".csv"); + if (!layerNodeFile.is_open()) { + FAIL() << "Could not open file " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; + } + std::string line; + bool foundFinalNode = false; + std::set nodeIds; + while (std::getline(layerNodeFile, line)) { + if (line.empty()) { + continue; + } + std::string col = 0; + std::size_t nodeId = 0; + std::size_t parentId = 0; + std::size_t depth = 0; + std::size_t isValidMapping = 0; + double costFixed = 0.; + double costHeur = 0.; + double lookaheadPenalty = 0.; + std::vector layout{}; + std::vector> swaps{}; + std::stringstream lineStream(line); + if (std::getline(lineStream, col, ';')) { + nodeId = std::stoull(col); + nodeIds.insert(nodeId); + } else { + FAIL() << "Missing value for node id in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + parentId = std::stoull(col); + if (nodeId != 0) { + EXPECT_TRUE(nodeIds.count(parentId) > 0); + } + } else { + FAIL() << "Missing value for parent node id in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + costFixed = std::stod(col); + } else { + FAIL() << "Missing value for fixed cost in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + costHeur = std::stod(col); + } else { + FAIL() << "Missing value for heuristic cost in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + lookaheadPenalty = std::stod(col); + } else { + FAIL() << "Missing value for lookahead penalty in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + isValidMapping = std::stoull(col); + if (isValidMapping > 1) { + FAIL() << "Non-boolean value " << isValidMapping + << " for isValidMapping in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; + } + } else { + FAIL() << "Missing value for isValidMapping in " + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + depth = std::stoull(col); } else { - std::string line; - bool foundFinalNode = false; - std::set nodeIds; - while (std::getline(layerNodeFile, line)) { - if (line.empty()) { - continue; - } - std::string col; - std::size_t nodeId, parentId, depth, isValidMapping; - double costFixed, costHeur, lookaheadPenalty; - std::vector layout{}; - std::vector> swaps{}; - std::stringstream lineStream(line); - if (std::getline(lineStream, col, ';')) { - nodeId = std::stoull(col); - nodeIds.insert(nodeId); - } else { - FAIL() << "Missing value for node id in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; - } - if (std::getline(lineStream, col, ';')) { - parentId = std::stoull(col); - if (nodeId != 0) { - EXPECT_TRUE(nodeIds.count(parentId) > 0); - } - } else { - FAIL() << "Missing value for parent node id in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; - } - if (std::getline(lineStream, col, ';')) { - costFixed = std::stod(col); - } else { - FAIL() << "Missing value for fixed cost in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; - } - if (std::getline(lineStream, col, ';')) { - costHeur = std::stod(col); - } else { - FAIL() << "Missing value for heuristic cost in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; - } - if (std::getline(lineStream, col, ';')) { - lookaheadPenalty = std::stod(col); - } else { - FAIL() << "Missing value for lookahead penalty in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; - } - if (std::getline(lineStream, col, ';')) { - isValidMapping = std::stoull(col); - if (isValidMapping > 1) { - FAIL() << "Non-boolean value " << isValidMapping - << " for isValidMapping in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; - } - } else { - FAIL() << "Missing value for isValidMapping in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; - } - if (std::getline(lineStream, col, ';')) { - depth = std::stoull(col); - } else { - FAIL() << "Missing value for depth in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; - } - if (std::getline(lineStream, col, ';')) { - std::stringstream qubitMapBuffer(col); - std::string entry; - while (std::getline(qubitMapBuffer, entry, ',')) { - std::int32_t qubit = std::stoi(entry); - layout.push_back(qubit); - EXPECT_TRUE(-1 <= qubit && qubit < architecture.getNqubits()); - } - EXPECT_EQ(layout.size(), architecture.getNqubits()); - } else { - FAIL() << "Missing value for layout in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; - } - if (std::getline(lineStream, col, ';')) { - std::stringstream swapBuffer(col); - std::string entry; - while (std::getline(swapBuffer, entry, ',')) { - std::int32_t q1, q2; - std::stringstream(entry) >> q1 >> q2; - EXPECT_TRUE(0 <= q1 && q1 < architecture.getNqubits()); - EXPECT_TRUE(0 <= q2 && q2 < architecture.getNqubits()); - swaps.push_back(std::make_pair(q1, q2)); - } - } - - if (nodeId == finalNodeId) { - foundFinalNode = true; - EXPECT_EQ(layerJson["final_cost_fixed"], costFixed); - EXPECT_EQ(layerJson["final_cost_heur"], costHeur); - EXPECT_EQ(layerJson["final_layout"], layout); - EXPECT_EQ(layerJson["final_lookahead_penalty"], lookaheadPenalty); - EXPECT_EQ(layerJson["final_search_depth"], depth); - EXPECT_EQ(layerJson["final_swaps"], swaps); - EXPECT_EQ(isValidMapping, 1); - } + FAIL() << "Missing value for depth in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + std::stringstream qubitMapBuffer(col); + std::string entry; + while (std::getline(qubitMapBuffer, entry, ',')) { + const std::int32_t qubit = std::stoi(entry); + layout.push_back(qubit); + EXPECT_TRUE(-1 <= qubit && qubit < architecture.getNqubits()); } - if (!foundFinalNode) { - FAIL() << "Could not find final node in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + EXPECT_EQ(layout.size(), architecture.getNqubits()); + } else { + FAIL() << "Missing value for layout in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; + } + if (std::getline(lineStream, col, ';')) { + std::stringstream swapBuffer(col); + std::string entry; + while (std::getline(swapBuffer, entry, ',')) { + std::int32_t q1 = 0; + std::int32_t q2 = 0; + std::stringstream(entry) >> q1 >> q2; + EXPECT_TRUE(0 <= q1 && q1 < architecture.getNqubits()); + EXPECT_TRUE(0 <= q2 && q2 < architecture.getNqubits()); + swaps.emplace_back(q1, q2); } } + + if (nodeId == finalNodeId) { + foundFinalNode = true; + EXPECT_EQ(layerJson["final_cost_fixed"], costFixed); + EXPECT_EQ(layerJson["final_cost_heur"], costHeur); + EXPECT_EQ(layerJson["final_layout"], layout); + EXPECT_EQ(layerJson["final_lookahead_penalty"], lookaheadPenalty); + EXPECT_EQ(layerJson["final_search_depth"], depth); + EXPECT_EQ(layerJson["final_swaps"], swaps); + EXPECT_EQ(isValidMapping, 1); + } + } + if (!foundFinalNode) { + FAIL() << "Could not find final node in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; } } @@ -885,11 +885,11 @@ TEST(HeuristicTestFidelity, RemapSingleQubit) { }; architecture.loadCouplingMap(6, cm); - double e5 = 0.99; - double e4 = 0.9; - double e3 = 0.5; - double e1 = 0.1; - double e0 = 0.01; + const double e5 = 0.99; + const double e4 = 0.9; + const double e3 = 0.5; + const double e1 = 0.1; + const double e0 = 0.01; auto props = Architecture::Properties(); props.setSingleQubitErrorRate(0, "x", e5); @@ -952,11 +952,11 @@ TEST(HeuristicTestFidelity, RemapSingleQubit) { */ EXPECT_EQ(result.output.swaps, 3); - double c3 = -std::log2(1 - e3); - double c1 = -std::log2(1 - e1); - double c0 = -std::log2(1 - e0); + const double c3 = -std::log2(1 - e3); + const double c1 = -std::log2(1 - e1); + const double c0 = -std::log2(1 - e0); - double expectedFidelity = 3 * c3 + 3 * c0 + 3 * c0 + // SWAPs + const double expectedFidelity = 3 * c3 + 3 * c0 + 3 * c0 + // SWAPs 5 * c1 + // Xs 5 * c1; // CXs EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); @@ -968,10 +968,10 @@ TEST(HeuristicTestFidelity, QubitRideAlong) { {1, 4}, {4, 1}, {2, 5}, {5, 2}, {5, 6}, {6, 5}}; architecture.loadCouplingMap(7, cm); - double e5 = 0.99; - double e4 = 0.9; - double e3 = 0.5; - double e1 = 0.1; + const double e5 = 0.99; + const double e4 = 0.9; + const double e3 = 0.5; + const double e1 = 0.1; auto props = Architecture::Properties(); props.setSingleQubitErrorRate(0, "x", e5); @@ -1035,11 +1035,11 @@ TEST(HeuristicTestFidelity, QubitRideAlong) { */ EXPECT_EQ(result.output.swaps, 4); - double c4 = -std::log2(1 - e4); - double c3 = -std::log2(1 - e3); - double c1 = -std::log2(1 - e1); + const double c4 = -std::log2(1 - e4); + const double c3 = -std::log2(1 - e3); + const double c1 = -std::log2(1 - e1); - double expectedFidelity = 3 * c4 + 3 * c3 + 3 * c4 + 3 * c3 + // SWAPs + const double expectedFidelity = 3 * c4 + 3 * c3 + 3 * c4 + 3 * c3 + // SWAPs 5 * c1 + 5 * c1; // CXs EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); } @@ -1088,7 +1088,7 @@ TEST(HeuristicTestFidelity, SingleQubitsCompete) { EXPECT_EQ(result.input.layers, 1); EXPECT_EQ(result.output.swaps, 1); - double expectedFidelity = -3 * std::log2(1 - 0.1) // SWAPs + const double expectedFidelity = -3 * std::log2(1 - 0.1) // SWAPs - std::log2(1 - 0.8) - std::log2(1 - 0.1); // Xs EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); } From 99548269e6e59a6f0c9b682b43cd737632b48c73 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 01:45:54 +0000 Subject: [PATCH 013/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/DataLogger.hpp | 10 ++- include/configuration/Configuration.hpp | 8 +- src/DataLogger.cpp | 7 +- src/heuristic/HeuristicMapper.cpp | 7 +- .../visualization/visualize_search_graph.py | 8 +- src/utils.cpp | 3 +- test/test_heuristic.cpp | 81 +++++++++---------- 7 files changed, 65 insertions(+), 59 deletions(-) diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index 19c3afb5d..dc9458918 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -15,9 +15,9 @@ class DataLogger { public: - DataLogger(std::string path, Architecture& arch, - qc::QuantumComputation& qc) - : dataLoggingPath(std::move(path)), architecture(std::move(arch)), inputCircuit(qc), nqubits(arch.getNqubits()) { + DataLogger(std::string path, Architecture& arch, qc::QuantumComputation& qc) + : dataLoggingPath(std::move(path)), architecture(std::move(arch)), + inputCircuit(qc), nqubits(arch.getNqubits()) { initLog(); logArchitecture(architecture); logInputCircuit(inputCircuit); @@ -42,7 +42,9 @@ class DataLogger { void logFinalizeLayer( std::size_t layer, const qc::CompoundOperation& ops, const std::vector& singleQubitMultiplicity, - const std::map, std::pair>& twoQubitMultiplicity, + const std::map, + std::pair>& + twoQubitMultiplicity, const std::array& initialLayout, std::size_t finalNodeId, double finalCostFixed, double finalCostHeur, double finalLookaheadPenalty, diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 4eda21ffe..aa87cb7cc 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -27,8 +27,8 @@ struct Configuration { bool addMeasurementsToMappedCircuit = true; bool swapOnFirstLayer = false; - bool verbose = false; - bool debug = false; + bool verbose = false; + bool debug = false; std::string dataLoggingPath; // map to particular subgraph of architecture (in exact mapper) @@ -76,7 +76,9 @@ struct Configuration { [[nodiscard]] nlohmann::json json() const; [[nodiscard]] std::string toString() const { return json().dump(2); } - [[nodiscard]] bool dataLoggingEnabled() const { return !dataLoggingPath.empty(); } + [[nodiscard]] bool dataLoggingEnabled() const { + return !dataLoggingPath.empty(); + } void setTimeout(const std::size_t sec) { timeout = sec; } [[nodiscard]] bool swapLimitsEnabled() const { diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index bae62f20d..5b94e5ebf 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -64,7 +64,8 @@ void DataLogger::openNewLayer(std::size_t layer) { } for (std::size_t i = searchNodesLogFiles.size(); i <= layer; ++i) { - searchNodesLogFiles.emplace_back(dataLoggingPath + "nodes_layer_" + std::to_string(i) + ".csv"); + searchNodesLogFiles.emplace_back(dataLoggingPath + "nodes_layer_" + + std::to_string(i) + ".csv"); if (!searchNodesLogFiles.at(i).good()) { deactivated = true; std::cerr << "[data-logging] Error opening file: " << dataLoggingPath @@ -77,7 +78,9 @@ void DataLogger::openNewLayer(std::size_t layer) { void DataLogger::logFinalizeLayer( std::size_t layer, const qc::CompoundOperation& ops, const std::vector& singleQubitMultiplicity, - const std::map, std::pair>& twoQubitMultiplicity, + const std::map, + std::pair>& + twoQubitMultiplicity, const std::array& initialLayout, std::size_t finalNodeId, double finalCostFixed, double finalCostHeur, double finalLookaheadPenalty, diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index d8c0fe29b..4ae8be68a 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -390,7 +390,7 @@ void HeuristicMapper::mapUnmappedGates( for (std::uint16_t physQbit = 0; physQbit < architecture.getNqubits(); ++physQbit) { if (qubits.at(physQbit) == -1) { - locations.at(q) = static_cast(physQbit); + locations.at(q) = static_cast(physQbit); qubits.at(physQbit) = static_cast(q); break; } @@ -1041,12 +1041,11 @@ void HeuristicMapper::Node::updateHeuristicCost( // qubits with higher fidelity double savingsPotential = 0.; if (considerFidelity) { - for (std::uint16_t logQbit = 0U; logQbit < arch.getNqubits(); - ++logQbit) { + for (std::uint16_t logQbit = 0U; logQbit < arch.getNqubits(); ++logQbit) { if (singleQubitGateMultiplicity.at(logQbit) == 0) { continue; } - double qbitSavings = 0; + double qbitSavings = 0; const double currFidelity = arch.getSingleQubitFidelityCost( static_cast(locations.at(logQbit))); for (std::uint16_t physQbit = 0U; physQbit < arch.getNqubits(); diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index a2980da0b..84d8805f5 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -1875,12 +1875,12 @@ def visualize_search_graph( arch_y_max + abs(arch_y_max - arch_y_min) * architecture_border, # type: ignore[operator] ] x_diff = ( - full_plotly_settings["architecture_xaxis"]["range"][1] # type: ignore[index] - - full_plotly_settings["architecture_xaxis"]["range"][0] # type: ignore[index] + full_plotly_settings["architecture_xaxis"]["range"][1] # type: ignore[index] + - full_plotly_settings["architecture_xaxis"]["range"][0] # type: ignore[index] ) y_diff = ( - full_plotly_settings["architecture_yaxis"]["range"][1] # type: ignore[index] - - full_plotly_settings["architecture_yaxis"]["range"][0] # type: ignore[index] + full_plotly_settings["architecture_yaxis"]["range"][1] # type: ignore[index] + - full_plotly_settings["architecture_yaxis"]["range"][0] # type: ignore[index] ) if x_diff == 0: mid = full_plotly_settings["architecture_xaxis"]["range"][0] # type: ignore[index] diff --git a/src/utils.cpp b/src/utils.cpp index 825bde82b..6d4da8f1b 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -112,7 +112,8 @@ void Dijkstra::buildEdgeSkipTable(const Matrix& distanceTable, const std::size_t n = distanceTable.size(); for (std::size_t k = 1; k <= n; ++k) { // k...number of edges to be skipped along each path - edgeSkipDistanceTable.emplace_back(n, std::vector(n, std::numeric_limits::max())); + edgeSkipDistanceTable.emplace_back( + n, std::vector(n, std::numeric_limits::max())); Matrix* currentTable = &edgeSkipDistanceTable.back(); for (std::size_t q = 0; q < n; ++q) { currentTable->at(q).at(q) = 0.; diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 983fe0817..df7fbd5e3 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -316,7 +316,8 @@ TEST(Functionality, DataLogger) { architecture.getFidelityTable()); // json does not support inf values, instead nlohmann::json replaces inf // with null - const auto& singleQubitFidelityCosts = architecture.getSingleQubitFidelityCosts(); + const auto& singleQubitFidelityCosts = + architecture.getSingleQubitFidelityCosts(); for (std::size_t i = 0; i < singleQubitFidelityCosts.size(); ++i) { if (std::isinf(singleQubitFidelityCosts[i])) { EXPECT_TRUE(fidelityJson["single_qubit_fidelity_costs"][i].is_null()); @@ -355,7 +356,7 @@ TEST(Functionality, DataLogger) { FAIL() << "Could not open file " << settings.dataLoggingPath << "mapping_result.json"; } - const auto resultJson = nlohmann::json::parse(resultFile); + const auto resultJson = nlohmann::json::parse(resultFile); const auto& configJson = resultJson["config"]; EXPECT_EQ(configJson["add_measurements_to_mapped_circuit"], settings.addMeasurementsToMappedCircuit); @@ -387,15 +388,14 @@ TEST(Functionality, DataLogger) { EXPECT_EQ(outCircJson["gates"], results.output.gates); EXPECT_EQ(outCircJson["name"], results.output.name); EXPECT_EQ(outCircJson["qubits"], results.output.qubits); - EXPECT_EQ(outCircJson["single_qubit_gates"], - results.output.singleQubitGates); + EXPECT_EQ(outCircJson["single_qubit_gates"], results.output.singleQubitGates); const auto& statJson = resultJson["statistics"]; EXPECT_EQ(statJson["additional_gates"], (static_cast>( - results.output.gates) - - static_cast>( - results.input.gates))); + results.output.gates) - + static_cast>( + results.input.gates))); EXPECT_EQ(statJson["layers"], results.input.layers); EXPECT_EQ(statJson["mapping_time"], results.time); EXPECT_EQ(statJson["swaps"], results.output.swaps); @@ -416,8 +416,7 @@ TEST(Functionality, DataLogger) { EXPECT_EQ(benchmarkJson["time_per_node"], results.heuristicBenchmark.timePerNode); const auto& benchmarkLayersJson = benchmarkJson["layers"]; - EXPECT_EQ(benchmarkLayersJson.size(), - results.layerHeuristicBenchmark.size()); + EXPECT_EQ(benchmarkLayersJson.size(), results.layerHeuristicBenchmark.size()); for (std::size_t i = 0; i < results.layerHeuristicBenchmark.size(); ++i) { EXPECT_EQ(benchmarkLayersJson[i]["average_branching_factor"], results.layerHeuristicBenchmark.at(i).averageBranchingFactor); @@ -466,18 +465,17 @@ TEST(Functionality, DataLogger) { FAIL() << "Could not open file " << settings.dataLoggingPath << "layer_" << i << ".json"; } - const auto layerJson = nlohmann::json::parse(layerFile); + const auto layerJson = nlohmann::json::parse(layerFile); const std::size_t finalNodeId = layerJson["final_node_id"]; EXPECT_EQ(layerJson["initial_layout"].size(), architecture.getNqubits()); EXPECT_EQ(layerJson["single_qubit_multiplicity"].size(), architecture.getNqubits()); - auto layerNodeFile = - std::ifstream(settings.dataLoggingPath + "nodes_layer_" + - std::to_string(i) + ".csv"); + auto layerNodeFile = std::ifstream( + settings.dataLoggingPath + "nodes_layer_" + std::to_string(i) + ".csv"); if (!layerNodeFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "nodes_layer_" << i << ".csv"; } std::string line; bool foundFinalNode = false; @@ -486,23 +484,23 @@ TEST(Functionality, DataLogger) { if (line.empty()) { continue; } - std::string col = 0; - std::size_t nodeId = 0; - std::size_t parentId = 0; - std::size_t depth = 0; - std::size_t isValidMapping = 0; - double costFixed = 0.; - double costHeur = 0.; - double lookaheadPenalty = 0.; - std::vector layout{}; + std::string col = 0; + std::size_t nodeId = 0; + std::size_t parentId = 0; + std::size_t depth = 0; + std::size_t isValidMapping = 0; + double costFixed = 0.; + double costHeur = 0.; + double lookaheadPenalty = 0.; + std::vector layout{}; std::vector> swaps{}; std::stringstream lineStream(line); if (std::getline(lineStream, col, ';')) { nodeId = std::stoull(col); nodeIds.insert(nodeId); } else { - FAIL() << "Missing value for node id in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + FAIL() << "Missing value for node id in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { parentId = std::stoull(col); @@ -511,42 +509,42 @@ TEST(Functionality, DataLogger) { } } else { FAIL() << "Missing value for parent node id in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { costFixed = std::stod(col); } else { - FAIL() << "Missing value for fixed cost in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + FAIL() << "Missing value for fixed cost in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { costHeur = std::stod(col); } else { FAIL() << "Missing value for heuristic cost in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { lookaheadPenalty = std::stod(col); } else { FAIL() << "Missing value for lookahead penalty in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { isValidMapping = std::stoull(col); if (isValidMapping > 1) { FAIL() << "Non-boolean value " << isValidMapping - << " for isValidMapping in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << " for isValidMapping in " << settings.dataLoggingPath + << "nodes_layer_" << i << ".csv"; } } else { FAIL() << "Missing value for isValidMapping in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { depth = std::stoull(col); } else { FAIL() << "Missing value for depth in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { std::stringstream qubitMapBuffer(col); @@ -559,7 +557,7 @@ TEST(Functionality, DataLogger) { EXPECT_EQ(layout.size(), architecture.getNqubits()); } else { FAIL() << "Missing value for layout in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { std::stringstream swapBuffer(col); @@ -587,7 +585,7 @@ TEST(Functionality, DataLogger) { } if (!foundFinalNode) { FAIL() << "Could not find final node in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "nodes_layer_" << i << ".csv"; } } @@ -957,8 +955,8 @@ TEST(HeuristicTestFidelity, RemapSingleQubit) { const double c0 = -std::log2(1 - e0); const double expectedFidelity = 3 * c3 + 3 * c0 + 3 * c0 + // SWAPs - 5 * c1 + // Xs - 5 * c1; // CXs + 5 * c1 + // Xs + 5 * c1; // CXs EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); } @@ -1040,7 +1038,7 @@ TEST(HeuristicTestFidelity, QubitRideAlong) { const double c1 = -std::log2(1 - e1); const double expectedFidelity = 3 * c4 + 3 * c3 + 3 * c4 + 3 * c3 + // SWAPs - 5 * c1 + 5 * c1; // CXs + 5 * c1 + 5 * c1; // CXs EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); } @@ -1088,7 +1086,8 @@ TEST(HeuristicTestFidelity, SingleQubitsCompete) { EXPECT_EQ(result.input.layers, 1); EXPECT_EQ(result.output.swaps, 1); - const double expectedFidelity = -3 * std::log2(1 - 0.1) // SWAPs - - std::log2(1 - 0.8) - std::log2(1 - 0.1); // Xs + const double expectedFidelity = -3 * std::log2(1 - 0.1) // SWAPs + - std::log2(1 - 0.8) - + std::log2(1 - 0.1); // Xs EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); } From 75f4785321b9591a8de2346fde469bdcc985d27e Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Tue, 31 Oct 2023 02:56:09 +0100 Subject: [PATCH 014/108] fixing var definition errors --- include/DataLogger.hpp | 4 ++-- test/test_heuristic.cpp | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index 19c3afb5d..a39fab34f 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -17,7 +17,7 @@ class DataLogger { public: DataLogger(std::string path, Architecture& arch, qc::QuantumComputation& qc) - : dataLoggingPath(std::move(path)), architecture(std::move(arch)), inputCircuit(qc), nqubits(arch.getNqubits()) { + : dataLoggingPath(path), architecture(arch), nqubits(arch.getNqubits()), inputCircuit(qc) { initLog(); logArchitecture(architecture); logInputCircuit(inputCircuit); @@ -60,7 +60,7 @@ class DataLogger { void close(); protected: - std::string& dataLoggingPath; + std::string dataLoggingPath; Architecture& architecture; std::uint16_t nqubits; qc::QuantumComputation& inputCircuit; diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 983fe0817..322777fad 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -440,22 +440,22 @@ TEST(Functionality, DataLogger) { FAIL() << "Could not open file " << settings.dataLoggingPath << "input.qasm"; } - std::stringstream fileBuffer; - fileBuffer << inputQasmFile.rdbuf(); - std::stringstream qasmBuffer; - qc.dumpOpenQASM(qasmBuffer); - EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); + std::stringstream inputFileBuffer; + inputFileBuffer << inputQasmFile.rdbuf(); + std::stringstream inputQasmBuffer; + qc.dumpOpenQASM(inputQasmBuffer); + EXPECT_EQ(inputFileBuffer.str(), inputQasmBuffer.str()); auto outputQasmFile = std::ifstream(settings.dataLoggingPath + "output.qasm"); if (!outputQasmFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << "output.qasm"; } - std::stringstream fileBuffer; - fileBuffer << outputQasmFile.rdbuf(); - std::stringstream qasmBuffer; - mapper->dumpResult(qasmBuffer, qc::Format::OpenQASM); - EXPECT_EQ(fileBuffer.str(), qasmBuffer.str()); + std::stringstream outputFileBuffer; + outputFileBuffer << outputQasmFile.rdbuf(); + std::stringstream outputQasmBuffer; + mapper->dumpResult(outputQasmBuffer, qc::Format::OpenQASM); + EXPECT_EQ(outputFileBuffer.str(), outputQasmBuffer.str()); // checking logged search graph info against known values (correct qubit // number, valid layouts, correct data types in all csv fields, etc.) From 6c06e8ddb7b6e0523c57384902a76cb64c095875 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 01:58:23 +0000 Subject: [PATCH 015/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/DataLogger.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index 1dc1ae645..a12913477 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -15,9 +15,9 @@ class DataLogger { public: - DataLogger(std::string path, Architecture& arch, - qc::QuantumComputation& qc) - : dataLoggingPath(path), architecture(arch), nqubits(arch.getNqubits()), inputCircuit(qc) { + DataLogger(std::string path, Architecture& arch, qc::QuantumComputation& qc) + : dataLoggingPath(path), architecture(arch), nqubits(arch.getNqubits()), + inputCircuit(qc) { initLog(); logArchitecture(architecture); logInputCircuit(inputCircuit); From 855037abffc3ead889cb03c07036ddc12bc49872 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Tue, 31 Oct 2023 03:38:12 +0100 Subject: [PATCH 016/108] fixing datalogger test --- test/test_heuristic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 0d6e9b3e1..e5466282d 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -484,7 +484,7 @@ TEST(Functionality, DataLogger) { if (line.empty()) { continue; } - std::string col = 0; + std::string col; std::size_t nodeId = 0; std::size_t parentId = 0; std::size_t depth = 0; From a4541582adaa5725150723e17f8d4b52b4a51f53 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 3 Nov 2023 15:22:49 +0100 Subject: [PATCH 017/108] fix dashing regex --- include/DataLogger.hpp | 1 - .../visualization/visualize_search_graph.py | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index a12913477..7eed5f178 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -58,7 +58,6 @@ class DataLogger { void logOutputCircuit(qc::QuantumComputation& qc) { qc.dump(dataLoggingPath + "/output.qasm", qc::Format::OpenQASM); } - // TODO: layering, initial layout void close(); protected: diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index 84d8805f5..1679b66b5 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -1142,8 +1142,13 @@ def _visualize_search_graph_check_parameters( if ColorValidator.perform_validate_coerce(search_edges_color, allow_number=False) is None: raise TypeError(ColorValidator("search_edges_color", "visualize_search_graph").description()) - if search_edges_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not re.match( - r"^(\d+(px|%)?(\s*\d+(px|%)?)*)$", search_edges_dash + if ( + search_edges_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and + not ( + re.match(r"^(\d+(\s*\d+)*)$", search_edges_dash) or + re.match(r"^(\d+px(\s*\d+px)*)$", search_edges_dash) or + re.match(r"^(\d+%(\s*\d+%)*)$", search_edges_dash) + ) ): msg = ( 'search_edges_dash must be one of "solid", "dot", "dash", "longdash", "dashdot", "longdashdot" or a string containing a dash length list in ' @@ -1197,8 +1202,13 @@ def _visualize_search_graph_check_parameters( if ColorValidator.perform_validate_coerce(stems_color, allow_number=False) is None: raise TypeError(ColorValidator("stems_color", "visualize_search_graph").description()) - if stems_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not re.match( - r"^(\d+(px|%)?(\s*\d+(px|%)?)*)$", stems_dash + if ( + stems_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and + not ( + re.match(r"^(\d+(\s*\d+)*)$", stems_dash) or + re.match(r"^(\d+px(\s*\d+px)*)$", stems_dash) or + re.match(r"^(\d+%(\s*\d+%)*)$", stems_dash) + ) ): msg = ( 'stems_dash must be one of "solid", "dot", "dash", "longdash", "dashdot", "longdashdot" or a string containing a dash length list in ' From 9fdb76ef66dc93a3a1b788af3123c8f1ad334024 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 3 Nov 2023 16:40:43 +0100 Subject: [PATCH 018/108] fixing swap visualization bug --- src/mqt/qmap/compile.py | 1 + src/mqt/qmap/visualization/search_visualizer.py | 4 ++-- .../qmap/visualization/visualize_search_graph.py | 14 +++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 4bdfe93b6..c8ec1f556 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -59,6 +59,7 @@ def compile( # noqa: A001 arch: str | Arch | Architecture | Backend | None, calibration: str | BackendProperties | Target | None = None, method: str | Method = "heuristic", + consider_fidelity: bool = False, initial_layout: str | InitialLayout = "dynamic", layering: str | Layering = "individual_gates", lookaheads: int | None = 15, diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py index 15e5a53e3..f311d388c 100644 --- a/src/mqt/qmap/visualization/search_visualizer.py +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -9,7 +9,7 @@ from typing_extensions import Self -from mqt.qmap.visualization.visualize_search_graph import Position, SearchNode, visualize_search_graph +from mqt.qmap.visualization.visualize_search_graph import SearchNode, visualize_search_graph if TYPE_CHECKING: from ipywidgets import Widget @@ -54,7 +54,7 @@ def close(self) -> None: def visualize_search_graph( self, layer: int | Literal["interactive"] = "interactive", # 'interactive' (slider menu) | index - architecture_node_positions: MutableMapping[int, Position] | None = None, + architecture_node_positions: MutableMapping[int, tuple[float, float]] | None = None, architecture_layout: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"] = "sfdp", search_node_layout: Literal[ "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index 1679b66b5..c6c4eded9 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -55,8 +55,6 @@ class _SwapArrowProps(TypedDict): from plotly.subplots import make_subplots from walkerlayout import WalkerLayouting -# TODO: show_swaps not working! - @dataclass class _TwoQbitMultiplicity: @@ -705,6 +703,7 @@ def _visualize_layout( arch_node_positions: MutableMapping[int, Position], initial_layout: list[int], considered_qubit_colors: MutableMapping[int, str], + show_swaps: bool, swap_arrow_offset: float, arch_x_arrow_spacing: float, arch_y_arrow_spacing: float, @@ -727,7 +726,7 @@ def _visualize_layout( ) stats = go.layout.Annotation(**plotly_settings["stats_legend"]) annotations: list[go.layout.Annotation] = [] - if len(swaps) > 0: + if show_swaps and len(swaps) > 0: annotations = _draw_swap_arrows( arch_node_positions, initial_layout, @@ -875,7 +874,7 @@ def _load_layer_data( ] shuffle(considered_qubits_color_codes) considered_qubit_colors: dict[int, str] = {} - for q in considered_qubit_colors: + for q in considered_qubit_color_groups: considered_qubit_colors[q] = considered_qubits_color_codes[considered_qubit_color_groups[q]] return ( # type: ignore[return-value] @@ -1591,8 +1590,8 @@ def visualize_search_graph( # 'architecture_xaxis': settings for plotly.graph_objects.layout.XAxis # 'architecture_yaxis': settings for plotly.graph_objects.layout.YAxis # } - # TODO: show archticture edge labels (and control text?) - # TODO: control hover text of search (especially for multiple points per node!) and architecture nodes? + # TODO: show archticture edge labels (and make text adjustable) + # TODO: make hover text of search (especially for multiple points per node!) and architecture nodes adjustable ) -> Widget: """Creates a widget to visualize a search graph. @@ -1950,6 +1949,7 @@ def visualize_search_node_layout( architecture_node_positions, # type: ignore[arg-type] initial_layout, considered_qubit_colors, + show_swaps, swap_arrow_offset, arch_x_arrow_spacing, # type: ignore[arg-type] arch_y_arrow_spacing, # type: ignore[arg-type] @@ -2125,7 +2125,7 @@ def update_layer(new_layer: int) -> None: layer_slider = interactive( update_layer, new_layer=IntSlider( - min=0, max=number_of_layers, step=1, value=0, description="Layer:", layout=Layout(width=f"{width-80}px") + min=0, max=number_of_layers-1, step=1, value=0, description="Layer:", layout=Layout(width=f"{width-80}px") ), ) From 2cf8c7ad320e624f9d523fddac24747409ebacaa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:41:35 +0000 Subject: [PATCH 019/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../visualization/visualize_search_graph.py | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index c6c4eded9..7ca1084af 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -1141,13 +1141,10 @@ def _visualize_search_graph_check_parameters( if ColorValidator.perform_validate_coerce(search_edges_color, allow_number=False) is None: raise TypeError(ColorValidator("search_edges_color", "visualize_search_graph").description()) - if ( - search_edges_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and - not ( - re.match(r"^(\d+(\s*\d+)*)$", search_edges_dash) or - re.match(r"^(\d+px(\s*\d+px)*)$", search_edges_dash) or - re.match(r"^(\d+%(\s*\d+%)*)$", search_edges_dash) - ) + if search_edges_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not ( + re.match(r"^(\d+(\s*\d+)*)$", search_edges_dash) + or re.match(r"^(\d+px(\s*\d+px)*)$", search_edges_dash) + or re.match(r"^(\d+%(\s*\d+%)*)$", search_edges_dash) ): msg = ( 'search_edges_dash must be one of "solid", "dot", "dash", "longdash", "dashdot", "longdashdot" or a string containing a dash length list in ' @@ -1201,13 +1198,10 @@ def _visualize_search_graph_check_parameters( if ColorValidator.perform_validate_coerce(stems_color, allow_number=False) is None: raise TypeError(ColorValidator("stems_color", "visualize_search_graph").description()) - if ( - stems_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and - not ( - re.match(r"^(\d+(\s*\d+)*)$", stems_dash) or - re.match(r"^(\d+px(\s*\d+px)*)$", stems_dash) or - re.match(r"^(\d+%(\s*\d+%)*)$", stems_dash) - ) + if stems_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not ( + re.match(r"^(\d+(\s*\d+)*)$", stems_dash) + or re.match(r"^(\d+px(\s*\d+px)*)$", stems_dash) + or re.match(r"^(\d+%(\s*\d+%)*)$", stems_dash) ): msg = ( 'stems_dash must be one of "solid", "dot", "dash", "longdash", "dashdot", "longdashdot" or a string containing a dash length list in ' @@ -2125,7 +2119,7 @@ def update_layer(new_layer: int) -> None: layer_slider = interactive( update_layer, new_layer=IntSlider( - min=0, max=number_of_layers-1, step=1, value=0, description="Layer:", layout=Layout(width=f"{width-80}px") + min=0, max=number_of_layers - 1, step=1, value=0, description="Layer:", layout=Layout(width=f"{width-80}px") ), ) From 613aef69afba1e3c6a65799d139864108b4d01c9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 18:25:44 +0000 Subject: [PATCH 020/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qmap/visualization/__init__.py | 1 + .../qmap/visualization/search_visualizer.py | 1 + .../visualization/visualize_search_graph.py | 35 ++++++++----------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/mqt/qmap/visualization/__init__.py b/src/mqt/qmap/visualization/__init__.py index 98036d23b..8d72bca33 100644 --- a/src/mqt/qmap/visualization/__init__.py +++ b/src/mqt/qmap/visualization/__init__.py @@ -3,6 +3,7 @@ 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. """ + from __future__ import annotations from mqt.qmap.visualization.search_visualizer import SearchVisualizer diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py index f311d388c..8b2d727b3 100644 --- a/src/mqt/qmap/visualization/search_visualizer.py +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -1,4 +1,5 @@ """A class handling data logging for a search process and providing methods to visualize that data.""" + from __future__ import annotations from tempfile import TemporaryDirectory diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index 7ca1084af..30973fbc9 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -1,4 +1,5 @@ """Function for visualization of search graphs.""" + from __future__ import annotations import json @@ -427,12 +428,8 @@ def _prepare_search_graph_scatter_data( for n0, n1 in search_graph.edges(): n0_i = nodes_indices[n0] n1_i = nodes_indices[n1] - edge_x.append(node_x[n0_i]) - edge_x.append(node_x[n1_i]) - edge_x.append(None) - edge_y.append(node_y[n0_i]) - edge_y.append(node_y[n1_i]) - edge_y.append(None) + edge_x.extend((node_x[n0_i], node_x[n1_i], None)) + edge_y.extend((node_y[n0_i], node_y[n1_i], None)) if use3d: for i in range(len(node_z)): edge_z[i].append(node_z[i][n0_i]) @@ -537,12 +534,8 @@ def _draw_architecture_edges( x0, y0 = arch_pos[n1] x1, y1 = arch_pos[n2] mid_point = ((x0 + x1) / 2, (y0 + y1) / 2) - edge_x.append(x0) - edge_x.append(x1) - edge_x.append(None) - edge_y.append(y0) - edge_y.append(y1) - edge_y.append(None) + edge_x.extend((x0, x1, None)) + edge_y.extend((y0, y1, None)) edge_label_x.append(mid_point[0]) edge_label_y.append(mid_point[1]) edge_label_text.append("{:.3f}".format(arch_graph[n1][n2]["weight"])) @@ -1080,11 +1073,11 @@ def _visualize_search_graph_check_parameters( msg = "architecture_node_positions must be a dict of the form {qubit_index: (x: float, y: float)}" raise TypeError(msg) - if architecture_layout not in ["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"]: + if architecture_layout not in {"dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"}: msg = 'architecture_layout must be one of "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"' raise TypeError(msg) - if search_node_layout not in [ + if search_node_layout not in { "walker", "dot", "neato", @@ -1094,7 +1087,7 @@ def _visualize_search_graph_check_parameters( "twopi", "osage", "patchwork", - ]: + }: msg = 'search_node_layout must be one of "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"' raise TypeError(msg) @@ -1118,7 +1111,7 @@ def _visualize_search_graph_check_parameters( msg = "use3d must be a boolean" # type: ignore[unreachable] raise TypeError(msg) - if projection != "orthographic" and projection != "perspective": + if projection not in {"orthographic", "perspective"}: msg = 'projection must be either "orthographic" or "perspective"' # type: ignore[unreachable] raise TypeError(msg) @@ -1141,7 +1134,7 @@ def _visualize_search_graph_check_parameters( if ColorValidator.perform_validate_coerce(search_edges_color, allow_number=False) is None: raise TypeError(ColorValidator("search_edges_color", "visualize_search_graph").description()) - if search_edges_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not ( + if search_edges_dash not in {"solid", "dot", "dash", "longdash", "dashdot", "longdashdot"} and not ( re.match(r"^(\d+(\s*\d+)*)$", search_edges_dash) or re.match(r"^(\d+px(\s*\d+px)*)$", search_edges_dash) or re.match(r"^(\d+%(\s*\d+%)*)$", search_edges_dash) @@ -1156,7 +1149,7 @@ def _visualize_search_graph_check_parameters( msg = "tapered_search_layer_heights must be a boolean" # type: ignore[unreachable] raise TypeError(msg) - if show_layout not in ["hover", "click"] and show_layout is not None: + if show_layout not in {"hover", "click"} and show_layout is not None: msg = 'show_layout must be one of "hover", "click" or None' raise TypeError(msg) hide_layout = show_layout is None @@ -1198,7 +1191,7 @@ def _visualize_search_graph_check_parameters( if ColorValidator.perform_validate_coerce(stems_color, allow_number=False) is None: raise TypeError(ColorValidator("stems_color", "visualize_search_graph").description()) - if stems_dash not in ["solid", "dot", "dash", "longdash", "dashdot", "longdashdot"] and not ( + if stems_dash not in {"solid", "dot", "dash", "longdash", "dashdot", "longdashdot"} and not ( re.match(r"^(\d+(\s*\d+)*)$", stems_dash) or re.match(r"^(\d+px(\s*\d+px)*)$", stems_dash) or re.match(r"^(\d+%(\s*\d+%)*)$", stems_dash) @@ -1225,7 +1218,7 @@ def _visualize_search_graph_check_parameters( ( not isinstance(plotly_settings[key], dict) or key - not in [ + not in { "layout", "arrows", "stats_legend", @@ -1239,7 +1232,7 @@ def _visualize_search_graph_check_parameters( "search_zaxis", "architecture_xaxis", "architecture_yaxis", - ] + } ) for key in plotly_settings ): From b8e54ea1d070313c59648ec349ec5214dc95a51e Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Nov 2023 20:01:18 +0100 Subject: [PATCH 021/108] =?UTF-8?q?=F0=9F=A9=B9=20leftovers=20from=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mapper.cpp | 9 +++++---- src/heuristic/HeuristicMapper.cpp | 12 ++++++------ test/test_heuristic.cpp | 10 +++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 51321414d..15287e99b 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -322,8 +322,8 @@ void Mapper::countGates(decltype(qcMapped.cbegin()) it, ++info.singleQubitGates; ++info.gates; auto q1 = static_cast(g->getTargets()[0]); - if (architecture.isFidelityAvailable()) { - info.totalLogFidelity += architecture.getSingleQubitFidelityCost(q1); + if (architecture->isFidelityAvailable()) { + info.totalLogFidelity += architecture->getSingleQubitFidelityCost(q1); } } else { assert(g->getType() == qc::X); @@ -332,8 +332,9 @@ void Mapper::countGates(decltype(qcMapped.cbegin()) it, auto q1 = static_cast((*(g->getControls().begin())).qubit); auto q2 = static_cast(g->getTargets()[0]); - if (architecture.isFidelityAvailable()) { - info.totalLogFidelity += architecture.getTwoQubitFidelityCost(q1, q2); + if (architecture->isFidelityAvailable()) { + info.totalLogFidelity += + architecture->getTwoQubitFidelityCost(q1, q2); } } continue; diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 8e8f90177..5131a3711 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -10,7 +10,7 @@ void HeuristicMapper::map(const Configuration& configuration) { if (configuration.dataLoggingEnabled()) { dataLogger = std::make_unique(configuration.dataLoggingPath, - architecture, qc); + *architecture, qc); } results = MappingResults{}; results.config = configuration; @@ -21,7 +21,7 @@ void HeuristicMapper::map(const Configuration& configuration) { << " not suitable for heuristic mapper!" << std::endl; return; } - if (config.considerFidelity && !architecture.isFidelityAvailable()) { + if (config.considerFidelity && !architecture->isFidelityAvailable()) { std::cerr << "No calibration data available for this architecture! " << "Performing mapping without considering fidelity." << std::endl; @@ -387,7 +387,7 @@ void HeuristicMapper::mapUnmappedGates( if (locations.at(q) == DEFAULT_POSITION) { // TODO: consider fidelity // map to first free physical qubit - for (std::uint16_t physQbit = 0; physQbit < architecture.getNqubits(); + for (std::uint16_t physQbit = 0; physQbit < architecture->getNqubits(); ++physQbit) { if (qubits.at(physQbit) == -1) { locations.at(q) = static_cast(physQbit); @@ -486,8 +486,8 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { Node node(nextNodeId++); // number of single qubit gates acting on each logical qubit in the current // layer - SingleQubitMultiplicity singleQubitGateMultiplicity(architecture.getNqubits(), - 0); + SingleQubitMultiplicity singleQubitGateMultiplicity( + architecture->getNqubits(), 0); // number of two qubit gates acting on each logical qubit edge in the current // layer where the first number in the value pair corresponds to the number of // edges having their gates given as (control, target) in the key, and the @@ -608,7 +608,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { } if (results.config.dataLoggingEnabled()) { - qc::CompoundOperation compOp(architecture.getNqubits()); + qc::CompoundOperation compOp(architecture->getNqubits()); for (const auto& gate : layers.at(layer)) { std::unique_ptr op = gate.op->clone(); compOp.emplace_back(op); diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index dd44b27e0..273d2d448 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -271,8 +271,8 @@ TEST(Functionality, DataLogger) { architecture.setName("test_architecture"); qc::QuantumComputation qc{4, 4}; - qc.x(0, qc::Control{1}); - qc.x(2, qc::Control{3}); + qc.cx(1, 0); + qc.cx(3, 2); qc.setName("test_circ"); Configuration settings{}; @@ -912,7 +912,7 @@ TEST(HeuristicTestFidelity, RemapSingleQubit) { qc::QuantumComputation qc{6, 6}; for (std::size_t i = 0; i < 5; ++i) { - qc.x(0, qc::Control{2}); + qc.cx(2, 0); qc.x(3); } @@ -997,8 +997,8 @@ TEST(HeuristicTestFidelity, QubitRideAlong) { qc::QuantumComputation qc{7, 7}; for (std::size_t i = 0; i < 5; ++i) { - qc.x(0, qc::Control{3}); - qc.x(4, qc::Control{6}); + qc.cx(3, 0); + qc.cx(6, 4); } for (size_t i = 0; i < 7; ++i) { From 733069e3c2eeac59d5d917401b16c2e9563959bd Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Nov 2023 20:15:54 +0100 Subject: [PATCH 022/108] =?UTF-8?q?=F0=9F=9A=A8=20some=20new=20ruff=20fixe?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../visualization/visualize_search_graph.py | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index 30973fbc9..37999fa15 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -371,16 +371,10 @@ def _prepare_search_graph_scatter_data( min_nz = min(min_nz, nz) max_nz = max(max_nz, nz) if draw_stems: - stem_x.append(nx) - stem_y.append(ny) - stem_z.append(min_nz) - stem_x.append(nx) - stem_y.append(ny) - stem_z.append(max_nz) - stem_x.append(None) - stem_y.append(None) - stem_z.append(None) - if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: + stem_x.extend((nx, nx, None)) + stem_y.extend((ny, ny, None)) + stem_z.extend((min_nz, max_nz, None)) + if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: # noqa: PLR0916 min_x = nx max_x = nx min_y = ny @@ -417,14 +411,12 @@ def _prepare_search_graph_scatter_data( else: node_color[i].append(curr_color) - if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: + if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: # noqa: PLR0916 msg = "No nodes in search graph." raise ValueError(msg) if draw_edges: - nodes_indices = {} - for i, n in enumerate(list(search_graph.nodes())): - nodes_indices[n] = i + nodes_indices = {n: i for i, n in enumerate(search_graph.nodes())} for n0, n1 in search_graph.edges(): n0_i = nodes_indices[n0] n1_i = nodes_indices[n1] @@ -1112,7 +1104,7 @@ def _visualize_search_graph_check_parameters( raise TypeError(msg) if projection not in {"orthographic", "perspective"}: - msg = 'projection must be either "orthographic" or "perspective"' # type: ignore[unreachable] + msg = 'projection must be either "orthographic" or "perspective"' raise TypeError(msg) if not isinstance(width, int) or width < 1: # type: ignore[redundant-expr] @@ -1782,9 +1774,9 @@ def visualize_search_graph( if architecture_node_positions is None: for node in arch_graph.nodes: - print(node, arch_graph.nodes[node]) + print(node, arch_graph.nodes[node]) # noqa: T201 for edge in arch_graph.edges: - print(edge, arch_graph.edges[edge]) + print(edge, arch_graph.edges[edge]) # noqa: T201 architecture_node_positions = graphviz_layout(arch_graph, prog=architecture_layout) elif len(architecture_node_positions) != len(arch_graph.nodes): msg = f"architecture_node_positions must contain positions for all {len(arch_graph.nodes)} architecture nodes." From 4d6ff673a84b6aa1a9abe1e343d284184f75a1fa Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 9 Nov 2023 20:16:36 +0100 Subject: [PATCH 023/108] =?UTF-8?q?=F0=9F=A9=B9=20correctly=20pass=20the?= =?UTF-8?q?=20`consider=5Ffidelity`=20option=20through?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qmap/compile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 4e9eba0c5..109902bde 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -90,6 +90,7 @@ def compile( # noqa: A001 arch: The architecture to map to. calibration: The calibration to use. method: The mapping method to use. Either "heuristic" or "exact". Defaults to "heuristic". + consider_fidelity: Whether to consider the fidelity of the gates. Defaults to False. initial_layout: The initial layout to use. Defaults to "dynamic". layering: The layering strategy to use. Defaults to "individual_gates". lookaheads: The number of lookaheads to be used or None if no lookahead should be used. Defaults to 15. @@ -127,6 +128,7 @@ def compile( # noqa: A001 config = Configuration() config.method = Method(method) + config.consider_fidelity = consider_fidelity config.initial_layout = InitialLayout(initial_layout) config.layering = Layering(layering) config.encoding = Encoding(encoding) From 018c5c282ea76764599baae7dbca318f08a7a148 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 9 Nov 2023 23:58:22 +0100 Subject: [PATCH 024/108] layer splitting implementation --- include/DataLogger.hpp | 1 + include/Mapper.hpp | 24 +++++++++ include/configuration/Configuration.hpp | 7 ++- include/heuristic/HeuristicMapper.hpp | 14 ------ src/DataLogger.cpp | 50 +++++++++++++----- src/Mapper.cpp | 67 ++++++++++++++++++++++++- src/heuristic/HeuristicMapper.cpp | 62 +++++++++++++++-------- 7 files changed, 174 insertions(+), 51 deletions(-) diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index 7eed5f178..9a04548d0 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -51,6 +51,7 @@ class DataLogger { const std::array& finalLayout, const std::vector>& finalSwaps, std::size_t finalSearchDepth); + void splitLayer(); void logMappingResult(MappingResults& result); void logInputCircuit(qc::QuantumComputation& qc) { qc.dump(dataLoggingPath + "/input.qasm", qc::Format::OpenQASM); diff --git a/include/Mapper.hpp b/include/Mapper.hpp index f75771c50..ff9554777 100644 --- a/include/Mapper.hpp +++ b/include/Mapper.hpp @@ -18,6 +18,20 @@ #include #include +/** + * number of two-qubit gates acting on pairs of logical qubits in some layer + * where the keys correspond to logical qubit pairs ({q1, q2}, with q1<=q2) + * and the values to the number of gates acting on a pair in each direction + * (the first number with control=q1, target=q2 and the second the reverse). + * + * e.g., with multiplicity {{0,1},{2,3}} there are 2 gates with logical + * qubit 0 as control and qubit 1 as target, and 3 gates with 1 as control + * and 0 as target. + */ +using TwoQubitMultiplicity = + std::map>; +using SingleQubitMultiplicity = std::vector; + constexpr std::int16_t DEFAULT_POSITION = -1; constexpr double INITIAL_FIDELITY = 1.0; @@ -109,6 +123,16 @@ class Mapper { * a disjoint set of qubits */ virtual void createLayers(); + + /** + * @brief Splits the layer at the given index into two layers with half as many qubits acted on by gates in each layer + * + * @param index the index of the layer to be split + * @param singleQubitMultiplicity single qubit multiplicity of the layer to be split + * @param twoQubitMultiplicity two qubit multiplicity of the layer to be split + * @param arch architecture on which the circuit is mapped + */ + virtual void splitLayer(std::size_t index, SingleQubitMultiplicity& singleQubitMultiplicity, TwoQubitMultiplicity& twoQubitMultiplicity, Architecture& arch); /** * gates are put in the last layer (from the back of the circuit) in which diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index aa87cb7cc..93f5b9372 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -20,6 +20,8 @@ struct Configuration { // which method to use Method method = Method::Heuristic; + bool admissibleHeuristic = true; + bool considerFidelity = false; bool preMappingOptimizations = true; bool postMappingOptimizations = true; @@ -42,8 +44,6 @@ struct Configuration { // lookahead scheme settings bool lookahead = true; - bool admissibleHeuristic = true; - bool considerFidelity = false; std::size_t nrLookaheads = 15; double firstLookaheadFactor = 0.75; double lookaheadFactor = 0.5; @@ -56,6 +56,9 @@ struct Configuration { // timeout merely affects exact mapper std::size_t timeout = 3600000; // 60min timeout + + // after how many expanded nodes to split layer in heuristic mapper, 0 to disable + std::size_t splitLayerAfterExpandedNodes = 0; // encoding of at most and exactly one constraints in exact mapper Encoding encoding = Encoding::Commander; diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index 0e6fd586d..2a67d7467 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -11,20 +11,6 @@ #pragma once -/** - * number of two-qubit gates acting on pairs of logical qubits in some layer - * where the keys correspond to logical qubit pairs ({q1, q2}, with q1<=q2) - * and the values to the number of gates acting on a pair in each direction - * (the first number with control=q1, target=q2 and the second the reverse). - * - * e.g., with multiplicity {{0,1},{2,3}} there are 2 gates with logical - * qubit 0 as control and qubit 1 as target, and 3 gates with 1 as control - * and 0 as target. - */ -using TwoQubitMultiplicity = - std::map>; -using SingleQubitMultiplicity = std::vector; - class HeuristicMapper : public Mapper { public: using Mapper::Mapper; // import constructors from parent class diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index 5b94e5ebf..d39d97c51 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -58,12 +58,12 @@ void DataLogger::logArchitecture(Architecture& arch) { of.close(); }; -void DataLogger::openNewLayer(std::size_t layer) { +void DataLogger::openNewLayer(std::size_t layerIndex) { if (deactivated) { return; } - for (std::size_t i = searchNodesLogFiles.size(); i <= layer; ++i) { + for (std::size_t i = searchNodesLogFiles.size(); i <= layerIndex; ++i) { searchNodesLogFiles.emplace_back(dataLoggingPath + "nodes_layer_" + std::to_string(i) + ".csv"); if (!searchNodesLogFiles.at(i).good()) { @@ -76,7 +76,7 @@ void DataLogger::openNewLayer(std::size_t layer) { }; void DataLogger::logFinalizeLayer( - std::size_t layer, const qc::CompoundOperation& ops, + std::size_t layerIndex, const qc::CompoundOperation& ops, const std::vector& singleQubitMultiplicity, const std::map, std::pair>& @@ -91,19 +91,19 @@ void DataLogger::logFinalizeLayer( return; } - if (!searchNodesLogFiles.at(layer).is_open()) { - std::cerr << "[data-logging] Error: layer " << layer + if (!searchNodesLogFiles.at(layerIndex).is_open()) { + std::cerr << "[data-logging] Error: layer " << layerIndex << " has already been finalized" << std::endl; return; } - searchNodesLogFiles.at(layer).close(); + searchNodesLogFiles.at(layerIndex).close(); - auto of = std::ofstream(dataLoggingPath + "layer_" + std::to_string(layer) + + auto of = std::ofstream(dataLoggingPath + "layer_" + std::to_string(layerIndex) + ".json"); if (!of.good()) { deactivated = true; std::cerr << "[data-logging] Error opening file: " << dataLoggingPath - << "layer_" << layer << ".json" << std::endl; + << "layer_" << layerIndex << ".json" << std::endl; return; } nlohmann::json json; @@ -154,8 +154,32 @@ void DataLogger::logFinalizeLayer( of.close(); }; +void DataLogger::splitLayer() { + std::size_t layerIndex = searchNodesLogFiles.size() - 1; + if (searchNodesLogFiles.at(layerIndex).is_open()) { + std::cerr << "[data-logging] Error: layer " << layerIndex + << " has not been finalized before splitting" << std::endl; + return; + } + searchNodesLogFiles.pop_back(); + std::size_t splitIndex = 0; + while (std::filesystem::exists(dataLoggingPath + "nodes_layer_" + + std::to_string(layerIndex) + ".presplit-" + + std::to_string(splitIndex) + ".csv")) { + ++splitIndex; + } + std::filesystem::rename( + dataLoggingPath + "nodes_layer_" + std::to_string(layerIndex) + ".csv", + dataLoggingPath + "nodes_layer_" + std::to_string(layerIndex) + ".presplit-" + std::to_string(splitIndex) + ".csv" + ); + std::filesystem::rename( + dataLoggingPath + "layer_" + std::to_string(layerIndex) +".json", + dataLoggingPath + "layer_" + std::to_string(layerIndex) + ".presplit-" + std::to_string(splitIndex) + ".json" + ); +} + void DataLogger::logSearchNode( - std::size_t layer, std::size_t nodeId, std::size_t parentId, + std::size_t layerIndex, std::size_t nodeId, std::size_t parentId, double costFixed, double costHeur, double lookaheadPenalty, const std::array& qubits, bool validMapping, const std::vector>& swaps, @@ -164,14 +188,14 @@ void DataLogger::logSearchNode( return; } - if (layer >= searchNodesLogFiles.size()) { - openNewLayer(layer); + if (layerIndex >= searchNodesLogFiles.size()) { + openNewLayer(layerIndex); } - auto& of = searchNodesLogFiles.at(layer); + auto& of = searchNodesLogFiles.at(layerIndex); if (!of.is_open()) { deactivated = true; - std::cerr << "[data-logging] Error: layer " << layer + std::cerr << "[data-logging] Error: layer " << layerIndex << " has already been finalized" << std::endl; return; } diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 9de9443c2..cf8b6bc79 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -183,7 +183,72 @@ void Mapper::createLayers() { } } results.input.layers = layers.size(); -} +} + +void Mapper::splitLayer(std::size_t index, SingleQubitMultiplicity& singleQubitMultiplicity, TwoQubitMultiplicity& twoQubitMultiplicity, Architecture& arch) { + std::vector layer0 = {}; + std::vector layer1 = {}; + SingleQubitMultiplicity singleQubitMultiplicity0(arch.getNqubits(), 0); + SingleQubitMultiplicity singleQubitMultiplicity1(arch.getNqubits(), 0); + TwoQubitMultiplicity twoQubitMultiplicity0 = {}; + TwoQubitMultiplicity twoQubitMultiplicity1 = {}; + + bool even = true; + for (std::size_t q = 0; q < singleQubitMultiplicity.size(); ++q) { + if (singleQubitMultiplicity[q] == 0) { + continue; + } + if (even) { + singleQubitMultiplicity0[q] = singleQubitMultiplicity[q]; + } else { + singleQubitMultiplicity1[q] = singleQubitMultiplicity[q]; + } + even = !even; + } + even = false; + for (auto edge : twoQubitMultiplicity) { + if (even) { + twoQubitMultiplicity0.insert(edge); + } else { + twoQubitMultiplicity1.insert(edge); + } + even = !even; + } + + for (auto& gate : layers[index]) { + if (gate.singleQubit()) { + if (singleQubitMultiplicity0[gate.target] > 0) { + layer0.push_back(gate); + } else { + layer1.push_back(gate); + } + } else { + Edge edge; + if (gate.control < gate.target) { + edge = {gate.control, gate.target}; + } else { + edge = {gate.target, gate.control}; + } + if (twoQubitMultiplicity0.find(edge) != twoQubitMultiplicity0.end()) { + layer0.push_back(gate); + } else { + layer1.push_back(gate); + } + } + } + + singleQubitMultiplicity.clear(); + twoQubitMultiplicity.clear(); + for (auto q : singleQubitMultiplicity0) { + singleQubitMultiplicity.push_back(q); + } + for (auto edge : twoQubitMultiplicity0) { + twoQubitMultiplicity.insert(edge); + } + layers[index] = layer0; + layers.insert(layers.begin() + index + 1, layer1); + results.input.layers = layers.size(); + } std::size_t Mapper::getNextLayer(std::size_t idx) { auto next = idx + 1; diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 4ae8be68a..c505081c8 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -480,9 +480,11 @@ void HeuristicMapper::mapToMinDistance(const std::uint16_t source, } HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { + auto& config = results.config; nextNodeId = 0; std::unordered_set consideredQubits{}; + std::size_t consideredQubitsSingleGates = 0; Node node(nextNodeId++); // number of single qubit gates acting on each logical qubit in the current // layer @@ -495,11 +497,14 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { TwoQubitMultiplicity twoQubitGateMultiplicity{}; Node bestDoneNode(0); bool done = false; - const bool considerFidelity = results.config.considerFidelity; - + const bool considerFidelity = config.considerFidelity; + for (const auto& gate : layers.at(layer)) { if (gate.singleQubit()) { - singleQubitGateMultiplicity.at(gate.target)++; + if(singleQubitGateMultiplicity.at(gate.target) == 0) { + ++consideredQubitsSingleGates; + } + ++singleQubitGateMultiplicity.at(gate.target); if (considerFidelity) { consideredQubits.emplace(gate.target); } @@ -534,29 +539,44 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { node.qubits = qubits; node.recalculateFixedCost(architecture, singleQubitGateMultiplicity, twoQubitGateMultiplicity, - results.config.considerFidelity); + config.considerFidelity); node.updateHeuristicCost(architecture, singleQubitGateMultiplicity, twoQubitGateMultiplicity, consideredQubits, - results.config.admissibleHeuristic, - results.config.considerFidelity); + config.admissibleHeuristic, config.considerFidelity); - if (results.config.dataLoggingEnabled()) { + if (config.dataLoggingEnabled()) { dataLogger->logSearchNode(layer, node.id, node.parent, node.costFixed, node.costHeur, node.lookaheadPenalty, node.qubits, node.done, node.swaps, node.depth); } nodes.push(node); - const auto& debug = results.config.debug; const auto start = std::chrono::steady_clock::now(); - if (debug) { - results.layerHeuristicBenchmark.emplace_back(); - } - auto& totalExpandedNodes = results.heuristicBenchmark.expandedNodes; - auto layerResultsIt = results.layerHeuristicBenchmark.rbegin(); - + std::size_t expandedNodes = 0; + + bool splittable = (twoQubitGateMultiplicity.size() > 1 || consideredQubitsSingleGates > 1 || (twoQubitGateMultiplicity.size() > 0 && consideredQubitsSingleGates > 0)); + std::clog << "splittable: " << splittable << " (" << consideredQubitsSingleGates << ", " << twoQubitGateMultiplicity.size() << ")" << std::endl; + while (!nodes.empty() && (!done || nodes.top().getTotalCost() < bestDoneNode.getTotalFixedCost())) { + if (splittable && config.splitLayerAfterExpandedNodes > 0 && expandedNodes >= config.splitLayerAfterExpandedNodes) { + if (config.dataLoggingEnabled()) { + qc::CompoundOperation compOp(architecture.getNqubits()); + for (const auto& gate : layers.at(layer)) { + std::unique_ptr op = gate.op->clone(); + compOp.emplace_back(op); + } + + dataLogger->logFinalizeLayer(layer, compOp, singleQubitGateMultiplicity, twoQubitGateMultiplicity, qubits, 0, 0, 0, 0, {}, {}, 0); + dataLogger->splitLayer(); + } + splitLayer(layer, singleQubitGateMultiplicity, twoQubitGateMultiplicity, architecture); + if (config.verbose) { + std::clog << "Split layer" << std::endl; + } + // recursively restart search with newly split layer + return aStarMap(layer); + } Node current = nodes.top(); if (current.done) { if (!done || @@ -571,11 +591,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { nodes.pop(); expandNode(consideredQubits, current, layer, singleQubitGateMultiplicity, twoQubitGateMultiplicity); - - if (debug) { - ++totalExpandedNodes; - ++layerResultsIt->expandedNodes; - } + ++expandedNodes; } if (!done) { @@ -583,8 +599,12 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { } Node result = bestDoneNode; - if (debug) { + if (config.debug) { const auto end = std::chrono::steady_clock::now(); + results.layerHeuristicBenchmark.emplace_back(); + auto layerResultsIt = results.layerHeuristicBenchmark.rbegin(); + layerResultsIt->expandedNodes = expandedNodes; + results.heuristicBenchmark.expandedNodes += expandedNodes; layerResultsIt->solutionDepth = result.depth; @@ -607,7 +627,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { layerResultsIt->expandedNodes + 1, result.depth); } - if (results.config.dataLoggingEnabled()) { + if (config.dataLoggingEnabled()) { qc::CompoundOperation compOp(architecture.getNqubits()); for (const auto& gate : layers.at(layer)) { std::unique_ptr op = gate.op->clone(); From fd8b74f4be14500aee806137b4e44821537b1bf0 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 10 Nov 2023 00:40:34 +0100 Subject: [PATCH 025/108] python layer splitting setting pass-through --- src/mqt/qmap/compile.py | 3 +++ src/mqt/qmap/pyqmap.pyi | 1 + src/python/bindings.cpp | 1 + 3 files changed, 5 insertions(+) diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 109902bde..d1631b20e 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -63,6 +63,7 @@ def compile( # noqa: A001 consider_fidelity: bool = False, initial_layout: str | InitialLayout = "dynamic", layering: str | Layering = "individual_gates", + split_layer_after_expanded_nodes: int = 5000, lookaheads: int | None = 15, lookahead_factor: float = 0.5, use_teleportation: bool = False, @@ -93,6 +94,7 @@ def compile( # noqa: A001 consider_fidelity: Whether to consider the fidelity of the gates. Defaults to False. initial_layout: The initial layout to use. Defaults to "dynamic". layering: The layering strategy to use. Defaults to "individual_gates". + split_layer_after_expanded_nodes: The number of expanded nodes after which to split a layer (set to 0 to turn off layer splitting). Defaults to 5000. lookaheads: The number of lookaheads to be used or None if no lookahead should be used. Defaults to 15. lookahead_factor: The rate at which the contribution of future layers to the lookahead decreases. Defaults to 0.5. encoding: The encoding to use for the AMO and exactly one constraints. Defaults to "naive". @@ -131,6 +133,7 @@ def compile( # noqa: A001 config.consider_fidelity = consider_fidelity config.initial_layout = InitialLayout(initial_layout) config.layering = Layering(layering) + config.split_layer_after_expanded_nodes = split_layer_after_expanded_nodes config.encoding = Encoding(encoding) config.commander_grouping = CommanderGrouping(commander_grouping) config.swap_reduction = SwapReduction(swap_reduction) diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index 5444f1207..96ff00c8c 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -117,6 +117,7 @@ class Configuration: include_WCNF: bool # noqa: N815 initial_layout: InitialLayout layering: Layering + split_layer_after_expanded_nodes: int lookahead: bool lookahead_factor: float lookaheads: int diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 1297c1c35..99a3a8336 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -182,6 +182,7 @@ PYBIND11_MODULE(pyqmap, m) { .def_readwrite("debug", &Configuration::debug) .def_readwrite("data_logging_path", &Configuration::dataLoggingPath) .def_readwrite("layering", &Configuration::layering) + .def_readwrite("split_layer_after_expanded_nodes", &Configuration::splitLayerAfterExpandedNodes) .def_readwrite("initial_layout", &Configuration::initialLayout) .def_readwrite("lookahead", &Configuration::lookahead) .def_readwrite("admissible_heuristic", From 043bbb2dcb6e80e6769569fb1014d81a02f9c197 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 10 Nov 2023 02:34:04 +0100 Subject: [PATCH 026/108] adding layer splitting test --- src/heuristic/HeuristicMapper.cpp | 1 - test/test_heuristic.cpp | 223 ++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 1 deletion(-) diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 1e5d003f6..7375a1bd2 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -554,7 +554,6 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { std::size_t expandedNodes = 0; bool splittable = (twoQubitGateMultiplicity.size() > 1 || consideredQubitsSingleGates > 1 || (twoQubitGateMultiplicity.size() > 0 && consideredQubitsSingleGates > 0)); - std::clog << "splittable: " << splittable << " (" << consideredQubitsSingleGates << ", " << twoQubitGateMultiplicity.size() << ")" << std::endl; while (!nodes.empty() && (!done || nodes.top().getTotalCost() < bestDoneNode.getTotalFixedCost())) { diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 273d2d448..8b6cdcbf8 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -8,6 +8,7 @@ #include "gtest/gtest.h" #include +#include #include #include #include @@ -1091,3 +1092,225 @@ TEST(HeuristicTestFidelity, SingleQubitsCompete) { std::log2(1 - 0.1); // Xs EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); } + +TEST(HeuristicTestFidelity, LayerSplitting) { + Architecture architecture{}; + const CouplingMap cm = { + {0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 3}, {3, 2}, + + {0, 4}, {4, 0}, {1, 5}, {5, 1}, {2, 6}, {6, 2}, {3, 7}, {7, 3}, + + {4, 5}, {5, 4}, {5, 6}, {6, 5}, {6, 7}, {7, 6}, + + {4, 8}, {8, 4}, {5, 9}, {9, 5}, {6, 10}, {10, 6}, {7, 11}, {11, 7}, + + {8, 9}, {9, 8}, {9, 10}, {10, 9}, {10, 11}, {11, 10}}; + architecture.loadCouplingMap(12, cm); + + double e5 = 0.99; + double e4 = 0.9; + double e3 = 0.5; + double e2 = 0.4; + double e1 = 0.1; + double e0 = 0.01; + + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", e5); + props.setSingleQubitErrorRate(1, "x", e5); + props.setSingleQubitErrorRate(2, "x", e5); + props.setSingleQubitErrorRate(3, "x", e5); + props.setSingleQubitErrorRate(4, "x", e3); + props.setSingleQubitErrorRate(5, "x", e3); + props.setSingleQubitErrorRate(6, "x", e5); + props.setSingleQubitErrorRate(7, "x", e3); + props.setSingleQubitErrorRate(8, "x", e2); + props.setSingleQubitErrorRate(9, "x", e1); + props.setSingleQubitErrorRate(10, "x", e5); + props.setSingleQubitErrorRate(11, "x", e2); + + props.setTwoQubitErrorRate(0, 1, e4); + props.setTwoQubitErrorRate(1, 0, e4); + props.setTwoQubitErrorRate(1, 2, e4); + props.setTwoQubitErrorRate(2, 1, e4); + props.setTwoQubitErrorRate(2, 3, e1); + props.setTwoQubitErrorRate(3, 2, e1); + + props.setTwoQubitErrorRate(0, 4, e5); + props.setTwoQubitErrorRate(4, 0, e5); + props.setTwoQubitErrorRate(1, 5, e5); + props.setTwoQubitErrorRate(5, 1, e5); + props.setTwoQubitErrorRate(2, 6, e4); + props.setTwoQubitErrorRate(6, 2, e4); + props.setTwoQubitErrorRate(3, 7, e5); + props.setTwoQubitErrorRate(7, 3, e5); + + props.setTwoQubitErrorRate(4, 5, e3); + props.setTwoQubitErrorRate(5, 4, e3); + props.setTwoQubitErrorRate(5, 6, e5); + props.setTwoQubitErrorRate(6, 5, e5); + props.setTwoQubitErrorRate(6, 7, e5); + props.setTwoQubitErrorRate(7, 6, e5); + + props.setTwoQubitErrorRate(4, 8, e0); + props.setTwoQubitErrorRate(8, 4, e0); + props.setTwoQubitErrorRate(5, 9, e3); + props.setTwoQubitErrorRate(9, 5, e3); + props.setTwoQubitErrorRate(6, 10, e1); + props.setTwoQubitErrorRate(10, 6, e1); + props.setTwoQubitErrorRate(7, 11, e3); + props.setTwoQubitErrorRate(11, 7, e3); + + props.setTwoQubitErrorRate(8, 9, e5); + props.setTwoQubitErrorRate(9, 8, e5); + props.setTwoQubitErrorRate(9, 10, e4); + props.setTwoQubitErrorRate(10, 9, e4); + props.setTwoQubitErrorRate(10, 11, e5); + props.setTwoQubitErrorRate(11, 10, e5); + + architecture.loadProperties(props); + + qc::QuantumComputation qc{12, 12}; + + for (std::size_t i = 0; i < 50; ++i) { + qc.x(4); + } + qc.x(5); + qc.x(7); + for (std::size_t i = 0; i < 5; ++i) { + qc.cx(qc::Control{3}, 0); + qc.cx(qc::Control{9}, 2); + } + + for (size_t i = 0; i < 12; ++i) { + qc.measure(static_cast(i), i); + } + + auto mapper = std::make_unique(qc, architecture); + + Configuration settings{}; + settings.verbose = true; + settings.admissibleHeuristic = true; + settings.layering = Layering::Disjoint2qBlocks; + settings.initialLayout = InitialLayout::Identity; + settings.considerFidelity = true; + settings.preMappingOptimizations = false; + settings.postMappingOptimizations = false; + settings.swapOnFirstLayer = true; + settings.splitLayerAfterExpandedNodes = 1; // force splittings after 1st expanded node until layers are unsplittable + settings.dataLoggingPath = "test_log/"; + mapper->map(settings); + mapper->dumpResult("simple_grid_mapped.qasm"); + mapper->printResult(std::cout); + + auto& result = mapper->getResults(); + EXPECT_EQ(result.input.layers, 5); // originally 1 but split into 5 during A* + /* + expected output: + === layout (placing of logical qubits in physical grid): + 0 1 2 3 + 4 5 6 7 + 8 9 10 11 + SWAP (4,5) + SWAP (5,9) + === layout: + 0 1 2 3 + 5 9 6 7 + 8 4 10 11 + X(9) [x50] (originally X(4)) + + === layout: + 0 1 2 3 + 5 9 6 7 + 8 4 10 11 + X(7) (originally X(7)) + + SWAP (5,9) + SWAP (9,10) + SWAP (2,6) + === layout: + 0 1 6 3 + 5 4 2 7 + 8 10 9 11 + CX(6,10) [x5] (originally CX(2,9)) + + SWAP (4,8) + === layout: + 0 1 6 3 + 8 4 2 7 + 5 10 9 11 + X(8) (originally X(5)) + + SWAP (0,1) + SWAP (1,2) + === layout: + 1 6 0 3 + 8 4 2 7 + 5 10 9 11 + CX(2,3) [x5] (originally CX(0,3)) + */ + EXPECT_EQ(result.output.swaps, 8); + + double c4 = -std::log2(1 - e4); + double c3 = -std::log2(1 - e3); + double c2 = -std::log2(1 - e2); + double c1 = -std::log2(1 - e1); + double c0 = -std::log2(1 - e0); + + double expectedFidelity = 3 * (c3 + c3 + c3 + c4 + c4 + c0 + c4 + c4) + // SWAPs + 50 * c1 + c3 + c2 + // Xs + 5 * c1 + 5 * c1; // CXs + EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); + + // check data log + const char* layerNodeFilePaths[4] = { + "nodes_layer_0.presplit-0.csv", + "nodes_layer_0.presplit-1.csv", + "nodes_layer_1.presplit-0.csv", + "nodes_layer_3.presplit-0.csv" + }; + for (std::size_t i = 0; i < 4; ++i) { + auto layerNodeFile = std::ifstream(settings.dataLoggingPath + layerNodeFilePaths[i]); + if (!layerNodeFile.is_open()) { + FAIL() << "Could not open file " << settings.dataLoggingPath + << layerNodeFilePaths[i]; + } + std::string line; + while (std::getline(layerNodeFile, line)) { + if (line.empty()) { + continue; + } + std::string col; + std::stringstream lineStream(line); + if (!std::getline(lineStream, col, ';')) { + FAIL() << "Missing value for node id in " << settings.dataLoggingPath + << layerNodeFilePaths[i]; + } + if (std::getline(lineStream, col, ';')) { + if(std::stoull(col) != 0) { + // should only contain root node and its direct children + FAIL() << "Unexpected value for parent node id in " + << settings.dataLoggingPath << layerNodeFilePaths[i]; + } + } else { + FAIL() << "Missing value for parent node id in " + << settings.dataLoggingPath << layerNodeFilePaths[i]; + } + } + } + if (!std::filesystem::exists(settings.dataLoggingPath + "layer_0.presplit-0.json")) { + FAIL() << "File " << settings.dataLoggingPath << "layer_0.presplit-0.json" + << " does not exist"; + } + if (!std::filesystem::exists(settings.dataLoggingPath + "layer_0.presplit-1.json")) { + FAIL() << "File " << settings.dataLoggingPath << "layer_0.presplit-1.json" + << " does not exist"; + } + if (!std::filesystem::exists(settings.dataLoggingPath + "layer_1.presplit-0.json")) { + FAIL() << "File " << settings.dataLoggingPath << "layer_1.presplit-0.json" + << " does not exist"; + } + if (!std::filesystem::exists(settings.dataLoggingPath + "layer_3.presplit-0.json")) { + FAIL() << "File " << settings.dataLoggingPath << "layer_3.presplit-0.json" + << " does not exist"; + } +} \ No newline at end of file From 076a39346e93b08679310a46859fd77710bb6ae4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 01:48:44 +0000 Subject: [PATCH 027/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/Mapper.hpp | 15 ++- include/configuration/Configuration.hpp | 11 ++- src/DataLogger.cpp | 16 ++-- src/Mapper.cpp | 119 ++++++++++++------------ src/heuristic/HeuristicMapper.cpp | 31 +++--- src/python/bindings.cpp | 3 +- test/test_heuristic.cpp | 101 ++++++++++---------- 7 files changed, 159 insertions(+), 137 deletions(-) diff --git a/include/Mapper.hpp b/include/Mapper.hpp index 4ae1e7717..803b86473 100644 --- a/include/Mapper.hpp +++ b/include/Mapper.hpp @@ -123,16 +123,21 @@ class Mapper { * a disjoint set of qubits */ virtual void createLayers(); - + /** - * @brief Splits the layer at the given index into two layers with half as many qubits acted on by gates in each layer - * + * @brief Splits the layer at the given index into two layers with half as + * many qubits acted on by gates in each layer + * * @param index the index of the layer to be split - * @param singleQubitMultiplicity single qubit multiplicity of the layer to be split + * @param singleQubitMultiplicity single qubit multiplicity of the layer to be + * split * @param twoQubitMultiplicity two qubit multiplicity of the layer to be split * @param arch architecture on which the circuit is mapped */ - virtual void splitLayer(std::size_t index, SingleQubitMultiplicity& singleQubitMultiplicity, TwoQubitMultiplicity& twoQubitMultiplicity, Architecture& arch); + virtual void splitLayer(std::size_t index, + SingleQubitMultiplicity& singleQubitMultiplicity, + TwoQubitMultiplicity& twoQubitMultiplicity, + Architecture& arch); /** * gates are put in the last layer (from the back of the circuit) in which diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 93f5b9372..ae1832cd6 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -19,9 +19,9 @@ struct Configuration { Configuration() = default; // which method to use - Method method = Method::Heuristic; - bool admissibleHeuristic = true; - bool considerFidelity = false; + Method method = Method::Heuristic; + bool admissibleHeuristic = true; + bool considerFidelity = false; bool preMappingOptimizations = true; bool postMappingOptimizations = true; @@ -56,8 +56,9 @@ struct Configuration { // timeout merely affects exact mapper std::size_t timeout = 3600000; // 60min timeout - - // after how many expanded nodes to split layer in heuristic mapper, 0 to disable + + // after how many expanded nodes to split layer in heuristic mapper, 0 to + // disable std::size_t splitLayerAfterExpandedNodes = 0; // encoding of at most and exactly one constraints in exact mapper diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index d39d97c51..65c47165a 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -98,8 +98,8 @@ void DataLogger::logFinalizeLayer( } searchNodesLogFiles.at(layerIndex).close(); - auto of = std::ofstream(dataLoggingPath + "layer_" + std::to_string(layerIndex) + - ".json"); + auto of = std::ofstream(dataLoggingPath + "layer_" + + std::to_string(layerIndex) + ".json"); if (!of.good()) { deactivated = true; std::cerr << "[data-logging] Error opening file: " << dataLoggingPath @@ -169,13 +169,13 @@ void DataLogger::splitLayer() { ++splitIndex; } std::filesystem::rename( - dataLoggingPath + "nodes_layer_" + std::to_string(layerIndex) + ".csv", - dataLoggingPath + "nodes_layer_" + std::to_string(layerIndex) + ".presplit-" + std::to_string(splitIndex) + ".csv" - ); + dataLoggingPath + "nodes_layer_" + std::to_string(layerIndex) + ".csv", + dataLoggingPath + "nodes_layer_" + std::to_string(layerIndex) + + ".presplit-" + std::to_string(splitIndex) + ".csv"); std::filesystem::rename( - dataLoggingPath + "layer_" + std::to_string(layerIndex) +".json", - dataLoggingPath + "layer_" + std::to_string(layerIndex) + ".presplit-" + std::to_string(splitIndex) + ".json" - ); + dataLoggingPath + "layer_" + std::to_string(layerIndex) + ".json", + dataLoggingPath + "layer_" + std::to_string(layerIndex) + ".presplit-" + + std::to_string(splitIndex) + ".json"); } void DataLogger::logSearchNode( diff --git a/src/Mapper.cpp b/src/Mapper.cpp index dd62b2024..6e43c0df9 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -184,73 +184,76 @@ void Mapper::createLayers() { } } results.input.layers = layers.size(); -} - -void Mapper::splitLayer(std::size_t index, SingleQubitMultiplicity& singleQubitMultiplicity, TwoQubitMultiplicity& twoQubitMultiplicity, Architecture& arch) { - std::vector layer0 = {}; - std::vector layer1 = {}; - SingleQubitMultiplicity singleQubitMultiplicity0(arch.getNqubits(), 0); - SingleQubitMultiplicity singleQubitMultiplicity1(arch.getNqubits(), 0); - TwoQubitMultiplicity twoQubitMultiplicity0 = {}; - TwoQubitMultiplicity twoQubitMultiplicity1 = {}; - - bool even = true; - for (std::size_t q = 0; q < singleQubitMultiplicity.size(); ++q) { - if (singleQubitMultiplicity[q] == 0) { - continue; - } - if (even) { - singleQubitMultiplicity0[q] = singleQubitMultiplicity[q]; +} + +void Mapper::splitLayer(std::size_t index, + SingleQubitMultiplicity& singleQubitMultiplicity, + TwoQubitMultiplicity& twoQubitMultiplicity, + Architecture& arch) { + std::vector layer0 = {}; + std::vector layer1 = {}; + SingleQubitMultiplicity singleQubitMultiplicity0(arch.getNqubits(), 0); + SingleQubitMultiplicity singleQubitMultiplicity1(arch.getNqubits(), 0); + TwoQubitMultiplicity twoQubitMultiplicity0 = {}; + TwoQubitMultiplicity twoQubitMultiplicity1 = {}; + + bool even = true; + for (std::size_t q = 0; q < singleQubitMultiplicity.size(); ++q) { + if (singleQubitMultiplicity[q] == 0) { + continue; + } + if (even) { + singleQubitMultiplicity0[q] = singleQubitMultiplicity[q]; + } else { + singleQubitMultiplicity1[q] = singleQubitMultiplicity[q]; + } + even = !even; + } + even = false; + for (auto edge : twoQubitMultiplicity) { + if (even) { + twoQubitMultiplicity0.insert(edge); + } else { + twoQubitMultiplicity1.insert(edge); + } + even = !even; + } + + for (auto& gate : layers[index]) { + if (gate.singleQubit()) { + if (singleQubitMultiplicity0[gate.target] > 0) { + layer0.push_back(gate); } else { - singleQubitMultiplicity1[q] = singleQubitMultiplicity[q]; + layer1.push_back(gate); } - even = !even; - } - even = false; - for (auto edge : twoQubitMultiplicity) { - if (even) { - twoQubitMultiplicity0.insert(edge); + } else { + Edge edge; + if (gate.control < gate.target) { + edge = {gate.control, gate.target}; } else { - twoQubitMultiplicity1.insert(edge); + edge = {gate.target, gate.control}; } - even = !even; - } - - for (auto& gate : layers[index]) { - if (gate.singleQubit()) { - if (singleQubitMultiplicity0[gate.target] > 0) { - layer0.push_back(gate); - } else { - layer1.push_back(gate); - } + if (twoQubitMultiplicity0.find(edge) != twoQubitMultiplicity0.end()) { + layer0.push_back(gate); } else { - Edge edge; - if (gate.control < gate.target) { - edge = {gate.control, gate.target}; - } else { - edge = {gate.target, gate.control}; - } - if (twoQubitMultiplicity0.find(edge) != twoQubitMultiplicity0.end()) { - layer0.push_back(gate); - } else { - layer1.push_back(gate); - } + layer1.push_back(gate); } } - - singleQubitMultiplicity.clear(); - twoQubitMultiplicity.clear(); - for (auto q : singleQubitMultiplicity0) { - singleQubitMultiplicity.push_back(q); - } - for (auto edge : twoQubitMultiplicity0) { - twoQubitMultiplicity.insert(edge); - } - layers[index] = layer0; - layers.insert(layers.begin() + index + 1, layer1); - results.input.layers = layers.size(); } + singleQubitMultiplicity.clear(); + twoQubitMultiplicity.clear(); + for (auto q : singleQubitMultiplicity0) { + singleQubitMultiplicity.push_back(q); + } + for (auto edge : twoQubitMultiplicity0) { + twoQubitMultiplicity.insert(edge); + } + layers[index] = layer0; + layers.insert(layers.begin() + index + 1, layer1); + results.input.layers = layers.size(); +} + std::size_t Mapper::getNextLayer(std::size_t idx) { auto next = idx + 1; while (next < layers.size()) { diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 7375a1bd2..605aada90 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -481,10 +481,10 @@ void HeuristicMapper::mapToMinDistance(const std::uint16_t source, HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { auto& config = results.config; - nextNodeId = 0; + nextNodeId = 0; std::unordered_set consideredQubits{}; - std::size_t consideredQubitsSingleGates = 0; + std::size_t consideredQubitsSingleGates = 0; Node node(nextNodeId++); // number of single qubit gates acting on each logical qubit in the current // layer @@ -498,10 +498,10 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { Node bestDoneNode(0); bool done = false; const bool considerFidelity = config.considerFidelity; - + for (const auto& gate : layers.at(layer)) { if (gate.singleQubit()) { - if(singleQubitGateMultiplicity.at(gate.target) == 0) { + if (singleQubitGateMultiplicity.at(gate.target) == 0) { ++consideredQubitsSingleGates; } ++singleQubitGateMultiplicity.at(gate.target); @@ -550,14 +550,18 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { } nodes.push(node); - const auto start = std::chrono::steady_clock::now(); + const auto start = std::chrono::steady_clock::now(); std::size_t expandedNodes = 0; - - bool splittable = (twoQubitGateMultiplicity.size() > 1 || consideredQubitsSingleGates > 1 || (twoQubitGateMultiplicity.size() > 0 && consideredQubitsSingleGates > 0)); - + + bool splittable = + (twoQubitGateMultiplicity.size() > 1 || consideredQubitsSingleGates > 1 || + (twoQubitGateMultiplicity.size() > 0 && + consideredQubitsSingleGates > 0)); + while (!nodes.empty() && (!done || nodes.top().getTotalCost() < bestDoneNode.getTotalFixedCost())) { - if (splittable && config.splitLayerAfterExpandedNodes > 0 && expandedNodes >= config.splitLayerAfterExpandedNodes) { + if (splittable && config.splitLayerAfterExpandedNodes > 0 && + expandedNodes >= config.splitLayerAfterExpandedNodes) { if (config.dataLoggingEnabled()) { qc::CompoundOperation compOp(architecture->getNqubits()); for (const auto& gate : layers.at(layer)) { @@ -565,10 +569,13 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { compOp.emplace_back(op); } - dataLogger->logFinalizeLayer(layer, compOp, singleQubitGateMultiplicity, twoQubitGateMultiplicity, qubits, 0, 0, 0, 0, {}, {}, 0); + dataLogger->logFinalizeLayer(layer, compOp, singleQubitGateMultiplicity, + twoQubitGateMultiplicity, qubits, 0, 0, 0, + 0, {}, {}, 0); dataLogger->splitLayer(); } - splitLayer(layer, singleQubitGateMultiplicity, twoQubitGateMultiplicity, *architecture); + splitLayer(layer, singleQubitGateMultiplicity, twoQubitGateMultiplicity, + *architecture); if (config.verbose) { std::clog << "Split layer" << std::endl; } @@ -600,7 +607,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { if (config.debug) { const auto end = std::chrono::steady_clock::now(); results.layerHeuristicBenchmark.emplace_back(); - auto layerResultsIt = results.layerHeuristicBenchmark.rbegin(); + auto layerResultsIt = results.layerHeuristicBenchmark.rbegin(); layerResultsIt->expandedNodes = expandedNodes; results.heuristicBenchmark.expandedNodes += expandedNodes; diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 99a3a8336..662120c98 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -182,7 +182,8 @@ PYBIND11_MODULE(pyqmap, m) { .def_readwrite("debug", &Configuration::debug) .def_readwrite("data_logging_path", &Configuration::dataLoggingPath) .def_readwrite("layering", &Configuration::layering) - .def_readwrite("split_layer_after_expanded_nodes", &Configuration::splitLayerAfterExpandedNodes) + .def_readwrite("split_layer_after_expanded_nodes", + &Configuration::splitLayerAfterExpandedNodes) .def_readwrite("initial_layout", &Configuration::initialLayout) .def_readwrite("lookahead", &Configuration::lookahead) .def_readwrite("admissible_heuristic", diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 8b6cdcbf8..2c7550a4c 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -7,8 +7,8 @@ #include "nlohmann/json.hpp" #include "gtest/gtest.h" -#include #include +#include #include #include #include @@ -1188,16 +1188,18 @@ TEST(HeuristicTestFidelity, LayerSplitting) { auto mapper = std::make_unique(qc, architecture); Configuration settings{}; - settings.verbose = true; - settings.admissibleHeuristic = true; - settings.layering = Layering::Disjoint2qBlocks; - settings.initialLayout = InitialLayout::Identity; - settings.considerFidelity = true; - settings.preMappingOptimizations = false; - settings.postMappingOptimizations = false; - settings.swapOnFirstLayer = true; - settings.splitLayerAfterExpandedNodes = 1; // force splittings after 1st expanded node until layers are unsplittable - settings.dataLoggingPath = "test_log/"; + settings.verbose = true; + settings.admissibleHeuristic = true; + settings.layering = Layering::Disjoint2qBlocks; + settings.initialLayout = InitialLayout::Identity; + settings.considerFidelity = true; + settings.preMappingOptimizations = false; + settings.postMappingOptimizations = false; + settings.swapOnFirstLayer = true; + settings.splitLayerAfterExpandedNodes = + 1; // force splittings after 1st expanded node until layers are + // unsplittable + settings.dataLoggingPath = "test_log/"; mapper->map(settings); mapper->dumpResult("simple_grid_mapped.qasm"); mapper->printResult(std::cout); @@ -1206,45 +1208,45 @@ TEST(HeuristicTestFidelity, LayerSplitting) { EXPECT_EQ(result.input.layers, 5); // originally 1 but split into 5 during A* /* expected output: - === layout (placing of logical qubits in physical grid): - 0 1 2 3 - 4 5 6 7 + === layout (placing of logical qubits in physical grid): + 0 1 2 3 + 4 5 6 7 8 9 10 11 SWAP (4,5) SWAP (5,9) - === layout: - 0 1 2 3 - 5 9 6 7 + === layout: + 0 1 2 3 + 5 9 6 7 8 4 10 11 X(9) [x50] (originally X(4)) - + === layout: - 0 1 2 3 - 5 9 6 7 + 0 1 2 3 + 5 9 6 7 8 4 10 11 X(7) (originally X(7)) - + SWAP (5,9) SWAP (9,10) SWAP (2,6) === layout: - 0 1 6 3 - 5 4 2 7 + 0 1 6 3 + 5 4 2 7 8 10 9 11 CX(6,10) [x5] (originally CX(2,9)) - + SWAP (4,8) === layout: - 0 1 6 3 - 8 4 2 7 + 0 1 6 3 + 8 4 2 7 5 10 9 11 X(8) (originally X(5)) - + SWAP (0,1) SWAP (1,2) === layout: - 1 6 0 3 - 8 4 2 7 + 1 6 0 3 + 8 4 2 7 5 10 9 11 CX(2,3) [x5] (originally CX(0,3)) */ @@ -1256,25 +1258,24 @@ TEST(HeuristicTestFidelity, LayerSplitting) { double c1 = -std::log2(1 - e1); double c0 = -std::log2(1 - e0); - double expectedFidelity = 3 * (c3 + c3 + c3 + c4 + c4 + c0 + c4 + c4) + // SWAPs - 50 * c1 + c3 + c2 + // Xs - 5 * c1 + 5 * c1; // CXs + double expectedFidelity = + 3 * (c3 + c3 + c3 + c4 + c4 + c0 + c4 + c4) + // SWAPs + 50 * c1 + c3 + c2 + // Xs + 5 * c1 + 5 * c1; // CXs EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); - + // check data log const char* layerNodeFilePaths[4] = { - "nodes_layer_0.presplit-0.csv", - "nodes_layer_0.presplit-1.csv", - "nodes_layer_1.presplit-0.csv", - "nodes_layer_3.presplit-0.csv" - }; + "nodes_layer_0.presplit-0.csv", "nodes_layer_0.presplit-1.csv", + "nodes_layer_1.presplit-0.csv", "nodes_layer_3.presplit-0.csv"}; for (std::size_t i = 0; i < 4; ++i) { - auto layerNodeFile = std::ifstream(settings.dataLoggingPath + layerNodeFilePaths[i]); + auto layerNodeFile = + std::ifstream(settings.dataLoggingPath + layerNodeFilePaths[i]); if (!layerNodeFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath - << layerNodeFilePaths[i]; + << layerNodeFilePaths[i]; } - std::string line; + std::string line; while (std::getline(layerNodeFile, line)) { if (line.empty()) { continue; @@ -1286,31 +1287,35 @@ TEST(HeuristicTestFidelity, LayerSplitting) { << layerNodeFilePaths[i]; } if (std::getline(lineStream, col, ';')) { - if(std::stoull(col) != 0) { + if (std::stoull(col) != 0) { // should only contain root node and its direct children FAIL() << "Unexpected value for parent node id in " << settings.dataLoggingPath << layerNodeFilePaths[i]; } } else { FAIL() << "Missing value for parent node id in " - << settings.dataLoggingPath << layerNodeFilePaths[i]; + << settings.dataLoggingPath << layerNodeFilePaths[i]; } } } - if (!std::filesystem::exists(settings.dataLoggingPath + "layer_0.presplit-0.json")) { + if (!std::filesystem::exists(settings.dataLoggingPath + + "layer_0.presplit-0.json")) { FAIL() << "File " << settings.dataLoggingPath << "layer_0.presplit-0.json" << " does not exist"; } - if (!std::filesystem::exists(settings.dataLoggingPath + "layer_0.presplit-1.json")) { + if (!std::filesystem::exists(settings.dataLoggingPath + + "layer_0.presplit-1.json")) { FAIL() << "File " << settings.dataLoggingPath << "layer_0.presplit-1.json" << " does not exist"; } - if (!std::filesystem::exists(settings.dataLoggingPath + "layer_1.presplit-0.json")) { + if (!std::filesystem::exists(settings.dataLoggingPath + + "layer_1.presplit-0.json")) { FAIL() << "File " << settings.dataLoggingPath << "layer_1.presplit-0.json" << " does not exist"; } - if (!std::filesystem::exists(settings.dataLoggingPath + "layer_3.presplit-0.json")) { + if (!std::filesystem::exists(settings.dataLoggingPath + + "layer_3.presplit-0.json")) { FAIL() << "File " << settings.dataLoggingPath << "layer_3.presplit-0.json" << " does not exist"; } -} \ No newline at end of file +} From e66e1b0c6e535bf7f09b54da8374faaca6e682e4 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 10 Nov 2023 04:08:47 +0100 Subject: [PATCH 028/108] fixed linter errors --- include/DataLogger.hpp | 14 ++++++------ src/DataLogger.cpp | 26 ++++++++++----------- src/Mapper.cpp | 2 +- src/heuristic/HeuristicMapper.cpp | 6 ++--- test/test_heuristic.cpp | 38 +++++++++++++++---------------- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index 9a04548d0..6f0148375 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -15,11 +15,11 @@ class DataLogger { public: - DataLogger(std::string path, Architecture& arch, qc::QuantumComputation& qc) - : dataLoggingPath(path), architecture(arch), nqubits(arch.getNqubits()), - inputCircuit(qc) { + DataLogger(std::string path, Architecture& arch, qc::QuantumComputation qc) + : dataLoggingPath(std::move(path)), architecture(&arch), nqubits(arch.getNqubits()), + inputCircuit(std::move(qc)) { initLog(); - logArchitecture(architecture); + logArchitecture(); logInputCircuit(inputCircuit); for (std::size_t i = 0; i < qc.getNqubits(); ++i) { qregs.emplace_back("q", "q[" + std::to_string(i) + "]"); @@ -31,7 +31,7 @@ class DataLogger { void initLog(); void clearLog(); - void logArchitecture(Architecture& arch); + void logArchitecture(); void logSearchNode(std::size_t layer, std::size_t nodeId, std::size_t parentId, double costFixed, double costHeur, double lookaheadPenalty, @@ -63,9 +63,9 @@ class DataLogger { protected: std::string dataLoggingPath; - Architecture& architecture; + Architecture* architecture; std::uint16_t nqubits; - qc::QuantumComputation& inputCircuit; + qc::QuantumComputation inputCircuit; qc::RegisterNames qregs{}; qc::RegisterNames cregs{}; std::vector searchNodesLogFiles; // 1 per layer diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index 65c47165a..decafc02a 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -27,7 +27,7 @@ void DataLogger::clearLog() { } }; -void DataLogger::logArchitecture(Architecture& arch) { +void DataLogger::logArchitecture() { if (deactivated) { return; } @@ -40,19 +40,19 @@ void DataLogger::logArchitecture(Architecture& arch) { return; } nlohmann::json json; - json["name"] = arch.getName(); - json["nqubits"] = arch.getNqubits(); - json["coupling_map"] = arch.getCouplingMap(); - json["distances"] = arch.getDistanceTable(); - if (arch.isFidelityAvailable()) { + json["name"] = architecture->getName(); + json["nqubits"] = architecture->getNqubits(); + json["coupling_map"] = architecture->getCouplingMap(); + json["distances"] = architecture->getDistanceTable(); + if (architecture->isFidelityAvailable()) { auto& fidelity = json["fidelity"]; - fidelity["single_qubit_fidelities"] = arch.getSingleQubitFidelities(); - fidelity["two_qubit_fidelities"] = arch.getFidelityTable(); + fidelity["single_qubit_fidelities"] = architecture->getSingleQubitFidelities(); + fidelity["two_qubit_fidelities"] = architecture->getFidelityTable(); fidelity["single_qubit_fidelity_costs"] = - arch.getSingleQubitFidelityCosts(); - fidelity["two_qubit_fidelity_costs"] = arch.getTwoQubitFidelityCosts(); - fidelity["swap_fidelity_costs"] = arch.getSwapFidelityCosts(); - fidelity["fidelity_distances"] = arch.getFidelityDistanceTables(); + architecture->getSingleQubitFidelityCosts(); + fidelity["two_qubit_fidelity_costs"] = architecture->getTwoQubitFidelityCosts(); + fidelity["swap_fidelity_costs"] = architecture->getSwapFidelityCosts(); + fidelity["fidelity_distances"] = architecture->getFidelityDistanceTables(); } of << json.dump(2); of.close(); @@ -155,7 +155,7 @@ void DataLogger::logFinalizeLayer( }; void DataLogger::splitLayer() { - std::size_t layerIndex = searchNodesLogFiles.size() - 1; + const std::size_t layerIndex = searchNodesLogFiles.size() - 1; if (searchNodesLogFiles.at(layerIndex).is_open()) { std::cerr << "[data-logging] Error: layer " << layerIndex << " has not been finalized before splitting" << std::endl; diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 6e43c0df9..bdf935038 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -250,7 +250,7 @@ void Mapper::splitLayer(std::size_t index, twoQubitMultiplicity.insert(edge); } layers[index] = layer0; - layers.insert(layers.begin() + index + 1, layer1); + layers.insert(layers.begin() + static_cast>::difference_type>(index) + 1, layer1); results.input.layers = layers.size(); } diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 605aada90..1aada8c33 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -268,6 +268,7 @@ void HeuristicMapper::map(const Configuration& configuration) { if (config.dataLoggingEnabled()) { dataLogger->logOutputCircuit(qcMapped); dataLogger->logMappingResult(results); + dataLogger->close(); } } @@ -553,10 +554,9 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { const auto start = std::chrono::steady_clock::now(); std::size_t expandedNodes = 0; - bool splittable = + const bool splittable = (twoQubitGateMultiplicity.size() > 1 || consideredQubitsSingleGates > 1 || - (twoQubitGateMultiplicity.size() > 0 && - consideredQubitsSingleGates > 0)); + (!twoQubitGateMultiplicity.empty() && consideredQubitsSingleGates > 0)); while (!nodes.empty() && (!done || nodes.top().getTotalCost() < bestDoneNode.getTotalFixedCost())) { diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 2c7550a4c..e7d3dfadd 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -1107,12 +1107,12 @@ TEST(HeuristicTestFidelity, LayerSplitting) { {8, 9}, {9, 8}, {9, 10}, {10, 9}, {10, 11}, {11, 10}}; architecture.loadCouplingMap(12, cm); - double e5 = 0.99; - double e4 = 0.9; - double e3 = 0.5; - double e2 = 0.4; - double e1 = 0.1; - double e0 = 0.01; + const double e5 = 0.99; + const double e4 = 0.9; + const double e3 = 0.5; + const double e2 = 0.4; + const double e1 = 0.1; + const double e0 = 0.01; auto props = Architecture::Properties(); props.setSingleQubitErrorRate(0, "x", e5); @@ -1252,28 +1252,28 @@ TEST(HeuristicTestFidelity, LayerSplitting) { */ EXPECT_EQ(result.output.swaps, 8); - double c4 = -std::log2(1 - e4); - double c3 = -std::log2(1 - e3); - double c2 = -std::log2(1 - e2); - double c1 = -std::log2(1 - e1); - double c0 = -std::log2(1 - e0); + const double c4 = -std::log2(1 - e4); + const double c3 = -std::log2(1 - e3); + const double c2 = -std::log2(1 - e2); + const double c1 = -std::log2(1 - e1); + const double c0 = -std::log2(1 - e0); - double expectedFidelity = + const double expectedFidelity = 3 * (c3 + c3 + c3 + c4 + c4 + c0 + c4 + c4) + // SWAPs 50 * c1 + c3 + c2 + // Xs 5 * c1 + 5 * c1; // CXs EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); // check data log - const char* layerNodeFilePaths[4] = { + std::array layerNodeFilePaths = { "nodes_layer_0.presplit-0.csv", "nodes_layer_0.presplit-1.csv", "nodes_layer_1.presplit-0.csv", "nodes_layer_3.presplit-0.csv"}; - for (std::size_t i = 0; i < 4; ++i) { + for (auto& path : layerNodeFilePaths) { auto layerNodeFile = - std::ifstream(settings.dataLoggingPath + layerNodeFilePaths[i]); + std::ifstream(settings.dataLoggingPath + path); if (!layerNodeFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath - << layerNodeFilePaths[i]; + << path; } std::string line; while (std::getline(layerNodeFile, line)) { @@ -1284,17 +1284,17 @@ TEST(HeuristicTestFidelity, LayerSplitting) { std::stringstream lineStream(line); if (!std::getline(lineStream, col, ';')) { FAIL() << "Missing value for node id in " << settings.dataLoggingPath - << layerNodeFilePaths[i]; + << path; } if (std::getline(lineStream, col, ';')) { if (std::stoull(col) != 0) { // should only contain root node and its direct children FAIL() << "Unexpected value for parent node id in " - << settings.dataLoggingPath << layerNodeFilePaths[i]; + << settings.dataLoggingPath << path; } } else { FAIL() << "Missing value for parent node id in " - << settings.dataLoggingPath << layerNodeFilePaths[i]; + << settings.dataLoggingPath << path; } } } From a9d0b9beb6df3c03d427b177614bd5c0f121dd12 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 03:10:01 +0000 Subject: [PATCH 029/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/DataLogger.hpp | 4 ++-- src/DataLogger.cpp | 14 ++++++++------ src/Mapper.cpp | 7 ++++++- test/test_heuristic.cpp | 6 ++---- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index 6f0148375..741a74da2 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -16,8 +16,8 @@ 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)) { + : dataLoggingPath(std::move(path)), architecture(&arch), + nqubits(arch.getNqubits()), inputCircuit(std::move(qc)) { initLog(); logArchitecture(); logInputCircuit(inputCircuit); diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index decafc02a..73f655bce 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -45,14 +45,16 @@ void DataLogger::logArchitecture() { json["coupling_map"] = architecture->getCouplingMap(); json["distances"] = architecture->getDistanceTable(); if (architecture->isFidelityAvailable()) { - auto& fidelity = json["fidelity"]; - fidelity["single_qubit_fidelities"] = architecture->getSingleQubitFidelities(); - fidelity["two_qubit_fidelities"] = architecture->getFidelityTable(); + auto& fidelity = json["fidelity"]; + fidelity["single_qubit_fidelities"] = + architecture->getSingleQubitFidelities(); + fidelity["two_qubit_fidelities"] = architecture->getFidelityTable(); fidelity["single_qubit_fidelity_costs"] = architecture->getSingleQubitFidelityCosts(); - fidelity["two_qubit_fidelity_costs"] = architecture->getTwoQubitFidelityCosts(); - fidelity["swap_fidelity_costs"] = architecture->getSwapFidelityCosts(); - fidelity["fidelity_distances"] = architecture->getFidelityDistanceTables(); + fidelity["two_qubit_fidelity_costs"] = + architecture->getTwoQubitFidelityCosts(); + fidelity["swap_fidelity_costs"] = architecture->getSwapFidelityCosts(); + fidelity["fidelity_distances"] = architecture->getFidelityDistanceTables(); } of << json.dump(2); of.close(); diff --git a/src/Mapper.cpp b/src/Mapper.cpp index bdf935038..285670ab6 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -250,7 +250,12 @@ void Mapper::splitLayer(std::size_t index, twoQubitMultiplicity.insert(edge); } layers[index] = layer0; - layers.insert(layers.begin() + static_cast>::difference_type>(index) + 1, layer1); + layers.insert( + layers.begin() + + static_cast>::difference_type>( + index) + + 1, + layer1); results.input.layers = layers.size(); } diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index e7d3dfadd..4f1d48511 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -1269,11 +1269,9 @@ TEST(HeuristicTestFidelity, LayerSplitting) { "nodes_layer_0.presplit-0.csv", "nodes_layer_0.presplit-1.csv", "nodes_layer_1.presplit-0.csv", "nodes_layer_3.presplit-0.csv"}; for (auto& path : layerNodeFilePaths) { - auto layerNodeFile = - std::ifstream(settings.dataLoggingPath + path); + auto layerNodeFile = std::ifstream(settings.dataLoggingPath + path); if (!layerNodeFile.is_open()) { - FAIL() << "Could not open file " << settings.dataLoggingPath - << path; + FAIL() << "Could not open file " << settings.dataLoggingPath << path; } std::string line; while (std::getline(layerNodeFile, line)) { From aa1f2fdac2f962f7f1a13b7c94506b97f9706e9e Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 10 Nov 2023 04:27:56 +0100 Subject: [PATCH 030/108] further lint fixes --- include/DataLogger.hpp | 4 ++-- test/test_heuristic.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index 6f0148375..7a640fa79 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -21,10 +21,10 @@ class DataLogger { initLog(); logArchitecture(); logInputCircuit(inputCircuit); - for (std::size_t i = 0; i < qc.getNqubits(); ++i) { + 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 < qc.getNcbits(); ++i) { + for (std::size_t i = 0; i < inputCircuit.getNcbits(); ++i) { cregs.emplace_back("c", "c[" + std::to_string(i) + "]"); } } diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index e7d3dfadd..7cd817c2a 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -1265,7 +1265,7 @@ TEST(HeuristicTestFidelity, LayerSplitting) { EXPECT_NEAR(result.output.totalLogFidelity, expectedFidelity, 1e-6); // check data log - std::array layerNodeFilePaths = { + const std::array layerNodeFilePaths = { "nodes_layer_0.presplit-0.csv", "nodes_layer_0.presplit-1.csv", "nodes_layer_1.presplit-0.csv", "nodes_layer_3.presplit-0.csv"}; for (auto& path : layerNodeFilePaths) { From 7ffb664a157e82744804800ed3bdd01960b17154 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 10 Nov 2023 04:51:37 +0100 Subject: [PATCH 031/108] adding const --- test/test_heuristic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 875c816d9..f424c9d3d 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -1268,7 +1268,7 @@ TEST(HeuristicTestFidelity, LayerSplitting) { const std::array layerNodeFilePaths = { "nodes_layer_0.presplit-0.csv", "nodes_layer_0.presplit-1.csv", "nodes_layer_1.presplit-0.csv", "nodes_layer_3.presplit-0.csv"}; - for (auto& path : layerNodeFilePaths) { + for (const auto& path : layerNodeFilePaths) { auto layerNodeFile = std::ifstream(settings.dataLoggingPath + path); if (!layerNodeFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << path; From 17ca44143ca8abc0c90c5250c22aa00e2b8c3f33 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Tue, 14 Nov 2023 00:39:44 +0100 Subject: [PATCH 032/108] preparation for iterative bidirectional routing --- include/Mapper.hpp | 52 ++- include/heuristic/HeuristicMapper.hpp | 17 +- src/Mapper.cpp | 177 +++++++-- src/heuristic/HeuristicMapper.cpp | 521 +++++++++++++------------- 4 files changed, 457 insertions(+), 310 deletions(-) diff --git a/include/Mapper.hpp b/include/Mapper.hpp index 803b86473..5900e3517 100644 --- a/include/Mapper.hpp +++ b/include/Mapper.hpp @@ -30,6 +30,14 @@ */ using TwoQubitMultiplicity = std::map>; + +/** + * number of single-qubit gates acting on each logical qubit in some + * layer. + * + * e.g. with multiplicity {1,0,2} there is 1 1Q-gate acting on q0, no 1Q-gates + * acting on q1, and 2 1Q-gates acting on q2 + */ using SingleQubitMultiplicity = std::vector; constexpr std::int16_t DEFAULT_POSITION = -1; @@ -77,6 +85,32 @@ class Mapper { * gates in an inner vector */ std::vector> layers{}; + + /** + * @brief The number of 1Q-gates acting on each logical qubit in each layer + */ + std::vector singleQubitMultiplicities{}; + + /** + * @brief The number of 2Q-gates acting on each pair of logical qubits in each + * layer + */ + std::vector twoQubitMultiplicities{}; + + /** + * @brief For each layer the set of all logical qubits, which are acted on by a gate in the layer + */ + std::vector> activeQubits{}; + + /** + * @brief For each layer the set of all logical qubits, which are acted on by a 1Q-gate in the layer + */ + std::vector> activeQubits1QGates{}; + + /** + * @brief For each layer the set of all logical qubits, which are acted on by a 2Q-gate in the layer + */ + std::vector> activeQubits2QGates{}; /** * @brief containing the logical qubit currently mapped to each physical @@ -123,21 +157,25 @@ class Mapper { * a disjoint set of qubits */ virtual void createLayers(); + + /** + * @brief Returns true if the layer at the given index can be split into two + * without resulting in an empty layer (assuming the original layer only has disjoint 2Q-gate-blocks) + * + * @param index the index of the layer to be split + * @return true if the layer is splittable + * @return false if splitting the layer will result in an empty layer + */ + virtual bool isLayerSplittable(std::size_t index); /** * @brief Splits the layer at the given index into two layers with half as * many qubits acted on by gates in each layer * * @param index the index of the layer to be split - * @param singleQubitMultiplicity single qubit multiplicity of the layer to be - * split - * @param twoQubitMultiplicity two qubit multiplicity of the layer to be split * @param arch architecture on which the circuit is mapped */ - virtual void splitLayer(std::size_t index, - SingleQubitMultiplicity& singleQubitMultiplicity, - TwoQubitMultiplicity& twoQubitMultiplicity, - Architecture& arch); + virtual void splitLayer(std::size_t index, Architecture& arch); /** * gates are put in the last layer (from the back of the circuit) in which diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index fe69e40c5..a13a1c1cb 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -215,8 +215,15 @@ class HeuristicMapper : public Mapper { * of logical qubits in the current layer */ virtual void - mapUnmappedGates(const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity); + mapUnmappedGates(std::size_t layer); + + /** + * @brief Routes the input circuit, i.e. inserts SWAPs to meet topology constraints and optimize fidelity if activated + * + * @param reverse if true, the circuit is routed from the end to the beginning (used in iterative bidirectional routing) + * @param pseudoRouting if true, routing will only be simulated without altering the circuit or modifying any other global data except for `qubits` and `locations`, which will hold the final qubit layout afterwards + */ + virtual void routeCircuit(bool reverse = false, bool pseudoRouting = false); /** * @brief search for an optimal mapping/set of swaps using A*-search and the @@ -243,9 +250,7 @@ class HeuristicMapper : public Mapper { * of logical qubits in the current layer */ void expandNode(const std::unordered_set& consideredQubits, - Node& node, std::size_t layer, - const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity); + Node& node, std::size_t layer); /** * @brief creates a new node with a swap on the given edge and adds it to @@ -259,8 +264,6 @@ class HeuristicMapper : public Mapper { */ void expandNodeAddOneSwap( const Edge& swap, Node& node, std::size_t layer, - const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity, const std::unordered_set& consideredQubits); /** diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 285670ab6..58fec0193 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -184,37 +184,127 @@ void Mapper::createLayers() { } } 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::unordered_set{}); + activeQubits1QGates = std::vector>(layers.size(), std::unordered_set{}); + activeQubits2QGates = std::vector>(layers.size(), std::unordered_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]; + } 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++; + } + } + } + } + } } -void Mapper::splitLayer(std::size_t index, - SingleQubitMultiplicity& singleQubitMultiplicity, - TwoQubitMultiplicity& twoQubitMultiplicity, - Architecture& arch) { - std::vector layer0 = {}; - std::vector layer1 = {}; +bool Mapper::isLayerSplittable(std::size_t index) { + if (twoQubitMultiplicities.at(index).size() > 1) { + return true; + } + if (activeQubits1QGates.at(index).size() > 2) { + return true; + } + if (twoQubitMultiplicities.at(index).size() == 0) { + return false; + } + // check if there is a 1Q gate on a qubit that is not part of the 2Q gate + for (auto q : activeQubits1QGates.at(index)) { + if(activeQubits2QGates.at(index).find(q) == activeQubits2QGates.at(index).end()) { + return true; + } + } + return false; +} + +void Mapper::splitLayer(std::size_t index, Architecture& arch) { + const SingleQubitMultiplicity& singleQubitMultiplicity = singleQubitMultiplicities.at(index); + const TwoQubitMultiplicity& twoQubitMultiplicity = twoQubitMultiplicities.at(index); + std::vector layer0{}; + std::vector layer1{}; SingleQubitMultiplicity singleQubitMultiplicity0(arch.getNqubits(), 0); SingleQubitMultiplicity singleQubitMultiplicity1(arch.getNqubits(), 0); - TwoQubitMultiplicity twoQubitMultiplicity0 = {}; - TwoQubitMultiplicity twoQubitMultiplicity1 = {}; + TwoQubitMultiplicity twoQubitMultiplicity0{}; + TwoQubitMultiplicity twoQubitMultiplicity1{}; + std::unordered_set activeQubits0{}; + std::unordered_set activeQubits1QGates0{}; + std::unordered_set activeQubits2QGates0{}; + std::unordered_set activeQubits1{}; + std::unordered_set activeQubits1QGates1{}; + std::unordered_set activeQubits2QGates1{}; + + bool even = false; + for (auto edge : twoQubitMultiplicity) { + if (even) { + twoQubitMultiplicity0.insert(edge); + activeQubits0.emplace(edge.first.first); + activeQubits0.emplace(edge.first.second); + activeQubits2QGates0.emplace(edge.first.first); + activeQubits2QGates0.emplace(edge.first.second); + } else { + twoQubitMultiplicity1.insert(edge); + activeQubits1.emplace(edge.first.first); + activeQubits1.emplace(edge.first.second); + activeQubits2QGates1.emplace(edge.first.first); + activeQubits2QGates1.emplace(edge.first.second); + } + even = !even; + } - bool even = true; + even = true; for (std::size_t q = 0; q < singleQubitMultiplicity.size(); ++q) { if (singleQubitMultiplicity[q] == 0) { continue; } - if (even) { + if (activeQubits2QGates0.find(q) != activeQubits2QGates0.end()) { singleQubitMultiplicity0[q] = singleQubitMultiplicity[q]; - } else { + activeQubits0.emplace(q); + activeQubits1QGates0.emplace(q); + continue; + } + if (activeQubits2QGates1.find(q) != activeQubits2QGates1.end()) { singleQubitMultiplicity1[q] = singleQubitMultiplicity[q]; + activeQubits1.emplace(q); + activeQubits1QGates1.emplace(q); + continue; } - even = !even; - } - even = false; - for (auto edge : twoQubitMultiplicity) { if (even) { - twoQubitMultiplicity0.insert(edge); + singleQubitMultiplicity0[q] = singleQubitMultiplicity[q]; + activeQubits0.emplace(q); + activeQubits1QGates0.emplace(q); } else { - twoQubitMultiplicity1.insert(edge); + singleQubitMultiplicity1[q] = singleQubitMultiplicity[q]; + activeQubits1.emplace(q); + activeQubits1QGates1.emplace(q); } even = !even; } @@ -227,28 +317,14 @@ void Mapper::splitLayer(std::size_t index, layer1.push_back(gate); } } else { - Edge edge; - if (gate.control < gate.target) { - edge = {gate.control, gate.target}; - } else { - edge = {gate.target, gate.control}; - } - if (twoQubitMultiplicity0.find(edge) != twoQubitMultiplicity0.end()) { + if (activeQubits2QGates0.find(gate.target) != activeQubits2QGates0.end()) { layer0.push_back(gate); } else { layer1.push_back(gate); } } } - - singleQubitMultiplicity.clear(); - twoQubitMultiplicity.clear(); - for (auto q : singleQubitMultiplicity0) { - singleQubitMultiplicity.push_back(q); - } - for (auto edge : twoQubitMultiplicity0) { - twoQubitMultiplicity.insert(edge); - } + layers[index] = layer0; layers.insert( layers.begin() + @@ -256,6 +332,41 @@ void Mapper::splitLayer(std::size_t index, index) + 1, layer1); + singleQubitMultiplicities[index] = singleQubitMultiplicity0; + singleQubitMultiplicities.insert( + singleQubitMultiplicities.begin() + + static_cast::difference_type>( + index) + + 1, + singleQubitMultiplicity1); + twoQubitMultiplicities[index] = twoQubitMultiplicity0; + twoQubitMultiplicities.insert( + twoQubitMultiplicities.begin() + + static_cast::difference_type>( + index) + + 1, + twoQubitMultiplicity1); + activeQubits[index] = activeQubits0; + activeQubits.insert( + activeQubits.begin() + + static_cast>::difference_type>( + index) + + 1, + activeQubits1); + activeQubits1QGates[index] = activeQubits1QGates0; + activeQubits1QGates.insert( + activeQubits1QGates.begin() + + static_cast>::difference_type>( + index) + + 1, + activeQubits1QGates1); + activeQubits2QGates[index] = activeQubits2QGates0; + activeQubits2QGates.insert( + activeQubits2QGates.begin() + + static_cast>::difference_type>( + index) + + 1, + activeQubits2QGates1); results.input.layers = layers.size(); } diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 1aada8c33..ab203d7f3 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -49,6 +49,14 @@ void HeuristicMapper::map(const Configuration& configuration) { << std::endl; config.teleportationQubits = 0; } + if (config.splitLayerAfterExpandedNodes > 0 && (config.layering == Layering::OddGates || config.layering == Layering::QubitTriangle)) { + std::cerr << "Layer splitting cannot be used with odd gates or qubit " + "triangle layering, as it might not conserve gate order for " + "non-disjoint gate sets! Performing mapping without layer " + "splitting." + << std::endl; + config.splitLayerAfterExpandedNodes = 0; + } const auto start = std::chrono::steady_clock::now(); initResults(); @@ -67,194 +75,7 @@ void HeuristicMapper::map(const Configuration& configuration) { printQubits(std::clog); } - std::size_t gateidx = 0; - std::vector gatesToAdjust{}; - results.output.gates = 0U; - for (std::size_t i = 0; i < layers.size(); ++i) { - const Node result = aStarMap(i); - - qubits = result.qubits; - locations = result.locations; - - if (config.verbose) { - printLocations(std::clog); - printQubits(std::clog); - } - - // initial layer needs no swaps - if (i != 0 || config.swapOnFirstLayer) { - for (const auto& swaps : result.swaps) { - for (const auto& swap : swaps) { - if (swap.op == qc::SWAP) { - if (config.verbose) { - std::clog << "SWAP: " << swap.first << " <-> " << swap.second - << "\n"; - } - if (!architecture->isEdgeConnected({swap.first, swap.second}) && - !architecture->isEdgeConnected({swap.second, swap.first})) { - throw QMAPException( - "Invalid SWAP: " + std::to_string(swap.first) + "<->" + - std::to_string(swap.second)); - } - qcMapped.swap(swap.first, swap.second); - results.output.swaps++; - } else if (swap.op == qc::Teleportation) { - if (config.verbose) { - std::clog << "TELE: " << swap.first << " <-> " << swap.second - << "\n"; - } - qcMapped.emplace_back( - qcMapped.getNqubits(), - qc::Targets{static_cast(swap.first), - static_cast(swap.second), - static_cast(swap.middleAncilla)}, - qc::Teleportation); - results.output.teleportations++; - } - gateidx++; - } - } - } - - // add gates of the layer to circuit - for (const auto& gate : layers.at(i)) { - auto* op = dynamic_cast(gate.op); - if (op == nullptr) { - throw QMAPException( - "Cast to StandardOperation not possible during mapping. Check that " - "circuit contains only StandardOperations"); - } - - if (gate.singleQubit()) { - if (locations.at(gate.target) == DEFAULT_POSITION) { - qcMapped.emplace_back( - qcMapped.getNqubits(), gate.target, op->getType(), - op->getParameter()); - gatesToAdjust.push_back(gateidx); - gateidx++; - } else { - qcMapped.emplace_back( - qcMapped.getNqubits(), locations.at(gate.target), op->getType(), - op->getParameter()); - gateidx++; - } - } else { - const Edge cnot = { - locations.at(static_cast(gate.control)), - locations.at(gate.target)}; - if (!architecture->isEdgeConnected(cnot)) { - const Edge reverse = {cnot.second, cnot.first}; - if (!architecture->isEdgeConnected(reverse)) { - throw QMAPException( - "Invalid CNOT: " + std::to_string(reverse.first) + "-" + - std::to_string(reverse.second)); - } - qcMapped.h(reverse.first); - qcMapped.h(reverse.second); - qcMapped.cx(qc::Control{static_cast(reverse.first)}, - reverse.second); - qcMapped.h(reverse.second); - qcMapped.h(reverse.first); - - results.output.directionReverse++; - gateidx += 5; - } else { - qcMapped.cx(qc::Control{static_cast(cnot.first)}, - cnot.second); - gateidx++; - } - } - } - } - - if (config.debug && results.heuristicBenchmark.expandedNodes > 0) { - auto& benchmark = results.heuristicBenchmark; - benchmark.timePerNode /= static_cast(benchmark.expandedNodes); - benchmark.averageBranchingFactor = - static_cast(benchmark.generatedNodes - layers.size()) / - static_cast(benchmark.expandedNodes); - for (const auto& layer : results.layerHeuristicBenchmark) { - benchmark.effectiveBranchingFactor += - layer.effectiveBranchingFactor * - (static_cast(layer.expandedNodes) / - static_cast(benchmark.expandedNodes)); - } - } - - // infer output permutation from qubit locations - qcMapped.outputPermutation.clear(); - for (std::size_t i = 0U; i < architecture->getNqubits(); ++i) { - if (const auto lq = qubits.at(i); lq != -1) { - const auto logicalQubit = static_cast(lq); - // check whether this is a qubit from the original circuit - if (logicalQubit < qc.getNqubits()) { - qcMapped.outputPermutation[static_cast(i)] = - static_cast(qubits.at(i)); - } else { - qcMapped.setLogicalQubitGarbage(logicalQubit); - } - } - } - - // fix single qubit gates - if (!gatesToAdjust.empty()) { - gateidx--; // index of last operation - for (auto it = qcMapped.rbegin(); it != qcMapped.rend(); ++it, --gateidx) { - auto* op = dynamic_cast(it->get()); - if (op == nullptr) { - throw QMAPException( - "Cast to StandardOperation not possible during mapping. Check that " - "circuit contains only StandardOperations"); - } - if (op->getType() == qc::SWAP) { - const auto q0 = qubits.at(op->getTargets().at(0)); - const auto q1 = qubits.at(op->getTargets().at(1)); - qubits.at(op->getTargets().at(0)) = q1; - qubits.at(op->getTargets().at(1)) = q0; - - if (q0 != DEFAULT_POSITION) { - locations.at(static_cast(q0)) = - static_cast(op->getTargets().at(1)); - } - if (q1 != DEFAULT_POSITION) { - locations.at(static_cast(q1)) = - static_cast(op->getTargets().at(0)); - } - } - if (!gatesToAdjust.empty() && gatesToAdjust.back() == gateidx) { - gatesToAdjust.pop_back(); - auto target = op->getTargets().at(0); - auto targetLocation = locations.at(target); - - if (targetLocation == -1) { - // qubit only occurs in single qubit gates, can be mapped to an - // arbitrary free qubit - std::uint16_t loc = 0; - while (qubits.at(loc) != DEFAULT_POSITION) { - ++loc; - } - locations.at(target) = static_cast(loc); - qubits.at(loc) = static_cast(target); - op->setTargets({static_cast(loc)}); - qcMapped.initialLayout.at(target) = loc; - qcMapped.outputPermutation[static_cast(loc)] = target; - qcMapped.garbage.at(loc) = false; - } else { - op->setTargets({static_cast(targetLocation)}); - } - } - } - } - - // mark every qubit that is not mapped to a logical qubit as garbage - std::size_t count = 0U; - for (std::size_t i = 0U; i < architecture->getNqubits(); ++i) { - if (const auto lq = qubits.at(i); lq == -1) { - qcMapped.setLogicalQubitGarbage( - static_cast(qc.getNqubits() + count)); - ++count; - } - } + routeCircuit(); postMappingOptimizations(config); countGates(qcMapped, results.output); @@ -377,12 +198,10 @@ void HeuristicMapper::createInitialMapping() { } } -void HeuristicMapper::mapUnmappedGates( - const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity) { +void HeuristicMapper::mapUnmappedGates(std::size_t layer) { if (results.config.considerFidelity) { - for (std::size_t q = 0; q < singleQubitGateMultiplicity.size(); ++q) { - if (singleQubitGateMultiplicity.at(q) == 0) { + for (std::size_t q = 0; q < singleQubitMultiplicities.at(layer).size(); ++q) { + if (singleQubitMultiplicities.at(layer).at(q) == 0) { continue; } if (locations.at(q) == DEFAULT_POSITION) { @@ -400,7 +219,7 @@ void HeuristicMapper::mapUnmappedGates( } } - for (const auto& [logEdge, _] : twoQubitGateMultiplicity) { + for (const auto& [logEdge, _] : twoQubitMultiplicities.at(layer)) { const auto& [q1, q2] = logEdge; auto q1Location = locations.at(q1); @@ -480,68 +299,254 @@ void HeuristicMapper::mapToMinDistance(const std::uint16_t source, qc::QuantumComputation::findAndSWAP(target, *pos, qcMapped.outputPermutation); } -HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { +void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { + // save original global data for restoring it later if pseudo routing is used + const auto originalResults = results; + const auto originalLayers = layers; + const auto originalSingleQubitMultiplicities = singleQubitMultiplicities; + const auto originalTwoQubitMultiplicities = twoQubitMultiplicities; + const auto originalActiveQubits = activeQubits; + const auto originalActiveQubits1QGates = activeQubits1QGates; + const auto originalActiveQubits2QGates = activeQubits2QGates; + auto& config = results.config; - nextNodeId = 0; + if (pseudoRouting) { + config.dataLoggingPath = ""; // disable data logging for pseudo routing + config.debug = false; + + if (config.verbose) { + std::clog << std::endl << std::endl << "Pseudo routing (" << (reverse ? "backward" : "forward") << " pass):" << std::endl; + } + } + + std::size_t gateidx = 0; + std::vector gatesToAdjust{}; + results.output.gates = 0U; + for (std::size_t i = 0; i < layers.size(); ++i) { + auto layerIndex = (reverse ? layers.size() - i - 1 : i); + const Node result = aStarMap(layerIndex); - std::unordered_set consideredQubits{}; - std::size_t consideredQubitsSingleGates = 0; - Node node(nextNodeId++); - // number of single qubit gates acting on each logical qubit in the current - // layer - SingleQubitMultiplicity singleQubitGateMultiplicity( - architecture->getNqubits(), 0); - // number of two qubit gates acting on each logical qubit edge in the current - // layer where the first number in the value pair corresponds to the number of - // edges having their gates given as (control, target) in the key, and the - // second with all gates in reverse to that - TwoQubitMultiplicity twoQubitGateMultiplicity{}; - Node bestDoneNode(0); - bool done = false; - const bool considerFidelity = config.considerFidelity; - - for (const auto& gate : layers.at(layer)) { - if (gate.singleQubit()) { - if (singleQubitGateMultiplicity.at(gate.target) == 0) { - ++consideredQubitsSingleGates; + qubits = result.qubits; + locations = result.locations; + + if (config.verbose) { + printLocations(std::clog); + printQubits(std::clog); + } + + if (pseudoRouting) { + continue; + } + + // initial layer needs no swaps + if (layerIndex != 0 || config.swapOnFirstLayer) { + for (const auto& swaps : result.swaps) { + for (const auto& swap : swaps) { + if (swap.op == qc::SWAP) { + if (config.verbose) { + std::clog << "SWAP: " << swap.first << " <-> " << swap.second + << "\n"; + } + if (!architecture->isEdgeConnected({swap.first, swap.second}) && + !architecture->isEdgeConnected({swap.second, swap.first})) { + throw QMAPException( + "Invalid SWAP: " + std::to_string(swap.first) + "<->" + + std::to_string(swap.second)); + } + qcMapped.swap(swap.first, swap.second); + results.output.swaps++; + } else if (swap.op == qc::Teleportation) { + if (config.verbose) { + std::clog << "TELE: " << swap.first << " <-> " << swap.second + << "\n"; + } + qcMapped.emplace_back( + qcMapped.getNqubits(), + qc::Targets{static_cast(swap.first), + static_cast(swap.second), + static_cast(swap.middleAncilla)}, + qc::Teleportation); + results.output.teleportations++; + } + gateidx++; + } } - ++singleQubitGateMultiplicity.at(gate.target); - if (considerFidelity) { - consideredQubits.emplace(gate.target); + } + + // add gates of the layer to circuit + for (const auto& gate : layers.at(layerIndex)) { + auto* op = dynamic_cast(gate.op); + if (op == nullptr) { + throw QMAPException( + "Cast to StandardOperation not possible during mapping. Check that " + "circuit contains only StandardOperations"); } - } else { - consideredQubits.emplace(gate.control); - consideredQubits.emplace(gate.target); - if (gate.control >= gate.target) { - const auto edge = - std::pair(gate.target, static_cast(gate.control)); - if (twoQubitGateMultiplicity.find(edge) == - twoQubitGateMultiplicity.end()) { - twoQubitGateMultiplicity[edge] = {0, 1}; + + if (gate.singleQubit()) { + if (locations.at(gate.target) == DEFAULT_POSITION) { + qcMapped.emplace_back( + qcMapped.getNqubits(), gate.target, op->getType(), + op->getParameter()); + gatesToAdjust.push_back(gateidx); + gateidx++; } else { - twoQubitGateMultiplicity[edge].second++; + qcMapped.emplace_back( + qcMapped.getNqubits(), locations.at(gate.target), op->getType(), + op->getParameter()); + gateidx++; } } else { - const auto edge = - std::pair(static_cast(gate.control), gate.target); - if (twoQubitGateMultiplicity.find(edge) == - twoQubitGateMultiplicity.end()) { - twoQubitGateMultiplicity[edge] = {1, 0}; + const Edge cnot = { + locations.at(static_cast(gate.control)), + locations.at(gate.target)}; + if (!architecture->isEdgeConnected(cnot)) { + const Edge reverse = {cnot.second, cnot.first}; + if (!architecture->isEdgeConnected(reverse)) { + throw QMAPException( + "Invalid CNOT: " + std::to_string(reverse.first) + "-" + + std::to_string(reverse.second)); + } + qcMapped.h(reverse.first); + qcMapped.h(reverse.second); + qcMapped.cx(qc::Control{static_cast(reverse.first)}, + reverse.second); + qcMapped.h(reverse.second); + qcMapped.h(reverse.first); + + results.output.directionReverse++; + gateidx += 5; + } else { + qcMapped.cx(qc::Control{static_cast(cnot.first)}, + cnot.second); + gateidx++; + } + } + } + } + + if (config.debug && results.heuristicBenchmark.expandedNodes > 0) { + auto& benchmark = results.heuristicBenchmark; + benchmark.timePerNode /= static_cast(benchmark.expandedNodes); + benchmark.averageBranchingFactor = + static_cast(benchmark.generatedNodes - layers.size()) / + static_cast(benchmark.expandedNodes); + for (const auto& layer : results.layerHeuristicBenchmark) { + benchmark.effectiveBranchingFactor += + layer.effectiveBranchingFactor * + (static_cast(layer.expandedNodes) / + static_cast(benchmark.expandedNodes)); + } + } + + + if (pseudoRouting) { + // restore original global data + results = originalResults; + layers = originalLayers; + singleQubitMultiplicities = originalSingleQubitMultiplicities; + twoQubitMultiplicities = originalTwoQubitMultiplicities; + activeQubits = originalActiveQubits; + activeQubits1QGates = originalActiveQubits1QGates; + activeQubits2QGates = originalActiveQubits2QGates; + return; + } + + // infer output permutation from qubit locations + qcMapped.outputPermutation.clear(); + for (std::size_t i = 0U; i < architecture->getNqubits(); ++i) { + if (const auto lq = qubits.at(i); lq != -1) { + const auto logicalQubit = static_cast(lq); + // check whether this is a qubit from the original circuit + if (logicalQubit < qc.getNqubits()) { + qcMapped.outputPermutation[static_cast(i)] = + static_cast(qubits.at(i)); + } else { + qcMapped.setLogicalQubitGarbage(logicalQubit); + } + } + } + + // fix single qubit gates + if (!gatesToAdjust.empty()) { + gateidx--; // index of last operation + for (auto it = qcMapped.rbegin(); it != qcMapped.rend(); ++it, --gateidx) { + auto* op = dynamic_cast(it->get()); + if (op == nullptr) { + throw QMAPException( + "Cast to StandardOperation not possible during mapping. Check that " + "circuit contains only StandardOperations"); + } + if (op->getType() == qc::SWAP) { + const auto q0 = qubits.at(op->getTargets().at(0)); + const auto q1 = qubits.at(op->getTargets().at(1)); + qubits.at(op->getTargets().at(0)) = q1; + qubits.at(op->getTargets().at(1)) = q0; + + if (q0 != DEFAULT_POSITION) { + locations.at(static_cast(q0)) = + static_cast(op->getTargets().at(1)); + } + if (q1 != DEFAULT_POSITION) { + locations.at(static_cast(q1)) = + static_cast(op->getTargets().at(0)); + } + } + if (!gatesToAdjust.empty() && gatesToAdjust.back() == gateidx) { + gatesToAdjust.pop_back(); + auto target = op->getTargets().at(0); + auto targetLocation = locations.at(target); + + if (targetLocation == -1) { + // qubit only occurs in single qubit gates, can be mapped to an + // arbitrary free qubit + std::uint16_t loc = 0; + while (qubits.at(loc) != DEFAULT_POSITION) { + ++loc; + } + locations.at(target) = static_cast(loc); + qubits.at(loc) = static_cast(target); + op->setTargets({static_cast(loc)}); + qcMapped.initialLayout.at(target) = loc; + qcMapped.outputPermutation[static_cast(loc)] = target; + qcMapped.garbage.at(loc) = false; } else { - twoQubitGateMultiplicity[edge].first++; + op->setTargets({static_cast(targetLocation)}); } } } } - mapUnmappedGates(singleQubitGateMultiplicity, twoQubitGateMultiplicity); + // mark every qubit that is not mapped to a logical qubit as garbage + std::size_t count = 0U; + for (std::size_t i = 0U; i < architecture->getNqubits(); ++i) { + if (const auto lq = qubits.at(i); lq == -1) { + qcMapped.setLogicalQubitGarbage( + static_cast(qc.getNqubits() + count)); + ++count; + } + } +} + +HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { + auto& config = results.config; + const bool considerFidelity = config.considerFidelity; + nextNodeId = 0; + + const std::unordered_set& consideredQubits = (considerFidelity ? activeQubits[layer] : activeQubits2QGates[layer]); + const SingleQubitMultiplicity& singleQubitMultiplicity = singleQubitMultiplicities.at(layer); + const TwoQubitMultiplicity& twoQubitMultiplicity = twoQubitMultiplicities.at(layer); + Node node(nextNodeId++); + Node bestDoneNode(0); + bool done = false; + + mapUnmappedGates(layer); node.locations = locations; node.qubits = qubits; - node.recalculateFixedCost(*architecture, singleQubitGateMultiplicity, - twoQubitGateMultiplicity, config.considerFidelity); - node.updateHeuristicCost(*architecture, singleQubitGateMultiplicity, - twoQubitGateMultiplicity, consideredQubits, + node.recalculateFixedCost(*architecture, singleQubitMultiplicity, + twoQubitMultiplicity, config.considerFidelity); + node.updateHeuristicCost(*architecture, singleQubitMultiplicity, + twoQubitMultiplicity, consideredQubits, config.admissibleHeuristic, config.considerFidelity); if (config.dataLoggingEnabled()) { @@ -554,9 +559,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { const auto start = std::chrono::steady_clock::now(); std::size_t expandedNodes = 0; - const bool splittable = - (twoQubitGateMultiplicity.size() > 1 || consideredQubitsSingleGates > 1 || - (!twoQubitGateMultiplicity.empty() && consideredQubitsSingleGates > 0)); + const bool splittable = isLayerSplittable(layer); while (!nodes.empty() && (!done || nodes.top().getTotalCost() < bestDoneNode.getTotalFixedCost())) { @@ -569,13 +572,12 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { compOp.emplace_back(op); } - dataLogger->logFinalizeLayer(layer, compOp, singleQubitGateMultiplicity, - twoQubitGateMultiplicity, qubits, 0, 0, 0, + dataLogger->logFinalizeLayer(layer, compOp, singleQubitMultiplicity, + twoQubitMultiplicity, qubits, 0, 0, 0, 0, {}, {}, 0); dataLogger->splitLayer(); } - splitLayer(layer, singleQubitGateMultiplicity, twoQubitGateMultiplicity, - *architecture); + splitLayer(layer, *architecture); if (config.verbose) { std::clog << "Split layer" << std::endl; } @@ -594,8 +596,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { } } nodes.pop(); - expandNode(consideredQubits, current, layer, singleQubitGateMultiplicity, - twoQubitGateMultiplicity); + expandNode(consideredQubits, current, layer); ++expandedNodes; } @@ -640,7 +641,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { } dataLogger->logFinalizeLayer( - layer, compOp, singleQubitGateMultiplicity, twoQubitGateMultiplicity, + layer, compOp, singleQubitMultiplicities.at(layer), twoQubitMultiplicities.at(layer), qubits, result.id, result.costFixed, result.costHeur, result.lookaheadPenalty, result.qubits, result.swaps, result.depth); } @@ -655,9 +656,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { void HeuristicMapper::expandNode( const std::unordered_set& consideredQubits, Node& node, - std::size_t layer, - const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity) { + std::size_t layer) { std::vector> usedSwaps; usedSwaps.reserve(architecture->getNqubits()); for (int p = 0; p < architecture->getNqubits(); ++p) { @@ -716,16 +715,14 @@ void HeuristicMapper::expandNode( auto q1 = node.qubits.at(edge.first); auto q2 = node.qubits.at(edge.second); if (q2 == -1 || q1 == -1) { - expandNodeAddOneSwap(edge, node, layer, singleQubitGateMultiplicity, - twoQubitGateMultiplicity, consideredQubits); + expandNodeAddOneSwap(edge, node, layer, consideredQubits); } else if (!usedSwaps.at(static_cast(q1)) .at(static_cast(q2))) { usedSwaps.at(static_cast(q1)) .at(static_cast(q2)) = true; usedSwaps.at(static_cast(q2)) .at(static_cast(q1)) = true; - expandNodeAddOneSwap(edge, node, layer, singleQubitGateMultiplicity, - twoQubitGateMultiplicity, consideredQubits); + expandNodeAddOneSwap(edge, node, layer, consideredQubits); } } } @@ -734,8 +731,6 @@ void HeuristicMapper::expandNode( void HeuristicMapper::expandNodeAddOneSwap( const Edge& swap, Node& node, const std::size_t layer, - const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity, const std::unordered_set& consideredQubits) { const auto& config = results.config; @@ -744,14 +739,14 @@ void HeuristicMapper::expandNodeAddOneSwap( if (architecture->isEdgeConnected(swap) || architecture->isEdgeConnected(Edge{swap.second, swap.first})) { - newNode.applySWAP(swap, *architecture, singleQubitGateMultiplicity, - twoQubitGateMultiplicity, config.considerFidelity); + newNode.applySWAP(swap, *architecture, singleQubitMultiplicities.at(layer), + twoQubitMultiplicities.at(layer), config.considerFidelity); } else { newNode.applyTeleportation(swap, *architecture); } - newNode.updateHeuristicCost(*architecture, singleQubitGateMultiplicity, - twoQubitGateMultiplicity, consideredQubits, + newNode.updateHeuristicCost(*architecture, singleQubitMultiplicities.at(layer), + twoQubitMultiplicities.at(layer), consideredQubits, results.config.admissibleHeuristic, results.config.considerFidelity); From 286a14bd0cf4ec5606b140fb0da3adc80ed426b9 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Tue, 14 Nov 2023 01:08:43 +0100 Subject: [PATCH 033/108] iterative bidirectional routing --- include/configuration/Configuration.hpp | 2 ++ include/heuristic/HeuristicMapper.hpp | 5 +++-- src/heuristic/HeuristicMapper.cpp | 27 ++++++++++++++++++------- src/mqt/qmap/compile.py | 3 +++ src/mqt/qmap/pyqmap.pyi | 1 + src/python/bindings.cpp | 1 + 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index ae1832cd6..12a1d3d3e 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -41,6 +41,8 @@ struct Configuration { // initial layout to use for heuristic approach InitialLayout initialLayout = InitialLayout::None; + + std::size_t iterativeBidirectionalRouting = 0; // lookahead scheme settings bool lookahead = true; diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index a13a1c1cb..95dc12645 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -220,7 +220,7 @@ class HeuristicMapper : public Mapper { /** * @brief Routes the input circuit, i.e. inserts SWAPs to meet topology constraints and optimize fidelity if activated * - * @param reverse if true, the circuit is routed from the end to the beginning (used in iterative bidirectional routing) + * @param reverse if true, the circuit is routed from the end to the beginning (this will not produce a valid mapping, use only with pseudo routing!) * @param pseudoRouting if true, routing will only be simulated without altering the circuit or modifying any other global data except for `qubits` and `locations`, which will hold the final qubit layout afterwards */ virtual void routeCircuit(bool reverse = false, bool pseudoRouting = false); @@ -234,8 +234,9 @@ class HeuristicMapper : public Mapper { * current layer in their fields `costHeur` and `done`) * * @param layer index of the current circuit layer + * @param reverse if true, the circuit is mapped from the end to the beginning */ - virtual Node aStarMap(std::size_t layer); + virtual Node aStarMap(std::size_t layer, bool reverse); /** * @brief expand the given node by calling `expand_node_add_one_swap` for all diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index ab203d7f3..03905eb38 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -74,6 +74,21 @@ void HeuristicMapper::map(const Configuration& configuration) { printLocations(std::clog); printQubits(std::clog); } + + for (std::size_t i = 0; i < config.iterativeBidirectionalRouting; ++i) { + if (config.verbose) { + std::clog << std::endl << "Iterative bidirectional routing (forward pass " << i << "):" << std::endl; + } + routeCircuit(false, true); + if (config.verbose) { + std::clog << std::endl << "Iterative bidirectional routing (backward pass " << i << "):" << std::endl; + } + routeCircuit(true, true); + + if (config.verbose) { + std::clog << std::endl << "Main routing:" << std::endl; + } + } routeCircuit(); @@ -300,6 +315,8 @@ void HeuristicMapper::mapToMinDistance(const std::uint16_t source, } void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { + assert (!reverse || pseudoRouting); // reverse routing is only supported for pseudo routing + // save original global data for restoring it later if pseudo routing is used const auto originalResults = results; const auto originalLayers = layers; @@ -313,10 +330,6 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { if (pseudoRouting) { config.dataLoggingPath = ""; // disable data logging for pseudo routing config.debug = false; - - if (config.verbose) { - std::clog << std::endl << std::endl << "Pseudo routing (" << (reverse ? "backward" : "forward") << " pass):" << std::endl; - } } std::size_t gateidx = 0; @@ -324,7 +337,7 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { results.output.gates = 0U; for (std::size_t i = 0; i < layers.size(); ++i) { auto layerIndex = (reverse ? layers.size() - i - 1 : i); - const Node result = aStarMap(layerIndex); + const Node result = aStarMap(layerIndex, reverse); qubits = result.qubits; locations = result.locations; @@ -527,7 +540,7 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { } } -HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { +HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { auto& config = results.config; const bool considerFidelity = config.considerFidelity; nextNodeId = 0; @@ -582,7 +595,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer) { std::clog << "Split layer" << std::endl; } // recursively restart search with newly split layer - return aStarMap(layer); + return aStarMap(reverse ? layer + 1 : layer, reverse); } Node current = nodes.top(); if (current.done) { diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index d1631b20e..862d4b291 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -62,6 +62,7 @@ def compile( # noqa: A001 method: str | Method = "heuristic", consider_fidelity: bool = False, initial_layout: str | InitialLayout = "dynamic", + iterative_bidirectional_routing: int = 0, layering: str | Layering = "individual_gates", split_layer_after_expanded_nodes: int = 5000, lookaheads: int | None = 15, @@ -93,6 +94,7 @@ def compile( # noqa: A001 method: The mapping method to use. Either "heuristic" or "exact". Defaults to "heuristic". consider_fidelity: Whether to consider the fidelity of the gates. Defaults to False. initial_layout: The initial layout to use. Defaults to "dynamic". + iterative_bidirectional_routing: Number of iterative bidirectional routing passes to perform. Defaults to 0. layering: The layering strategy to use. Defaults to "individual_gates". split_layer_after_expanded_nodes: The number of expanded nodes after which to split a layer (set to 0 to turn off layer splitting). Defaults to 5000. lookaheads: The number of lookaheads to be used or None if no lookahead should be used. Defaults to 15. @@ -132,6 +134,7 @@ def compile( # noqa: A001 config.method = Method(method) config.consider_fidelity = consider_fidelity config.initial_layout = InitialLayout(initial_layout) + config.iterative_bidirectional_routing = iterative_bidirectional_routing config.layering = Layering(layering) config.split_layer_after_expanded_nodes = split_layer_after_expanded_nodes config.encoding = Encoding(encoding) diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index 96ff00c8c..8dc70a782 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -116,6 +116,7 @@ class Configuration: first_lookahead_factor: float include_WCNF: bool # noqa: N815 initial_layout: InitialLayout + iterative_bidirectional_routing: int layering: Layering split_layer_after_expanded_nodes: int lookahead: bool diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 662120c98..bca8ac336 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -185,6 +185,7 @@ PYBIND11_MODULE(pyqmap, m) { .def_readwrite("split_layer_after_expanded_nodes", &Configuration::splitLayerAfterExpandedNodes) .def_readwrite("initial_layout", &Configuration::initialLayout) + .def_readwrite("iterative_bidirectional_routing", &Configuration::iterativeBidirectionalRouting) .def_readwrite("lookahead", &Configuration::lookahead) .def_readwrite("admissible_heuristic", &Configuration::admissibleHeuristic) From 9c0136115e2f83d732d422991c336978a3089799 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 00:09:39 +0000 Subject: [PATCH 034/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/Mapper.hpp | 32 ++++--- include/configuration/Configuration.hpp | 2 +- include/heuristic/HeuristicMapper.hpp | 17 ++-- src/Mapper.cpp | 46 ++++++---- src/heuristic/HeuristicMapper.cpp | 108 +++++++++++++----------- src/python/bindings.cpp | 3 +- 6 files changed, 120 insertions(+), 88 deletions(-) diff --git a/include/Mapper.hpp b/include/Mapper.hpp index 5900e3517..ff2a900cb 100644 --- a/include/Mapper.hpp +++ b/include/Mapper.hpp @@ -32,10 +32,10 @@ using TwoQubitMultiplicity = std::map>; /** - * number of single-qubit gates acting on each logical qubit in some + * number of single-qubit gates acting on each logical qubit in some * layer. - * - * e.g. with multiplicity {1,0,2} there is 1 1Q-gate acting on q0, no 1Q-gates + * + * e.g. with multiplicity {1,0,2} there is 1 1Q-gate acting on q0, no 1Q-gates * acting on q1, and 2 1Q-gates acting on q2 */ using SingleQubitMultiplicity = std::vector; @@ -85,30 +85,33 @@ class Mapper { * gates in an inner vector */ std::vector> layers{}; - + /** * @brief The number of 1Q-gates acting on each logical qubit in each layer */ std::vector singleQubitMultiplicities{}; - + /** * @brief The number of 2Q-gates acting on each pair of logical qubits in each * layer */ std::vector twoQubitMultiplicities{}; - + /** - * @brief For each layer the set of all logical qubits, which are acted on by a gate in the layer + * @brief For each layer the set of all logical qubits, which are acted on by + * a gate in the layer */ std::vector> activeQubits{}; - + /** - * @brief For each layer the set of all logical qubits, which are acted on by a 1Q-gate in the layer + * @brief For each layer the set of all logical qubits, which are acted on by + * a 1Q-gate in the layer */ std::vector> activeQubits1QGates{}; - + /** - * @brief For each layer the set of all logical qubits, which are acted on by a 2Q-gate in the layer + * @brief For each layer the set of all logical qubits, which are acted on by + * a 2Q-gate in the layer */ std::vector> activeQubits2QGates{}; @@ -157,11 +160,12 @@ class Mapper { * a disjoint set of qubits */ virtual void createLayers(); - + /** * @brief Returns true if the layer at the given index can be split into two - * without resulting in an empty layer (assuming the original layer only has disjoint 2Q-gate-blocks) - * + * without resulting in an empty layer (assuming the original layer only has + * disjoint 2Q-gate-blocks) + * * @param index the index of the layer to be split * @return true if the layer is splittable * @return false if splitting the layer will result in an empty layer diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 12a1d3d3e..21e5fd9af 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -41,7 +41,7 @@ struct Configuration { // initial layout to use for heuristic approach InitialLayout initialLayout = InitialLayout::None; - + std::size_t iterativeBidirectionalRouting = 0; // lookahead scheme settings diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index 95dc12645..f6650b7a2 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -214,14 +214,17 @@ class HeuristicMapper : public Mapper { * @param twoQubitGateMultiplicity number of two qubit gates acting on pairs * of logical qubits in the current layer */ - virtual void - mapUnmappedGates(std::size_t layer); - + virtual void mapUnmappedGates(std::size_t layer); + /** - * @brief Routes the input circuit, i.e. inserts SWAPs to meet topology constraints and optimize fidelity if activated - * - * @param reverse if true, the circuit is routed from the end to the beginning (this will not produce a valid mapping, use only with pseudo routing!) - * @param pseudoRouting if true, routing will only be simulated without altering the circuit or modifying any other global data except for `qubits` and `locations`, which will hold the final qubit layout afterwards + * @brief Routes the input circuit, i.e. inserts SWAPs to meet topology + * constraints and optimize fidelity if activated + * + * @param reverse if true, the circuit is routed from the end to the beginning + * (this will not produce a valid mapping, use only with pseudo routing!) + * @param pseudoRouting if true, routing will only be simulated without + * altering the circuit or modifying any other global data except for `qubits` + * and `locations`, which will hold the final qubit layout afterwards */ virtual void routeCircuit(bool reverse = false, bool pseudoRouting = false); diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 58fec0193..ac08a6ffe 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -184,14 +184,19 @@ void Mapper::createLayers() { } } 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::unordered_set{}); - activeQubits1QGates = std::vector>(layers.size(), std::unordered_set{}); - activeQubits2QGates = std::vector>(layers.size(), std::unordered_set{}); - + singleQubitMultiplicities = std::vector( + layers.size(), SingleQubitMultiplicity(architecture->getNqubits(), 0)); + twoQubitMultiplicities = + std::vector(layers.size(), TwoQubitMultiplicity{}); + activeQubits = std::vector>( + layers.size(), std::unordered_set{}); + activeQubits1QGates = std::vector>( + layers.size(), std::unordered_set{}); + activeQubits2QGates = std::vector>( + layers.size(), std::unordered_set{}); + for (std::size_t i = 0; i < layers.size(); ++i) { for (const auto& gate : layers[i]) { if (gate.singleQubit()) { @@ -239,7 +244,8 @@ bool Mapper::isLayerSplittable(std::size_t index) { } // check if there is a 1Q gate on a qubit that is not part of the 2Q gate for (auto q : activeQubits1QGates.at(index)) { - if(activeQubits2QGates.at(index).find(q) == activeQubits2QGates.at(index).end()) { + if (activeQubits2QGates.at(index).find(q) == + activeQubits2QGates.at(index).end()) { return true; } } @@ -247,8 +253,10 @@ bool Mapper::isLayerSplittable(std::size_t index) { } void Mapper::splitLayer(std::size_t index, Architecture& arch) { - const SingleQubitMultiplicity& singleQubitMultiplicity = singleQubitMultiplicities.at(index); - const TwoQubitMultiplicity& twoQubitMultiplicity = twoQubitMultiplicities.at(index); + const SingleQubitMultiplicity& singleQubitMultiplicity = + singleQubitMultiplicities.at(index); + const TwoQubitMultiplicity& twoQubitMultiplicity = + twoQubitMultiplicities.at(index); std::vector layer0{}; std::vector layer1{}; SingleQubitMultiplicity singleQubitMultiplicity0(arch.getNqubits(), 0); @@ -261,7 +269,7 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { std::unordered_set activeQubits1{}; std::unordered_set activeQubits1QGates1{}; std::unordered_set activeQubits2QGates1{}; - + bool even = false; for (auto edge : twoQubitMultiplicity) { if (even) { @@ -290,7 +298,7 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { activeQubits0.emplace(q); activeQubits1QGates0.emplace(q); continue; - } + } if (activeQubits2QGates1.find(q) != activeQubits2QGates1.end()) { singleQubitMultiplicity1[q] = singleQubitMultiplicity[q]; activeQubits1.emplace(q); @@ -317,14 +325,15 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { layer1.push_back(gate); } } else { - if (activeQubits2QGates0.find(gate.target) != activeQubits2QGates0.end()) { + if (activeQubits2QGates0.find(gate.target) != + activeQubits2QGates0.end()) { layer0.push_back(gate); } else { layer1.push_back(gate); } } } - + layers[index] = layer0; layers.insert( layers.begin() + @@ -349,21 +358,24 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { activeQubits[index] = activeQubits0; activeQubits.insert( activeQubits.begin() + - static_cast>::difference_type>( + static_cast< + std::vector>::difference_type>( index) + 1, activeQubits1); activeQubits1QGates[index] = activeQubits1QGates0; activeQubits1QGates.insert( activeQubits1QGates.begin() + - static_cast>::difference_type>( + static_cast< + std::vector>::difference_type>( index) + 1, activeQubits1QGates1); activeQubits2QGates[index] = activeQubits2QGates0; activeQubits2QGates.insert( activeQubits2QGates.begin() + - static_cast>::difference_type>( + static_cast< + std::vector>::difference_type>( index) + 1, activeQubits2QGates1); diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 03905eb38..05998dced 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -49,7 +49,9 @@ void HeuristicMapper::map(const Configuration& configuration) { << std::endl; config.teleportationQubits = 0; } - if (config.splitLayerAfterExpandedNodes > 0 && (config.layering == Layering::OddGates || config.layering == Layering::QubitTriangle)) { + if (config.splitLayerAfterExpandedNodes > 0 && + (config.layering == Layering::OddGates || + config.layering == Layering::QubitTriangle)) { std::cerr << "Layer splitting cannot be used with odd gates or qubit " "triangle layering, as it might not conserve gate order for " "non-disjoint gate sets! Performing mapping without layer " @@ -74,17 +76,21 @@ void HeuristicMapper::map(const Configuration& configuration) { printLocations(std::clog); printQubits(std::clog); } - + for (std::size_t i = 0; i < config.iterativeBidirectionalRouting; ++i) { if (config.verbose) { - std::clog << std::endl << "Iterative bidirectional routing (forward pass " << i << "):" << std::endl; + std::clog << std::endl + << "Iterative bidirectional routing (forward pass " << i + << "):" << std::endl; } routeCircuit(false, true); if (config.verbose) { - std::clog << std::endl << "Iterative bidirectional routing (backward pass " << i << "):" << std::endl; + std::clog << std::endl + << "Iterative bidirectional routing (backward pass " << i + << "):" << std::endl; } routeCircuit(true, true); - + if (config.verbose) { std::clog << std::endl << "Main routing:" << std::endl; } @@ -215,7 +221,8 @@ void HeuristicMapper::createInitialMapping() { void HeuristicMapper::mapUnmappedGates(std::size_t layer) { if (results.config.considerFidelity) { - for (std::size_t q = 0; q < singleQubitMultiplicities.at(layer).size(); ++q) { + for (std::size_t q = 0; q < singleQubitMultiplicities.at(layer).size(); + ++q) { if (singleQubitMultiplicities.at(layer).at(q) == 0) { continue; } @@ -315,29 +322,30 @@ void HeuristicMapper::mapToMinDistance(const std::uint16_t source, } void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { - assert (!reverse || pseudoRouting); // reverse routing is only supported for pseudo routing - + assert(!reverse || + pseudoRouting); // reverse routing is only supported for pseudo routing + // save original global data for restoring it later if pseudo routing is used - const auto originalResults = results; - const auto originalLayers = layers; + const auto originalResults = results; + const auto originalLayers = layers; const auto originalSingleQubitMultiplicities = singleQubitMultiplicities; - const auto originalTwoQubitMultiplicities = twoQubitMultiplicities; - const auto originalActiveQubits = activeQubits; - const auto originalActiveQubits1QGates = activeQubits1QGates; - const auto originalActiveQubits2QGates = activeQubits2QGates; - + const auto originalTwoQubitMultiplicities = twoQubitMultiplicities; + const auto originalActiveQubits = activeQubits; + const auto originalActiveQubits1QGates = activeQubits1QGates; + const auto originalActiveQubits2QGates = activeQubits2QGates; + auto& config = results.config; if (pseudoRouting) { config.dataLoggingPath = ""; // disable data logging for pseudo routing - config.debug = false; + config.debug = false; } - + std::size_t gateidx = 0; std::vector gatesToAdjust{}; results.output.gates = 0U; for (std::size_t i = 0; i < layers.size(); ++i) { - auto layerIndex = (reverse ? layers.size() - i - 1 : i); - const Node result = aStarMap(layerIndex, reverse); + auto layerIndex = (reverse ? layers.size() - i - 1 : i); + const Node result = aStarMap(layerIndex, reverse); qubits = result.qubits; locations = result.locations; @@ -346,7 +354,7 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { printLocations(std::clog); printQubits(std::clog); } - + if (pseudoRouting) { continue; } @@ -450,20 +458,19 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { static_cast(benchmark.expandedNodes)); } } - - + if (pseudoRouting) { // restore original global data - results = originalResults; - layers = originalLayers; + results = originalResults; + layers = originalLayers; singleQubitMultiplicities = originalSingleQubitMultiplicities; - twoQubitMultiplicities = originalTwoQubitMultiplicities; - activeQubits = originalActiveQubits; - activeQubits1QGates = originalActiveQubits1QGates; - activeQubits2QGates = originalActiveQubits2QGates; + twoQubitMultiplicities = originalTwoQubitMultiplicities; + activeQubits = originalActiveQubits; + activeQubits1QGates = originalActiveQubits1QGates; + activeQubits2QGates = originalActiveQubits2QGates; return; } - + // infer output permutation from qubit locations qcMapped.outputPermutation.clear(); for (std::size_t i = 0U; i < architecture->getNqubits(); ++i) { @@ -541,16 +548,19 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { } HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { - auto& config = results.config; + auto& config = results.config; const bool considerFidelity = config.considerFidelity; - nextNodeId = 0; - - const std::unordered_set& consideredQubits = (considerFidelity ? activeQubits[layer] : activeQubits2QGates[layer]); - const SingleQubitMultiplicity& singleQubitMultiplicity = singleQubitMultiplicities.at(layer); - const TwoQubitMultiplicity& twoQubitMultiplicity = twoQubitMultiplicities.at(layer); - Node node(nextNodeId++); - Node bestDoneNode(0); - bool done = false; + nextNodeId = 0; + + const std::unordered_set& consideredQubits = + (considerFidelity ? activeQubits[layer] : activeQubits2QGates[layer]); + const SingleQubitMultiplicity& singleQubitMultiplicity = + singleQubitMultiplicities.at(layer); + const TwoQubitMultiplicity& twoQubitMultiplicity = + twoQubitMultiplicities.at(layer); + Node node(nextNodeId++); + Node bestDoneNode(0); + bool done = false; mapUnmappedGates(layer); @@ -586,8 +596,8 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { } dataLogger->logFinalizeLayer(layer, compOp, singleQubitMultiplicity, - twoQubitMultiplicity, qubits, 0, 0, 0, - 0, {}, {}, 0); + twoQubitMultiplicity, qubits, 0, 0, 0, 0, + {}, {}, 0); dataLogger->splitLayer(); } splitLayer(layer, *architecture); @@ -654,9 +664,10 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { } dataLogger->logFinalizeLayer( - layer, compOp, singleQubitMultiplicities.at(layer), twoQubitMultiplicities.at(layer), - qubits, result.id, result.costFixed, result.costHeur, - result.lookaheadPenalty, result.qubits, result.swaps, result.depth); + layer, compOp, singleQubitMultiplicities.at(layer), + twoQubitMultiplicities.at(layer), qubits, result.id, result.costFixed, + result.costHeur, result.lookaheadPenalty, result.qubits, result.swaps, + result.depth); } // clear nodes @@ -753,15 +764,16 @@ void HeuristicMapper::expandNodeAddOneSwap( if (architecture->isEdgeConnected(swap) || architecture->isEdgeConnected(Edge{swap.second, swap.first})) { newNode.applySWAP(swap, *architecture, singleQubitMultiplicities.at(layer), - twoQubitMultiplicities.at(layer), config.considerFidelity); + twoQubitMultiplicities.at(layer), + config.considerFidelity); } else { newNode.applyTeleportation(swap, *architecture); } - newNode.updateHeuristicCost(*architecture, singleQubitMultiplicities.at(layer), - twoQubitMultiplicities.at(layer), consideredQubits, - results.config.admissibleHeuristic, - results.config.considerFidelity); + newNode.updateHeuristicCost( + *architecture, singleQubitMultiplicities.at(layer), + twoQubitMultiplicities.at(layer), consideredQubits, + results.config.admissibleHeuristic, results.config.considerFidelity); // calculate heuristics for the cost of the following layers if (config.lookahead) { diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index bca8ac336..94c3b0277 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -185,7 +185,8 @@ PYBIND11_MODULE(pyqmap, m) { .def_readwrite("split_layer_after_expanded_nodes", &Configuration::splitLayerAfterExpandedNodes) .def_readwrite("initial_layout", &Configuration::initialLayout) - .def_readwrite("iterative_bidirectional_routing", &Configuration::iterativeBidirectionalRouting) + .def_readwrite("iterative_bidirectional_routing", + &Configuration::iterativeBidirectionalRouting) .def_readwrite("lookahead", &Configuration::lookahead) .def_readwrite("admissible_heuristic", &Configuration::admissibleHeuristic) From a6d3ee3359b9234857b063e2b847ea72258d5568 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Tue, 14 Nov 2023 18:41:22 +0100 Subject: [PATCH 035/108] fix linter warnings --- include/heuristic/HeuristicMapper.hpp | 2 +- src/Mapper.cpp | 30 +++++++++++++++------------ src/heuristic/HeuristicMapper.cpp | 23 +++++++++++--------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index 95dc12645..b7f93e88d 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -223,7 +223,7 @@ class HeuristicMapper : public Mapper { * @param reverse if true, the circuit is routed from the end to the beginning (this will not produce a valid mapping, use only with pseudo routing!) * @param pseudoRouting if true, routing will only be simulated without altering the circuit or modifying any other global data except for `qubits` and `locations`, which will hold the final qubit layout afterwards */ - virtual void routeCircuit(bool reverse = false, bool pseudoRouting = false); + void routeCircuit(bool reverse = false, bool pseudoRouting = false); /** * @brief search for an optimal mapping/set of swaps using A*-search and the diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 58fec0193..118eacddc 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -234,15 +234,13 @@ bool Mapper::isLayerSplittable(std::size_t index) { if (activeQubits1QGates.at(index).size() > 2) { return true; } - if (twoQubitMultiplicities.at(index).size() == 0) { + if (twoQubitMultiplicities.at(index).empty()) { return false; } // check if there is a 1Q gate on a qubit that is not part of the 2Q gate - for (auto q : activeQubits1QGates.at(index)) { - if(activeQubits2QGates.at(index).find(q) == activeQubits2QGates.at(index).end()) { - return true; - } - } + return std::any_of(activeQubits1QGates.at(index).begin(), activeQubits1QGates.at(index).end(), [this, index](auto q) { + return activeQubits2QGates.at(index).find(q) == activeQubits2QGates.at(index).end(); + }); return false; } @@ -262,6 +260,7 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { std::unordered_set activeQubits1QGates1{}; std::unordered_set activeQubits2QGates1{}; + // 2Q-gates bool even = false; for (auto edge : twoQubitMultiplicity) { if (even) { @@ -279,24 +278,28 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { } even = !even; } - + + // 1Q-gates even = true; for (std::size_t q = 0; q < singleQubitMultiplicity.size(); ++q) { if (singleQubitMultiplicity[q] == 0) { continue; } - if (activeQubits2QGates0.find(q) != activeQubits2QGates0.end()) { + // if a qubit is also acted on by a 2Q-gate, put it on the same layer as + // the 2Q-gate + if (activeQubits2QGates0.find(static_cast(q)) != activeQubits2QGates0.end()) { singleQubitMultiplicity0[q] = singleQubitMultiplicity[q]; - activeQubits0.emplace(q); - activeQubits1QGates0.emplace(q); + activeQubits0.emplace(static_cast(q)); + activeQubits1QGates0.emplace(static_cast(q)); continue; } - if (activeQubits2QGates1.find(q) != activeQubits2QGates1.end()) { + if (activeQubits2QGates1.find(static_cast(q)) != activeQubits2QGates1.end()) { singleQubitMultiplicity1[q] = singleQubitMultiplicity[q]; - activeQubits1.emplace(q); - activeQubits1QGates1.emplace(q); + activeQubits1.emplace(static_cast(q)); + activeQubits1QGates1.emplace(static_cast(q)); continue; } + if (even) { singleQubitMultiplicity0[q] = singleQubitMultiplicity[q]; activeQubits0.emplace(q); @@ -325,6 +328,7 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { } } + // insert new layers layers[index] = layer0; layers.insert( layers.begin() + diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 03905eb38..8dd185672 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -413,18 +413,18 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { locations.at(static_cast(gate.control)), locations.at(gate.target)}; if (!architecture->isEdgeConnected(cnot)) { - const Edge reverse = {cnot.second, cnot.first}; - if (!architecture->isEdgeConnected(reverse)) { + const Edge reversed = {cnot.second, cnot.first}; + if (!architecture->isEdgeConnected(reversed)) { throw QMAPException( - "Invalid CNOT: " + std::to_string(reverse.first) + "-" + - std::to_string(reverse.second)); + "Invalid CNOT: " + std::to_string(reversed.first) + "-" + + std::to_string(reversed.second)); } - qcMapped.h(reverse.first); - qcMapped.h(reverse.second); - qcMapped.cx(qc::Control{static_cast(reverse.first)}, - reverse.second); - qcMapped.h(reverse.second); - qcMapped.h(reverse.first); + qcMapped.h(reversed.first); + qcMapped.h(reversed.second); + qcMapped.cx(qc::Control{static_cast(reversed.first)}, + reversed.second); + qcMapped.h(reversed.second); + qcMapped.h(reversed.first); results.output.directionReverse++; gateidx += 5; @@ -595,6 +595,9 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { std::clog << "Split layer" << std::endl; } // recursively restart search with newly split layer + // (step to the end of the circuit, if reverse mapping is active, since + // the split layer is inserted in this direction, otherwise 1 layer would + // be skipped) return aStarMap(reverse ? layer + 1 : layer, reverse); } Node current = nodes.top(); From a60bb50070ddc203bb93dbd89a5b17c5945cf92c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 17:45:53 +0000 Subject: [PATCH 036/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mapper.cpp | 27 ++++++++++++++++----------- src/heuristic/HeuristicMapper.cpp | 4 ++-- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 371c43b31..65baa2afc 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -243,9 +243,12 @@ bool Mapper::isLayerSplittable(std::size_t index) { return false; } // check if there is a 1Q gate on a qubit that is not part of the 2Q gate - return std::any_of(activeQubits1QGates.at(index).begin(), activeQubits1QGates.at(index).end(), [this, index](auto q) { - return activeQubits2QGates.at(index).find(q) == activeQubits2QGates.at(index).end(); - }); + return std::any_of(activeQubits1QGates.at(index).begin(), + activeQubits1QGates.at(index).end(), + [this, index](auto q) { + return activeQubits2QGates.at(index).find(q) == + activeQubits2QGates.at(index).end(); + }); return false; } @@ -266,7 +269,7 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { std::unordered_set activeQubits1{}; std::unordered_set activeQubits1QGates1{}; std::unordered_set activeQubits2QGates1{}; - + // 2Q-gates bool even = false; for (auto edge : twoQubitMultiplicity) { @@ -285,28 +288,30 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { } even = !even; } - + // 1Q-gates even = true; for (std::size_t q = 0; q < singleQubitMultiplicity.size(); ++q) { if (singleQubitMultiplicity[q] == 0) { continue; } - // if a qubit is also acted on by a 2Q-gate, put it on the same layer as + // if a qubit is also acted on by a 2Q-gate, put it on the same layer as // the 2Q-gate - if (activeQubits2QGates0.find(static_cast(q)) != activeQubits2QGates0.end()) { + if (activeQubits2QGates0.find(static_cast(q)) != + activeQubits2QGates0.end()) { singleQubitMultiplicity0[q] = singleQubitMultiplicity[q]; activeQubits0.emplace(static_cast(q)); activeQubits1QGates0.emplace(static_cast(q)); continue; - } - if (activeQubits2QGates1.find(static_cast(q)) != activeQubits2QGates1.end()) { + } + if (activeQubits2QGates1.find(static_cast(q)) != + activeQubits2QGates1.end()) { singleQubitMultiplicity1[q] = singleQubitMultiplicity[q]; activeQubits1.emplace(static_cast(q)); activeQubits1QGates1.emplace(static_cast(q)); continue; } - + if (even) { singleQubitMultiplicity0[q] = singleQubitMultiplicity[q]; activeQubits0.emplace(q); @@ -335,7 +340,7 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { } } } - + // insert new layers layers[index] = layer0; layers.insert( diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index e8c23bb50..018714314 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -605,8 +605,8 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { std::clog << "Split layer" << std::endl; } // recursively restart search with newly split layer - // (step to the end of the circuit, if reverse mapping is active, since - // the split layer is inserted in this direction, otherwise 1 layer would + // (step to the end of the circuit, if reverse mapping is active, since + // the split layer is inserted in this direction, otherwise 1 layer would // be skipped) return aStarMap(reverse ? layer + 1 : layer, reverse); } From cb62aff4da2242dc296e26316754a7e5d5fce447 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 1 Dec 2023 10:17:24 +0100 Subject: [PATCH 037/108] fix sphinx error --- pyproject.toml | 19 +++++++++++-------- src/mqt/qmap/__init__.py | 2 ++ .../visualization/visualize_search_graph.py | 2 +- test/python/constraints.txt | 5 +++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b85256409..2f61bc454 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,17 +39,12 @@ dependencies = [ "qiskit-terra>=0.20.2", "rustworkx[all]>=0.13.0", "importlib_resources>=5.0; python_version < '3.10'", - "typing_extensions>=4.0", - "distinctipy>=1.2.2", - "plotly>=5.15.0", - "networkx==2.5", - "walkerlayout==1.0.2", - "ipywidgets>=7.6.5", + "typing_extensions>=4.0" ] dynamic = ["version"] [project.optional-dependencies] -test = ["pytest>=7", "mqt.qcec>=2"] +test = ["pytest>=7", "mqt.qcec>=2", "mqt.qmap[visualization]"] coverage = ["mqt.qmap[test]", "pytest-cov"] docs = [ "furo>=2023.08.17", @@ -66,7 +61,14 @@ docs = [ "sphinx-autodoc-typehints", "qiskit-terra[visualization]", ] -dev = ["mqt.qmap[coverage, docs]"] +visualization = [ + "distinctipy>=1.2.2", + "plotly>=5.15.0", + "networkx>=2.5", + "walkerlayout>=1.0.2", + "ipywidgets>=7.6.5" +] +dev = ["mqt.qmap[coverage, docs, visualization]"] [project.urls] Homepage = "https://github.com/cda-tum/mqt-qmap" @@ -150,6 +152,7 @@ filterwarnings = [ [tool.coverage] run.source = ["mqt.qmap"] +run.omit = ["visualization/*"] report.exclude_also = [ '\.\.\.', 'if TYPE_CHECKING:', diff --git a/src/mqt/qmap/__init__.py b/src/mqt/qmap/__init__.py index 6f63d271a..fe8edaee7 100644 --- a/src/mqt/qmap/__init__.py +++ b/src/mqt/qmap/__init__.py @@ -41,8 +41,10 @@ Verbosity, ) from .subarchitectures import SubarchitectureOrder +from . import visualization __all__ = [ + "visualization", "compile", "Method", "InitialLayout", diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index 37999fa15..f1e4e369a 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -173,7 +173,7 @@ def _layout_search_graph( search_graph, root, origin=(0, 0), scalex=60, scaley=-1 ) else: - pos = graphviz_layout(search_graph, prog=method, root=search_graph.nodes[0]) # t + pos = graphviz_layout(search_graph, prog=method, root=search_graph.nodes[0]) if not tapered_layer_heights: return pos diff --git a/test/python/constraints.txt b/test/python/constraints.txt index 04096b91d..222dd15ca 100644 --- a/test/python/constraints.txt +++ b/test/python/constraints.txt @@ -7,3 +7,8 @@ typing_extensions==4.0.0 qiskit-terra==0.20.2 rustworkx==0.13.0 mqt.qcec==2.0.0 +distinctipy==1.2.2 +plotly==5.15.0 +networkx==2.5 +walkerlayout==1.0.2 +ipywidgets==7.6.5 \ No newline at end of file From 1fef5c8be7e578b570c7ec49d2c1fc71c4862a7d Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 1 Dec 2023 13:13:51 +0100 Subject: [PATCH 038/108] fix spinx references --- docs/source/Mapping.ipynb | 4 ++- docs/source/library/Visualization.rst | 18 +++++++----- .../qmap/visualization/search_visualizer.py | 24 ++++++++-------- .../visualization/visualize_search_graph.py | 28 ++++++++----------- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/docs/source/Mapping.ipynb b/docs/source/Mapping.ipynb index a1d9c0852..0cf616612 100644 --- a/docs/source/Mapping.ipynb +++ b/docs/source/Mapping.ipynb @@ -4,6 +4,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "\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", @@ -290,7 +292,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There exist a wide range of customizations for these visualizations. Please refer to the API reference for details." + "There exist a wide range of customizations for these visualizations. Please refer to the [reference documentation](library/Visualization.rst) for more information." ] } ], diff --git a/docs/source/library/Visualization.rst b/docs/source/library/Visualization.rst index 784cb7fad..1dd7d5b45 100644 --- a/docs/source/library/Visualization.rst +++ b/docs/source/library/Visualization.rst @@ -1,16 +1,20 @@ Visualization -=========================== +============= -All visualization functionality is bundled in the sub-module :code:`mqt.qmap.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 :code:`SearchVisualizer` object, which can be passed to the :code:`compile` function when mapping a circuit to enable data logging during mapping, after which this data can be visualized by calling the method :code:`visualize_search_graph` of the :code:`SearchVisualizer` object. -Closing the :code:`SearchVisualizer` object will cleanup the data logged during mapping, which will however also be done automatically when the python process terminates (if not custom data logging path is used). +The recommended way to visualize search graphs of a mapping process is via a :class:`SearchVisualizer` object, which can be passed to the :func:`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:: - :code:`visualize_search_graph` returns an IPython display object and therefore requires to be executed in a jupyter notebook or jupyter lab environment. + :meth:`visualize_search_graph` returns an IPython display object and therefore requires to be executed in a jupyter notebook or jupyter lab environment. - .. currentmodule:: mqt.qmap.visualization .. autoclass:: SearchVisualizer + :special-members: __init__ + :members: diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py index 8b2d727b3..f53ff4a54 100644 --- a/src/mqt/qmap/visualization/search_visualizer.py +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -137,35 +137,35 @@ def visualize_search_graph( """Creates a widget to visualize the search graph. Args: - layer (int | Literal["interactive"]): Index of the circuit layer, of which the mapping should be visualized. Defaults to "interactive", in which case a slider menu will be created. + layer (int | Literal['interactive']): Index of the circuit layer, of which the mapping should be visualized. Defaults to "interactive", in which case a slider menu will be created. architecture_node_positions (MutableMapping[int, tuple[float, float]] | None): Mapping from physical qubits to (x, y) coordinates. Defaults to None, in which case architecture_layout will be used to generate a layout. - architecture_layout (Literal[ "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ]): The method to use when layouting the qubit connectivity graph. Defaults to "sfdp". - search_node_layout (Literal[ "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ]): The method to use when layouting the search graph. Defaults to "walker". + architecture_layout (Literal[ 'dot', 'neato', 'fdp', 'sfdp', 'circo', 'twopi', 'osage', 'patchwork' ]): The method to use when layouting the qubit connectivity graph. Defaults to "sfdp". + search_node_layout (Literal[ 'walker', 'dot', 'neato', 'fdp', 'sfdp', 'circo', 'twopi', 'osage', 'patchwork' ]): The method to use when layouting the search graph. Defaults to "walker". search_graph_border (float): Size of the border around the search graph. Defaults to 0.05. architecture_border (float): Size of the border around the qubit connectivity graph. Defaults to 0.05. swap_arrow_spacing (float): Lateral spacing between arrows indicating swaps on the qubit connectivity graph. Defaults to 0.05. swap_arrow_offset (float): Offset of heads and shaft of swap arrows from qubits they are pointing to/from. Defaults to 0.05. use3d (bool): If a 3D graph should be used for the search graph using the z-axis to plot data features. Defaults to True. - projection (Literal["orthographic", "perspective"]): Projection type to use in 3D graphs. Defaults to "perspective". + projection (Literal['orthographic', 'perspective']): Projection type to use in 3D graphs. Defaults to "perspective". width (int): Pixel width of the widget. Defaults to 1400. height (int): Pixel height of the widget. Defaults to 700. draw_search_edges (bool): If edges between search nodes should be drawn. Defaults to True. search_edges_width (float): Width of edges between search nodes. Defaults to 0.5. - search_edges_color (str): Color of edges between search nodes (in CSS format, i.e. "#rrggbb", "#rgb", "colorname", etc.). Defaults to "#888". - search_edges_dash (str): Dashing of search edges (in CSS format, i.e. "solid", "dot", "dash", "longdash", etc.). Defaults to "solid". + search_edges_color (str): Color of edges between search nodes (in CSS format, i.e. '#rrggbb', '#rgb', 'colorname', etc.). Defaults to "#888". + search_edges_dash (str): Dashing of search edges (in CSS format, i.e. 'solid', 'dot', 'dash', 'longdash', etc.). Defaults to "solid". tapered_search_layer_heights (bool): If search graph tree should progressively reduce the height of each layer. Defaults to True. - show_layout (Literal["hover", "click"] | None): If the current qubit layout should be shown on the qubit connectivity graph, when clicking or hovering on a search node or not at all. Defaults to "hover". + show_layout (Literal['hover', 'click'] | None): If the current qubit layout should be shown on the qubit connectivity graph, when clicking or hovering on a search node or not at all. Defaults to "hover". show_swaps (bool): Showing swaps on the connectivity graph. Defaults to True. show_shared_swaps (bool): Indicate a shared swap by 1 arrow with 2 heads, otherwise 2 arrows in opposite direction are drawn for the 1 shared swap. Defaults to True. color_valid_mapping (str | None): Color to use for search nodes containing a valid qubit layout (in CSS format). Defaults to "green". color_final_node (str | None): Color to use for the final solution search node (in CSS format). Defaults to "red". - search_node_color (str | Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by `search_node_color_scale`, or a preset data feature ("total_cost" | "fixed_cost" | "heuristic_cost" | "lookahead_penalty"). In case a 3D search graph is used with multiple point per search node, each point"s color can be controlled individually via a list. Defaults to "total_cost". + search_node_color (str | Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by ``search_node_color_scale`` , or a preset data feature ('total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty'). In case a 3D search graph is used with multiple point per search node, each point's color can be controlled individually via a list. Defaults to "total_cost". prioritize_search_node_color (bool | list[ bool ]): If search_node_color should be prioritized over color_valid_mapping and color_final_node. Defaults to False. search_node_color_scale (str | list[str]): Color scale to be used for converting float data features to search node colors. (See https://plotly.com/python/builtin-colorscales/ for valid values). Defaults to "YlGnBu". search_node_invert_color_scale (bool | list[bool]): If the color scale should be inverted. Defaults to True. search_node_colorbar_title (str | list[str | None] | None): Title(s) to be shown next to the colorbar(s). Defaults to None. search_node_colorbar_spacing (float): Spacing between multiple colorbars. Defaults to 0.06. - search_node_height (str | Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]): Function mapping a mqt.qmap.visualization.SearchNode to a float value to be used as z-value in 3D search graphs or a preset data feature ("total_cost" | "fixed_cost" | "heuristic_cost" | "lookahead_penalty"). Or a list any of such functions/data features, to draw multiple points per search node. Defaults to "total_cost". + search_node_height (str | Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]): Function mapping a mqt.qmap.visualization.SearchNode to a float value to be used as z-value in 3D search graphs or a preset data feature ('total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty'). Or a list any of such functions/data features, to draw multiple points per search node. Defaults to "total_cost". draw_stems (bool): If a vertical stem should be drawn in 3D search graphs to each search node. Defaults to False. stems_width (float): Width of stems in 3D search graphs. Defaults to 0.7. stems_color (str): Color of stems in 3D search graphs (in CSS format). Defaults to "#444". @@ -173,8 +173,9 @@ def visualize_search_graph( show_search_progression (bool): If the search progression should be animated. Defaults to True. search_progression_step (int): Step size (in number of nodes added) of search progression animation. Defaults to 10. search_progression_speed (float): Speed of the search progression animation. Defaults to 2. - plotly_settings (MutableMapping[str, MutableMapping[str, any]] | None): Direct plotly configuration dictionaries to be passed through. Defaults to None. - ``` + plotly_settings (MutableMapping[str, MutableMapping[str, any]] | None): Plotly configuration dictionaries to be passed through. Defaults to None. + .. code-block:: text + { "layout": settings for plotly.graph_objects.Layout (of subplots figure) "arrows": settings for plotly.graph_objects.layout.Annotation @@ -190,7 +191,6 @@ def visualize_search_graph( "architecture_xaxis": settings for plotly.graph_objects.layout.XAxis "architecture_yaxis": settings for plotly.graph_objects.layout.YAxis } - ``` Raises: TypeError: If any of the arguments are invalid. diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index f1e4e369a..d5658fd6e 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -1576,35 +1576,35 @@ def visualize_search_graph( Args: data_logging_path (str): Path to the data logging directory of the search process to be visualized. - layer (int | Literal["interactive"]): Index of the circuit layer, of which the mapping should be visualized. Defaults to "interactive", in which case a slider menu will be created. + layer (int | Literal['interactive']): Index of the circuit layer, of which the mapping should be visualized. Defaults to "interactive", in which case a slider menu will be created. architecture_node_positions (MutableMapping[int, tuple[float, float]] | None): MutableMapping from physical qubits to (x, y) coordinates. Defaults to None, in which case architecture_layout will be used to generate a layout. - architecture_layout (Literal[ "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ]): The method to use when layouting the qubit connectivity graph. Defaults to "sfdp". - search_node_layout (Literal[ "walker", "dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork" ]): The method to use when layouting the search graph. Defaults to "walker". + architecture_layout (Literal[ 'dot', 'neato', 'fdp', 'sfdp', 'circo', 'twopi', 'osage', 'patchwork' ]): The method to use when layouting the qubit connectivity graph. Defaults to "sfdp". + search_node_layout (Literal[ 'walker', 'dot', 'neato', 'fdp', 'sfdp', 'circo', 'twopi', 'osage', 'patchwork' ]): The method to use when layouting the search graph. Defaults to "walker". search_graph_border (float): Size of the border around the search graph. Defaults to 0.05. architecture_border (float): Size of the border around the qubit connectivity graph. Defaults to 0.05. swap_arrow_spacing (float): Lateral spacing between arrows indicating swaps on the qubit connectivity graph. Defaults to 0.05. swap_arrow_offset (float): Offset of heads and shaft of swap arrows from qubits they are pointing to/from. Defaults to 0.05. use3d (bool): If a 3D graph should be used for the search graph using the z-axis to plot data features. Defaults to True. - projection (Literal["orthographic", "perspective"]): Projection type to use in 3D graphs. Defaults to "perspective". + projection (Literal['orthographic', 'perspective']): Projection type to use in 3D graphs. Defaults to "perspective". width (int): Pixel width of the widget. Defaults to 1400. height (int): Pixel height of the widget. Defaults to 700. draw_search_edges (bool): If edges between search nodes should be drawn. Defaults to True. search_edges_width (float): Width of edges between search nodes. Defaults to 0.5. - search_edges_color (str): Color of edges between search nodes (in CSS format, i.e. "#rrggbb", "#rgb", "colorname", etc.). Defaults to "#888". - search_edges_dash (str): Dashing of search edges (in CSS format, i.e. "solid", "dot", "dash", "longdash", etc.). Defaults to "solid". + search_edges_color (str): Color of edges between search nodes (in CSS format, i.e. '#rrggbb', '#rgb', 'colorname', etc.). Defaults to "#888". + search_edges_dash (str): Dashing of search edges (in CSS format, i.e. 'solid', 'dot', 'dash', 'longdash', etc.). Defaults to "solid". tapered_search_layer_heights (bool): If search graph tree should progressively reduce the height of each layer. Defaults to True. - show_layout (Literal["hover", "click"] | None): If the current qubit layout should be shown on the qubit connectivity graph, when clicking or hovering on a search node or not at all. Defaults to "hover". + show_layout (Literal['hover', 'click'] | None): If the current qubit layout should be shown on the qubit connectivity graph, when clicking or hovering on a search node or not at all. Defaults to "hover". show_swaps (bool): Showing swaps on the connectivity graph. Defaults to True. show_shared_swaps (bool): Indicate a shared swap by 1 arrow with 2 heads, otherwise 2 arrows in opposite direction are drawn for the 1 shared swap. Defaults to True. color_valid_mapping (str | None): Color to use for search nodes containing a valid qubit layout (in CSS format). Defaults to "green". color_final_node (str | None): Color to use for the final solution search node (in CSS format). Defaults to "red". - search_node_color (str | Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by `search_node_color_scale`, or a preset data feature ("total_cost" | "fixed_cost" | "heuristic_cost" | "lookahead_penalty"). In case a 3D search graph is used with multiple point per search node, each point"s color can be controlled individually via a list. Defaults to "total_cost". + search_node_color (str | Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by `search_node_color_scale`, or a preset data feature ('total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty'). In case a 3D search graph is used with multiple point per search node, each point's color can be controlled individually via a list. Defaults to "total_cost". prioritize_search_node_color (bool | Sequence[ bool ]): If search_node_color should be prioritized over color_valid_mapping and color_final_node. Defaults to False. search_node_color_scale (str | Sequence[str]): Color scale to be used for converting float data features to search node colors. (See https://plotly.com/python/builtin-colorscales/ for valid values). Defaults to "YlGnBu". search_node_invert_color_scale (bool | Sequence[bool]): If the color scale should be inverted. Defaults to True. search_node_colorbar_title (str | Sequence[str | None] | None): Title(s) to be shown next to the colorbar(s). Defaults to None. search_node_colorbar_spacing (float): Spacing between multiple colorbars. Defaults to 0.06. - search_node_height (str | Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]): Function mapping a mqt.qmap.visualization.SearchNode to a float value to be used as z-value in 3D search graphs or a preset data feature ("total_cost" | "fixed_cost" | "heuristic_cost" | "lookahead_penalty"). Or a list any of such functions/data features, to draw multiple points per search node. Defaults to "total_cost". + search_node_height (str | Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]): Function mapping a mqt.qmap.visualization.SearchNode to a float value to be used as z-value in 3D search graphs or a preset data feature ('total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty'). Or a list any of such functions/data features, to draw multiple points per search node. Defaults to "total_cost". draw_stems (bool): If a vertical stem should be drawn in 3D search graphs to each search node. Defaults to False. stems_width (float): Width of stems in 3D search graphs. Defaults to 0.7. stems_color (str): Color of stems in 3D search graphs (in CSS format). Defaults to "#444". @@ -1612,8 +1612,9 @@ def visualize_search_graph( show_search_progression (bool): If the search progression should be animated. Defaults to True. search_progression_step (int): Step size (in number of nodes added) of search progression animation. Defaults to 10. search_progression_speed (float): Speed of the search progression animation. Defaults to 2. - plotly_settings (MutableMapping[str, MutableMapping[str, any]] | None): Direct plotly configuration dictionaries to be passed through. Defaults to None. - ``` + plotly_settings (MutableMapping[str, MutableMapping[str, any]] | None): Plotly configuration dictionaries to be passed through. Defaults to None. + .. code-block:: text + { "layout": settings for plotly.graph_objects.Layout (of subplots figure) "arrows": settings for plotly.graph_objects.layout.Annotation @@ -1629,7 +1630,6 @@ def visualize_search_graph( "architecture_xaxis": settings for plotly.graph_objects.layout.XAxis "architecture_yaxis": settings for plotly.graph_objects.layout.YAxis } - ``` Raises: TypeError: If any of the arguments are invalid. @@ -1773,10 +1773,6 @@ def visualize_search_graph( arch_graph = _parse_arch_graph(f"{data_logging_path}architecture.json") if architecture_node_positions is None: - for node in arch_graph.nodes: - print(node, arch_graph.nodes[node]) # noqa: T201 - for edge in arch_graph.edges: - print(edge, arch_graph.edges[edge]) # noqa: T201 architecture_node_positions = graphviz_layout(arch_graph, prog=architecture_layout) elif len(architecture_node_positions) != len(arch_graph.nodes): msg = f"architecture_node_positions must contain positions for all {len(arch_graph.nodes)} architecture nodes." From a846a52e079e39ae86799d50e553abdd40f596e5 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Sat, 2 Dec 2023 15:52:23 +0100 Subject: [PATCH 039/108] added comment on iterative bidirectional routing setting --- include/configuration/Configuration.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 21e5fd9af..fcbd75e0b 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -42,6 +42,11 @@ struct Configuration { // initial layout to use for heuristic approach InitialLayout initialLayout = InitialLayout::None; + // controls the number of iterative bidirectional routing passes, i.e. after + // an initial layout is found, the circuit is routed multiple times back and + // forth (using settings optimized for time-efficiency) without inserting any + // swaps, to improve the initial layout, only after which the actual routing + // is performed std::size_t iterativeBidirectionalRouting = 0; // lookahead scheme settings From cbb0814a718fddeebc8959f4d8a20257fff4f7dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 2 Dec 2023 14:55:51 +0000 Subject: [PATCH 040/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/configuration/Configuration.hpp | 4 ++-- src/mqt/qmap/__init__.py | 2 +- src/mqt/qmap/visualization/search_visualizer.py | 2 +- src/mqt/qmap/visualization/visualize_search_graph.py | 2 +- test/python/constraints.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index fcbd75e0b..738652b7d 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -43,8 +43,8 @@ struct Configuration { InitialLayout initialLayout = InitialLayout::None; // controls the number of iterative bidirectional routing passes, i.e. after - // an initial layout is found, the circuit is routed multiple times back and - // forth (using settings optimized for time-efficiency) without inserting any + // an initial layout is found, the circuit is routed multiple times back and + // forth (using settings optimized for time-efficiency) without inserting any // swaps, to improve the initial layout, only after which the actual routing // is performed std::size_t iterativeBidirectionalRouting = 0; diff --git a/src/mqt/qmap/__init__.py b/src/mqt/qmap/__init__.py index fe8edaee7..7d6e6d6a7 100644 --- a/src/mqt/qmap/__init__.py +++ b/src/mqt/qmap/__init__.py @@ -18,6 +18,7 @@ if bin_path.exists(): os.add_dll_directory(str(bin_path)) +from . import visualization from ._version import version as __version__ from .clifford_synthesis import optimize_clifford, synthesize_clifford from .compile import compile @@ -41,7 +42,6 @@ Verbosity, ) from .subarchitectures import SubarchitectureOrder -from . import visualization __all__ = [ "visualization", diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py index f53ff4a54..9b31b546f 100644 --- a/src/mqt/qmap/visualization/search_visualizer.py +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -175,7 +175,7 @@ def visualize_search_graph( search_progression_speed (float): Speed of the search progression animation. Defaults to 2. plotly_settings (MutableMapping[str, MutableMapping[str, any]] | None): Plotly configuration dictionaries to be passed through. Defaults to None. .. code-block:: text - + { "layout": settings for plotly.graph_objects.Layout (of subplots figure) "arrows": settings for plotly.graph_objects.layout.Annotation diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index d5658fd6e..abdb6a2ee 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -1614,7 +1614,7 @@ def visualize_search_graph( search_progression_speed (float): Speed of the search progression animation. Defaults to 2. plotly_settings (MutableMapping[str, MutableMapping[str, any]] | None): Plotly configuration dictionaries to be passed through. Defaults to None. .. code-block:: text - + { "layout": settings for plotly.graph_objects.Layout (of subplots figure) "arrows": settings for plotly.graph_objects.layout.Annotation diff --git a/test/python/constraints.txt b/test/python/constraints.txt index 222dd15ca..4a020c3f4 100644 --- a/test/python/constraints.txt +++ b/test/python/constraints.txt @@ -11,4 +11,4 @@ distinctipy==1.2.2 plotly==5.15.0 networkx==2.5 walkerlayout==1.0.2 -ipywidgets==7.6.5 \ No newline at end of file +ipywidgets==7.6.5 From 32a559bcf59c1e62b0c0bcc4f986295bfd259883 Mon Sep 17 00:00:00 2001 From: Elias Leon Foramitti Date: Sat, 2 Dec 2023 15:59:26 +0100 Subject: [PATCH 041/108] Update src/mqt/qmap/visualization/__init__.py Co-authored-by: Lukas Burgholzer --- src/mqt/qmap/visualization/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mqt/qmap/visualization/__init__.py b/src/mqt/qmap/visualization/__init__.py index 8d72bca33..d67cd86e6 100644 --- a/src/mqt/qmap/visualization/__init__.py +++ b/src/mqt/qmap/visualization/__init__.py @@ -6,7 +6,7 @@ from __future__ import annotations -from mqt.qmap.visualization.search_visualizer import SearchVisualizer -from mqt.qmap.visualization.visualize_search_graph import SearchNode, visualize_search_graph +from .search_visualizer import SearchVisualizer +from .visualize_search_graph import SearchNode, visualize_search_graph __all__ = ["visualize_search_graph", "SearchVisualizer", "SearchNode"] From 448bfe3a78628c1fb8d72e57098db283896c6295 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Sat, 2 Dec 2023 16:01:23 +0100 Subject: [PATCH 042/108] changing Python imports to relative imports --- src/mqt/qmap/compile.py | 2 +- src/mqt/qmap/visualization/search_visualizer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 862d4b291..0c108fa04 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -12,7 +12,7 @@ from qiskit.providers.models import BackendProperties from qiskit.transpiler.target import Target - from mqt.qmap.visualization.search_visualizer import SearchVisualizer + from .visualization.search_visualizer import SearchVisualizer from .load_architecture import load_architecture from .load_calibration import load_calibration diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py index 9b31b546f..e665cf56e 100644 --- a/src/mqt/qmap/visualization/search_visualizer.py +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -10,7 +10,7 @@ from typing_extensions import Self -from mqt.qmap.visualization.visualize_search_graph import SearchNode, visualize_search_graph +from .visualize_search_graph import SearchNode, visualize_search_graph if TYPE_CHECKING: from ipywidgets import Widget From 91eec8f2babc5468367db7052585729324955cc9 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Sat, 2 Dec 2023 16:05:51 +0100 Subject: [PATCH 043/108] adding total_fidelity to Python CircuitInfo stub --- src/mqt/qmap/pyqmap.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index 8dc70a782..dcbc5a078 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -75,6 +75,7 @@ class CircuitInfo: direction_reverse: int gates: int layers: int + total_fidelity: float name: str qubits: int single_qubit_gates: int From ce397c1406a1015b0d5da5ea4f50b7af17f54306 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Sat, 2 Dec 2023 17:03:42 +0100 Subject: [PATCH 044/108] add visualization to docs dependencies --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2f61bc454..3624aa2f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,7 @@ docs = [ "sphinxext-opengraph", "sphinx-autodoc-typehints", "qiskit-terra[visualization]", + "mqt.qmap[visualization]" ] visualization = [ "distinctipy>=1.2.2", From 75e09d231e6cdb0cfecae8577dcf02911c4a492e Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Sat, 2 Dec 2023 19:40:13 +0100 Subject: [PATCH 045/108] make docs independent of graphviz --- docs/source/Mapping.ipynb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/source/Mapping.ipynb b/docs/source/Mapping.ipynb index 0cf616612..3b9e18e09 100644 --- a/docs/source/Mapping.ipynb +++ b/docs/source/Mapping.ipynb @@ -285,7 +285,15 @@ "\n", "visualizer = qmap.visualization.SearchVisualizer()\n", "qc_mapped, res = qmap.compile(qc, arch, method=\"heuristic\", visualizer=visualizer)\n", - "visualizer.visualize_search_graph()" + "arch_node_pos = {\n", + " 0: (0, 0),\n", + " 1: (0, 1),\n", + " 2: (0, 2),\n", + " 3: (1, 2),\n", + " 4: (1, 1),\n", + " 5: (1, 0)\n", + "}\n", + "visualizer.visualize_search_graph(architecture_node_positions=arch_node_pos)" ] }, { @@ -311,7 +319,8 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3" + "pygments_lexer": "ipython3", + "version": "3.8.5" } }, "nbformat": 4, From 8e023b17729e957cf32a8148a9e7e9655b145baf Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Sun, 3 Dec 2023 04:06:15 +0100 Subject: [PATCH 046/108] adding note on graphviz to docs --- docs/source/library/Visualization.rst | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/source/library/Visualization.rst b/docs/source/library/Visualization.rst index 1dd7d5b45..e095890f1 100644 --- a/docs/source/library/Visualization.rst +++ b/docs/source/library/Visualization.rst @@ -10,11 +10,18 @@ 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:`compile` function to enable data logging during mapping, after which this data can be visualized by calling the method :meth:`SearchVisualizer.visualize_search_graph`. +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:`visualize_search_graph` returns an IPython display object and therefore requires to be executed in a jupyter notebook or jupyter lab environment. +.. 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. - .. autoclass:: SearchVisualizer - :special-members: __init__ - :members: +.. note:: + Automatic layouting of architecture or search nodes requires `Graphviz `_ 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: From 14a6398708b577a1fe39a34ccdcc0e67f87ca641 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 3 Dec 2023 03:06:50 +0000 Subject: [PATCH 047/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/Mapping.ipynb | 12 ++---------- docs/source/library/Visualization.rst | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/docs/source/Mapping.ipynb b/docs/source/Mapping.ipynb index 3b9e18e09..b8c5935e9 100644 --- a/docs/source/Mapping.ipynb +++ b/docs/source/Mapping.ipynb @@ -285,14 +285,7 @@ "\n", "visualizer = qmap.visualization.SearchVisualizer()\n", "qc_mapped, res = qmap.compile(qc, arch, method=\"heuristic\", visualizer=visualizer)\n", - "arch_node_pos = {\n", - " 0: (0, 0),\n", - " 1: (0, 1),\n", - " 2: (0, 2),\n", - " 3: (1, 2),\n", - " 4: (1, 1),\n", - " 5: (1, 0)\n", - "}\n", + "arch_node_pos = {0: (0, 0), 1: (0, 1), 2: (0, 2), 3: (1, 2), 4: (1, 1), 5: (1, 0)}\n", "visualizer.visualize_search_graph(architecture_node_positions=arch_node_pos)" ] }, @@ -319,8 +312,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" + "pygments_lexer": "ipython3" } }, "nbformat": 4, diff --git a/docs/source/library/Visualization.rst b/docs/source/library/Visualization.rst index e095890f1..5cb1859b0 100644 --- a/docs/source/library/Visualization.rst +++ b/docs/source/library/Visualization.rst @@ -17,9 +17,9 @@ The recommended way to visualize search graphs of a mapping process is via a :cl .. note:: Automatic layouting of architecture or search nodes requires `Graphviz `_ 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 From 94180cf40c7d437189d4793aa6b3e36cdfe2917a Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Sun, 3 Dec 2023 22:01:03 +0100 Subject: [PATCH 048/108] deactivate clang tidy padding check --- include/configuration/Configuration.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 738652b7d..57bb2d9e1 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -15,6 +15,7 @@ #include +// NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) struct Configuration { Configuration() = default; From 0fb914fad240f904a7f710527e9e1cfb75b29682 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Mon, 4 Dec 2023 16:34:21 +0100 Subject: [PATCH 049/108] simplifying composite operation building for data logging --- src/heuristic/HeuristicMapper.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 018714314..1f8cbc5f0 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -591,8 +591,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { if (config.dataLoggingEnabled()) { qc::CompoundOperation compOp(architecture->getNqubits()); for (const auto& gate : layers.at(layer)) { - std::unique_ptr op = gate.op->clone(); - compOp.emplace_back(op); + compOp.emplace_back(gate.op->clone()); } dataLogger->logFinalizeLayer(layer, compOp, singleQubitMultiplicity, @@ -662,8 +661,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { if (config.dataLoggingEnabled()) { qc::CompoundOperation compOp(architecture->getNqubits()); for (const auto& gate : layers.at(layer)) { - std::unique_ptr op = gate.op->clone(); - compOp.emplace_back(op); + compOp.emplace_back(gate.op->clone()); } dataLogger->logFinalizeLayer( From 71d526521dbfdbf46f483f6b96d03ac82fb70f99 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 7 Dec 2023 14:35:14 +0100 Subject: [PATCH 050/108] readd const --- include/utils.hpp | 8 ++++---- src/Mapper.cpp | 10 +++++----- src/utils.cpp | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/utils.hpp b/include/utils.hpp index 75d019299..26404a014 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -90,9 +90,9 @@ class Dijkstra { * @param removeLastEdge if set to true, the cost for the last swap on any * path is removed (i.e. distance is given for moving "next to" qubit) */ - static void buildTable(std::uint16_t n, const CouplingMap& couplingMap, + static void buildTable(const std::uint16_t n, const CouplingMap& couplingMap, Matrix& distanceTable, const Matrix& edgeWeights, - double reversalCost, bool removeLastEdge); + const double reversalCost, const bool removeLastEdge); /** * @brief builds a 3d matrix containing the distance tables giving the minimal * distances between 2 qubit when upto k edges can be skipped. @@ -117,8 +117,8 @@ class Dijkstra { protected: static void dijkstra(const CouplingMap& couplingMap, std::vector& nodes, - std::uint16_t start, const Matrix& edgeWeights, - double reversalCost); + const std::uint16_t start, const Matrix& edgeWeights, + const double reversalCost); struct NodeComparator { bool operator()(const Node* x, const Node* y) { diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 65baa2afc..b43b65fd9 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -272,7 +272,7 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { // 2Q-gates bool even = false; - for (auto edge : twoQubitMultiplicity) { + for (const auto edge : twoQubitMultiplicity) { if (even) { twoQubitMultiplicity0.insert(edge); activeQubits0.emplace(edge.first.first); @@ -327,16 +327,16 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { for (auto& gate : layers[index]) { if (gate.singleQubit()) { if (singleQubitMultiplicity0[gate.target] > 0) { - layer0.push_back(gate); + layer0.emplace_back(gate); } else { - layer1.push_back(gate); + layer1.emplace_back(gate); } } else { if (activeQubits2QGates0.find(gate.target) != activeQubits2QGates0.end()) { - layer0.push_back(gate); + layer0.emplace_back(gate); } else { - layer1.push_back(gate); + layer1.emplace_back(gate); } } } diff --git a/src/utils.cpp b/src/utils.cpp index 6d4da8f1b..d4fe2ec83 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -9,7 +9,7 @@ void Dijkstra::buildTable(const std::uint16_t n, const CouplingMap& couplingMap, Matrix& distanceTable, const Matrix& edgeWeights, - double reversalCost, bool removeLastEdge) { + const double reversalCost, const bool removeLastEdge) { distanceTable.clear(); distanceTable.resize(n, std::vector(n, -1.)); @@ -46,8 +46,8 @@ void Dijkstra::buildTable(const std::uint16_t n, const CouplingMap& couplingMap, } void Dijkstra::dijkstra(const CouplingMap& couplingMap, - std::vector& nodes, std::uint16_t start, - const Matrix& edgeWeights, double reversalCost) { + std::vector& nodes, const std::uint16_t start, + const Matrix& edgeWeights, const double reversalCost) { std::priority_queue, NodeComparator> queue{}; queue.push(&nodes.at(start)); while (!queue.empty()) { From eaf538dd7a4379ae21047a20676e0f6565d8931f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:37:56 +0000 Subject: [PATCH 051/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils.cpp b/src/utils.cpp index d4fe2ec83..3e4b08133 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -9,7 +9,8 @@ void Dijkstra::buildTable(const std::uint16_t n, const CouplingMap& couplingMap, Matrix& distanceTable, const Matrix& edgeWeights, - const double reversalCost, const bool removeLastEdge) { + const double reversalCost, + const bool removeLastEdge) { distanceTable.clear(); distanceTable.resize(n, std::vector(n, -1.)); From c9b4b1ebbf161db5e1d85f050c65839831fddf54 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 7 Dec 2023 14:38:30 +0100 Subject: [PATCH 052/108] fix unreachable return --- src/Mapper.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mapper.cpp b/src/Mapper.cpp index b43b65fd9..32e58b413 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -249,7 +249,6 @@ bool Mapper::isLayerSplittable(std::size_t index) { return activeQubits2QGates.at(index).find(q) == activeQubits2QGates.at(index).end(); }); - return false; } void Mapper::splitLayer(std::size_t index, Architecture& arch) { From 5948be97ffb659a57cea5b4fa5d323a8a336fe24 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 7 Dec 2023 15:24:23 +0100 Subject: [PATCH 053/108] expose both fidelity and log fidelity --- include/MappingResults.hpp | 5 ++++- src/mqt/qmap/pyqmap.pyi | 1 + src/python/bindings.cpp | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/MappingResults.hpp b/include/MappingResults.hpp index 257ae92c0..218b72cd7 100644 --- a/include/MappingResults.hpp +++ b/include/MappingResults.hpp @@ -22,7 +22,9 @@ struct MappingResults { std::size_t cnots = 0; std::size_t layers = 0; double totalFidelity = 1.; - double totalLogFidelity = 0.; + // higher precision than totalFidelity because larger part of double's + // representation space is used + double totalLogFidelity = 0.; // info in output circuit std::size_t swaps = 0; @@ -99,6 +101,7 @@ struct MappingResults { stats["layers"] = input.layers; stats["swaps"] = output.swaps; stats["total_fidelity"] = output.totalFidelity; + stats["total_log_fidelity"] = output.totalLogFidelity; if (config.method == Method::Exact) { stats["direction_reverse"] = output.directionReverse; if (config.includeWCNF && !wcnf.empty()) { diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index dcbc5a078..361464f52 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -76,6 +76,7 @@ class CircuitInfo: gates: int layers: int total_fidelity: float + total_log_fidelity: float name: str qubits: int single_qubit_gates: int diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 94c3b0277..54b17e4ff 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -251,6 +251,8 @@ PYBIND11_MODULE(pyqmap, m) { .def_readwrite("layers", &MappingResults::CircuitInfo::layers) .def_readwrite("total_fidelity", &MappingResults::CircuitInfo::totalFidelity) + .def_readwrite("total_log_fidelity", + &MappingResults::CircuitInfo::totalLogFidelity) .def_readwrite("swaps", &MappingResults::CircuitInfo::swaps) .def_readwrite("direction_reverse", &MappingResults::CircuitInfo::directionReverse) From df6afd8d11fcdaca04e2011f0e65a203b9306673 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:25:10 +0000 Subject: [PATCH 054/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/MappingResults.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/MappingResults.hpp b/include/MappingResults.hpp index 218b72cd7..c438a609c 100644 --- a/include/MappingResults.hpp +++ b/include/MappingResults.hpp @@ -22,9 +22,9 @@ struct MappingResults { std::size_t cnots = 0; std::size_t layers = 0; double totalFidelity = 1.; - // higher precision than totalFidelity because larger part of double's + // higher precision than totalFidelity because larger part of double's // representation space is used - double totalLogFidelity = 0.; + double totalLogFidelity = 0.; // info in output circuit std::size_t swaps = 0; @@ -94,13 +94,13 @@ struct MappingResults { resultJSON["config"] = config.json(); - auto& stats = resultJSON["statistics"]; - stats["timeout"] = timeout; - stats["mapping_time"] = time; - stats["arch"] = architecture; - stats["layers"] = input.layers; - stats["swaps"] = output.swaps; - stats["total_fidelity"] = output.totalFidelity; + auto& stats = resultJSON["statistics"]; + stats["timeout"] = timeout; + stats["mapping_time"] = time; + stats["arch"] = architecture; + stats["layers"] = input.layers; + stats["swaps"] = output.swaps; + stats["total_fidelity"] = output.totalFidelity; stats["total_log_fidelity"] = output.totalLogFidelity; if (config.method == Method::Exact) { stats["direction_reverse"] = output.directionReverse; From fc50069e751a8f15ab8ca4051198d7bdf2637bb7 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 7 Dec 2023 15:47:30 +0100 Subject: [PATCH 055/108] remove InitialLayout::None and Layering::None --- include/Mapper.hpp | 2 +- include/configuration/Configuration.hpp | 4 ++-- include/configuration/InitialLayout.hpp | 13 ++++--------- include/configuration/Layering.hpp | 16 +++++----------- src/Mapper.cpp | 1 - src/heuristic/HeuristicMapper.cpp | 1 - test/test_exact.cpp | 2 -- 7 files changed, 12 insertions(+), 27 deletions(-) diff --git a/include/Mapper.hpp b/include/Mapper.hpp index ff2a900cb..d0d78281e 100644 --- a/include/Mapper.hpp +++ b/include/Mapper.hpp @@ -148,7 +148,7 @@ class Mapper { * 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::IndividualGates -> 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 diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 57bb2d9e1..8badec84d 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -38,10 +38,10 @@ struct Configuration { std::set subgraph{}; // how to cluster the gates into layers - Layering layering = Layering::None; + Layering layering = Layering::IndividualGates; // initial layout to use for heuristic approach - InitialLayout initialLayout = InitialLayout::None; + InitialLayout initialLayout = InitialLayout::Dynamic; // controls the number of iterative bidirectional routing passes, i.e. after // an initial layout is found, the circuit is routed multiple times back and diff --git a/include/configuration/InitialLayout.hpp b/include/configuration/InitialLayout.hpp index 69a8cb363..4778a8359 100644 --- a/include/configuration/InitialLayout.hpp +++ b/include/configuration/InitialLayout.hpp @@ -10,7 +10,7 @@ /// Identity: q_i -> Q_i /// Static: first layer is mapped q_c -> Q_c and q_t -> Q_t /// Dynamic: Layout is generated on demand upon encountering a specific gate -enum class InitialLayout { None, Identity, Static, Dynamic }; +enum class InitialLayout { Identity, Static, Dynamic }; [[maybe_unused]] static inline std::string toString(const InitialLayout strategy) { @@ -21,24 +21,19 @@ toString(const InitialLayout strategy) { return "static"; case InitialLayout::Dynamic: return "dynamic"; - case InitialLayout::None: - return "none"; } return " "; } [[maybe_unused]] static InitialLayout initialLayoutFromString(const std::string& initialLayout) { - if (initialLayout == "none" || initialLayout == "0") { - return InitialLayout::None; - } - if (initialLayout == "identity" || initialLayout == "1") { + if (initialLayout == "identity" || initialLayout == "0") { return InitialLayout::Identity; } - if (initialLayout == "static" || initialLayout == "2") { + if (initialLayout == "static" || initialLayout == "1") { return InitialLayout::Static; } - if (initialLayout == "dynamic" || initialLayout == "3") { + if (initialLayout == "dynamic" || initialLayout == "2") { return InitialLayout::Dynamic; } throw std::invalid_argument("Invalid initial layout value: " + initialLayout); diff --git a/include/configuration/Layering.hpp b/include/configuration/Layering.hpp index c686d5483..e9e19adef 100644 --- a/include/configuration/Layering.hpp +++ b/include/configuration/Layering.hpp @@ -8,7 +8,6 @@ #include enum class Layering { - None, IndividualGates, DisjointQubits, OddGates, @@ -28,30 +27,25 @@ enum class Layering { return "qubit_triangle"; case Layering::Disjoint2qBlocks: return "disjoint_2q_blocks"; - case Layering::None: - return "none"; } return " "; } [[maybe_unused]] static Layering layeringFromString(const std::string& layering) { - if (layering == "none" || layering == "0") { - return Layering::None; - } - if (layering == "individual_gates" || layering == "1") { + if (layering == "individual_gates" || layering == "0") { return Layering::IndividualGates; } - if (layering == "disjoint_qubits" || layering == "2") { + if (layering == "disjoint_qubits" || layering == "1") { return Layering::DisjointQubits; } - if (layering == "odd_gates" || layering == "3") { + if (layering == "odd_gates" || layering == "2") { return Layering::OddGates; } - if (layering == "qubit_triangle" || layering == "4") { + if (layering == "qubit_triangle" || layering == "3") { return Layering::QubitTriangle; } - if (layering == "disjoint_2q_blocks" || layering == "5") { + if (layering == "disjoint_2q_blocks" || layering == "4") { return Layering::Disjoint2qBlocks; } throw std::invalid_argument("Invalid layering value: " + layering); diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 32e58b413..26ede7570 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -125,7 +125,6 @@ void Mapper::createLayers() { // 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(); if (control.has_value()) { diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 1f8cbc5f0..dad18b020 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -210,7 +210,6 @@ void HeuristicMapper::createInitialMapping() { staticInitialMapping(); break; case InitialLayout::Dynamic: - case InitialLayout::None: // nothing to be done here break; diff --git a/test/test_exact.cpp b/test/test_exact.cpp index 1fd7d92dc..563dc0e05 100644 --- a/test/test_exact.cpp +++ b/test/test_exact.cpp @@ -361,14 +361,12 @@ TEST_P(ExactTest, toStringMethods) { EXPECT_EQ(toString(InitialLayout::Identity), "identity"); EXPECT_EQ(toString(InitialLayout::Static), "static"); EXPECT_EQ(toString(InitialLayout::Dynamic), "dynamic"); - EXPECT_EQ(toString(InitialLayout::None), "none"); EXPECT_EQ(toString(Layering::IndividualGates), "individual_gates"); EXPECT_EQ(toString(Layering::DisjointQubits), "disjoint_qubits"); EXPECT_EQ(toString(Layering::Disjoint2qBlocks), "disjoint_2q_blocks"); EXPECT_EQ(toString(Layering::OddGates), "odd_gates"); EXPECT_EQ(toString(Layering::QubitTriangle), "qubit_triangle"); - EXPECT_EQ(toString(Layering::None), "none"); EXPECT_EQ(toString(Encoding::Naive), "naive"); EXPECT_EQ(toString(Encoding::Commander), "commander"); From 9145b7d86b2e20e6f932f3c617a2930c9c40826b Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 7 Dec 2023 16:10:30 +0100 Subject: [PATCH 056/108] split IBR and layer splitting config into activation and limit --- include/configuration/Configuration.hpp | 22 +++++++++++++--------- src/heuristic/HeuristicMapper.cpp | 10 +++++----- src/mqt/qmap/compile.py | 20 ++++++++++++++------ src/mqt/qmap/pyqmap.pyi | 6 ++++-- src/python/bindings.cpp | 8 ++++++-- test/test_heuristic.cpp | 5 ++--- 6 files changed, 44 insertions(+), 27 deletions(-) diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 8badec84d..eb6a3b2fc 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -43,12 +43,13 @@ struct Configuration { // initial layout to use for heuristic approach InitialLayout initialLayout = InitialLayout::Dynamic; - // controls the number of iterative bidirectional routing passes, i.e. after - // an initial layout is found, the circuit is routed multiple times back and - // forth (using settings optimized for time-efficiency) without inserting any - // swaps, to improve the initial layout, only after which the actual routing - // is performed - std::size_t iterativeBidirectionalRouting = 0; + // iterative bidirectional routing, i.e. after an initial layout is found, + // the circuit is routed multiple times back and forth (using settings + // optimized for time-efficiency) without actually inserting any swaps; + // this gradually improves the initial layout; after all passes are done, + // one final full routing pass is performed + bool iterativeBidirectionalRouting = true; + std::size_t iterativeBidirectionalRoutingPasses = 0; // lookahead scheme settings bool lookahead = true; @@ -65,9 +66,12 @@ struct Configuration { // timeout merely affects exact mapper std::size_t timeout = 3600000; // 60min timeout - // after how many expanded nodes to split layer in heuristic mapper, 0 to - // disable - std::size_t splitLayerAfterExpandedNodes = 0; + // if layers should be automatically split after a certain number of expanded + // nodes, thereby reducing the search space (but potentially eliminating + // opportunities for cost savings); acts as a control between runtime and + // result quality + bool automaticLayerSplits = false; + std::size_t automaticLayerSplitsNodeLimit = 5000; // encoding of at most and exactly one constraints in exact mapper Encoding encoding = Encoding::Commander; diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index dad18b020..20e47a247 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -49,7 +49,7 @@ void HeuristicMapper::map(const Configuration& configuration) { << std::endl; config.teleportationQubits = 0; } - if (config.splitLayerAfterExpandedNodes > 0 && + if (config.automaticLayerSplits && (config.layering == Layering::OddGates || config.layering == Layering::QubitTriangle)) { std::cerr << "Layer splitting cannot be used with odd gates or qubit " @@ -57,7 +57,7 @@ void HeuristicMapper::map(const Configuration& configuration) { "non-disjoint gate sets! Performing mapping without layer " "splitting." << std::endl; - config.splitLayerAfterExpandedNodes = 0; + config.automaticLayerSplits = false; } const auto start = std::chrono::steady_clock::now(); initResults(); @@ -77,7 +77,7 @@ void HeuristicMapper::map(const Configuration& configuration) { printQubits(std::clog); } - for (std::size_t i = 0; i < config.iterativeBidirectionalRouting; ++i) { + for (std::size_t i = 0; i < config.iterativeBidirectionalRoutingPasses; ++i) { if (config.verbose) { std::clog << std::endl << "Iterative bidirectional routing (forward pass " << i @@ -585,8 +585,8 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { while (!nodes.empty() && (!done || nodes.top().getTotalCost() < bestDoneNode.getTotalFixedCost())) { - if (splittable && config.splitLayerAfterExpandedNodes > 0 && - expandedNodes >= config.splitLayerAfterExpandedNodes) { + if (splittable && config.automaticLayerSplits && + expandedNodes >= config.automaticLayerSplitsNodeLimit) { if (config.dataLoggingEnabled()) { qc::CompoundOperation compOp(architecture->getNqubits()); for (const auto& gate : layers.at(layer)) { diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 0c108fa04..0a4503148 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -62,9 +62,9 @@ def compile( # noqa: A001 method: str | Method = "heuristic", consider_fidelity: bool = False, initial_layout: str | InitialLayout = "dynamic", - iterative_bidirectional_routing: int = 0, + iterative_bidirectional_routing_passes: int | None = None, layering: str | Layering = "individual_gates", - split_layer_after_expanded_nodes: int = 5000, + automatic_layer_splits_node_limit: int | None = 5000, lookaheads: int | None = 15, lookahead_factor: float = 0.5, use_teleportation: bool = False, @@ -94,9 +94,9 @@ def compile( # noqa: A001 method: The mapping method to use. Either "heuristic" or "exact". Defaults to "heuristic". consider_fidelity: Whether to consider the fidelity of the gates. Defaults to False. initial_layout: The initial layout to use. Defaults to "dynamic". - iterative_bidirectional_routing: Number of iterative bidirectional routing passes to perform. Defaults to 0. + 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". - split_layer_after_expanded_nodes: The number of expanded nodes after which to split a layer (set to 0 to turn off layer splitting). Defaults to 5000. + 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. lookaheads: The number of lookaheads to be used or None if no lookahead should be used. Defaults to 15. lookahead_factor: The rate at which the contribution of future layers to the lookahead decreases. Defaults to 0.5. encoding: The encoding to use for the AMO and exactly one constraints. Defaults to "naive". @@ -134,9 +134,17 @@ def compile( # noqa: A001 config.method = Method(method) config.consider_fidelity = consider_fidelity config.initial_layout = InitialLayout(initial_layout) - config.iterative_bidirectional_routing = iterative_bidirectional_routing + if iterative_bidirectional_routing_passes is None: + config.iterative_bidirectional_routing = False + else: + config.iterative_bidirectional_routing = True + config.iterative_bidirectional_routing_passes = iterative_bidirectional_routing_passes config.layering = Layering(layering) - config.split_layer_after_expanded_nodes = split_layer_after_expanded_nodes + if automatic_layer_splits_node_limit is None: + config.automatic_layer_splits = False + else: + config.automatic_layer_splits = True + config.automatic_layer_splits_node_limit = automatic_layer_splits_node_limit config.encoding = Encoding(encoding) config.commander_grouping = CommanderGrouping(commander_grouping) config.swap_reduction = SwapReduction(swap_reduction) diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index 361464f52..1ee131c41 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -118,9 +118,11 @@ class Configuration: first_lookahead_factor: float include_WCNF: bool # noqa: N815 initial_layout: InitialLayout - iterative_bidirectional_routing: int + iterative_bidirectional_routing: bool + iterative_bidirectional_routing_passes: int layering: Layering - split_layer_after_expanded_nodes: int + automatic_layer_splits: bool + automatic_layer_splits_node_limit: int lookahead: bool lookahead_factor: float lookaheads: int diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 54b17e4ff..9750a1832 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -182,11 +182,15 @@ PYBIND11_MODULE(pyqmap, m) { .def_readwrite("debug", &Configuration::debug) .def_readwrite("data_logging_path", &Configuration::dataLoggingPath) .def_readwrite("layering", &Configuration::layering) - .def_readwrite("split_layer_after_expanded_nodes", - &Configuration::splitLayerAfterExpandedNodes) + .def_readwrite("automatic_layer_splits", + &Configuration::automaticLayerSplits) + .def_readwrite("automatic_layer_splits_node_limit", + &Configuration::automaticLayerSplitsNodeLimit) .def_readwrite("initial_layout", &Configuration::initialLayout) .def_readwrite("iterative_bidirectional_routing", &Configuration::iterativeBidirectionalRouting) + .def_readwrite("iterative_bidirectional_routing_passes", + &Configuration::iterativeBidirectionalRoutingPasses) .def_readwrite("lookahead", &Configuration::lookahead) .def_readwrite("admissible_heuristic", &Configuration::admissibleHeuristic) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index f424c9d3d..2040163ca 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -1196,9 +1196,8 @@ TEST(HeuristicTestFidelity, LayerSplitting) { settings.preMappingOptimizations = false; settings.postMappingOptimizations = false; settings.swapOnFirstLayer = true; - settings.splitLayerAfterExpandedNodes = - 1; // force splittings after 1st expanded node until layers are - // unsplittable + settings.automaticLayerSplits = true; + settings.automaticLayerSplitsNodeLimit = 1; // force splittings after 1st expanded node until layers are unsplittable settings.dataLoggingPath = "test_log/"; mapper->map(settings); mapper->dumpResult("simple_grid_mapped.qasm"); From 405baa853c024608dd2ffbe9a78bd646a8f2783b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:12:12 +0000 Subject: [PATCH 057/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/configuration/Configuration.hpp | 14 +++++++------- test/test_heuristic.cpp | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index eb6a3b2fc..19c8352c5 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -43,12 +43,12 @@ struct Configuration { // initial layout to use for heuristic approach InitialLayout initialLayout = InitialLayout::Dynamic; - // iterative bidirectional routing, i.e. after an initial layout is found, - // the circuit is routed multiple times back and forth (using settings + // iterative bidirectional routing, i.e. after an initial layout is found, + // the circuit is routed multiple times back and forth (using settings // optimized for time-efficiency) without actually inserting any swaps; // this gradually improves the initial layout; after all passes are done, // one final full routing pass is performed - bool iterativeBidirectionalRouting = true; + bool iterativeBidirectionalRouting = true; std::size_t iterativeBidirectionalRoutingPasses = 0; // lookahead scheme settings @@ -66,11 +66,11 @@ struct Configuration { // timeout merely affects exact mapper std::size_t timeout = 3600000; // 60min timeout - // if layers should be automatically split after a certain number of expanded - // nodes, thereby reducing the search space (but potentially eliminating - // opportunities for cost savings); acts as a control between runtime and + // if layers should be automatically split after a certain number of expanded + // nodes, thereby reducing the search space (but potentially eliminating + // opportunities for cost savings); acts as a control between runtime and // result quality - bool automaticLayerSplits = false; + bool automaticLayerSplits = false; std::size_t automaticLayerSplitsNodeLimit = 5000; // encoding of at most and exactly one constraints in exact mapper diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 2040163ca..80c198a76 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -1197,7 +1197,9 @@ TEST(HeuristicTestFidelity, LayerSplitting) { settings.postMappingOptimizations = false; settings.swapOnFirstLayer = true; settings.automaticLayerSplits = true; - settings.automaticLayerSplitsNodeLimit = 1; // force splittings after 1st expanded node until layers are unsplittable + settings.automaticLayerSplitsNodeLimit = + 1; // force splittings after 1st expanded node until layers are + // unsplittable settings.dataLoggingPath = "test_log/"; mapper->map(settings); mapper->dumpResult("simple_grid_mapped.qasm"); From 03cd0efc8c31cece558415ff2333cbf180c4d24b Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 7 Dec 2023 17:05:31 +0100 Subject: [PATCH 058/108] abort for invalid config --- src/heuristic/HeuristicMapper.cpp | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 20e47a247..4d6d6a692 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -22,42 +22,30 @@ void HeuristicMapper::map(const Configuration& configuration) { return; } if (config.considerFidelity && !architecture->isFidelityAvailable()) { - std::cerr << "No calibration data available for this architecture! " - << "Performing mapping without considering fidelity." + std::cerr << "No calibration data available for this architecture!" << std::endl; - config.considerFidelity = false; + return; } if (config.considerFidelity && config.lookahead) { std::cerr << "Lookahead is not yet supported for heuristic mapper using " - "fidelity-aware mapping! Performing mapping without " - "using lookahead." + "fidelity-aware mapping!" << std::endl; - config.lookahead = false; + return; } if (config.considerFidelity && config.initialLayout == InitialLayout::Dynamic) { std::cerr << "Initial layout strategy " << toString(config.initialLayout) << " not yet supported for heuristic mapper using fidelity-aware " - "mapping! Mapping aborted." + "mapping!" << std::endl; return; } if (config.considerFidelity && config.teleportationQubits > 0) { std::cerr << "Teleportation is not yet supported for heuristic mapper using " - "fidelity-aware mapping! Performing mapping without teleportation." + "fidelity-aware mapping!." << std::endl; - config.teleportationQubits = 0; - } - if (config.automaticLayerSplits && - (config.layering == Layering::OddGates || - config.layering == Layering::QubitTriangle)) { - std::cerr << "Layer splitting cannot be used with odd gates or qubit " - "triangle layering, as it might not conserve gate order for " - "non-disjoint gate sets! Performing mapping without layer " - "splitting." - << std::endl; - config.automaticLayerSplits = false; + return; } const auto start = std::chrono::steady_clock::now(); initResults(); From a6dea083e24b1f801d61e2c99c656231f3ac503c Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 7 Dec 2023 22:59:58 +0100 Subject: [PATCH 059/108] fixed tests --- include/configuration/Configuration.hpp | 4 ++-- test/test_heuristic.cpp | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 19c8352c5..777d4cfa3 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -48,7 +48,7 @@ struct Configuration { // optimized for time-efficiency) without actually inserting any swaps; // this gradually improves the initial layout; after all passes are done, // one final full routing pass is performed - bool iterativeBidirectionalRouting = true; + bool iterativeBidirectionalRouting = false; std::size_t iterativeBidirectionalRoutingPasses = 0; // lookahead scheme settings @@ -70,7 +70,7 @@ struct Configuration { // nodes, thereby reducing the search space (but potentially eliminating // opportunities for cost savings); acts as a control between runtime and // result quality - bool automaticLayerSplits = false; + bool automaticLayerSplits = true; std::size_t automaticLayerSplitsNodeLimit = 5000; // encoding of at most and exactly one constraints in exact mapper diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 80c198a76..6931a67bb 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -847,6 +847,7 @@ TEST_P(HeuristicTestFidelity, Identity) { settings.layering = Layering::DisjointQubits; settings.initialLayout = InitialLayout::Identity; settings.considerFidelity = true; + settings.lookahead = false; mapper->map(settings); mapper->dumpResult(GetParam() + "_heuristic_london_fidelity_identity.qasm"); mapper->printResult(std::cout); @@ -858,6 +859,7 @@ TEST_P(HeuristicTestFidelity, Static) { settings.layering = Layering::DisjointQubits; settings.initialLayout = InitialLayout::Static; settings.considerFidelity = true; + settings.lookahead = false; mapper->map(settings); mapper->dumpResult(GetParam() + "_heuristic_london_fidelity_static.qasm"); mapper->printResult(std::cout); @@ -869,6 +871,7 @@ TEST_P(HeuristicTestFidelity, NoFidelity) { settings.layering = Layering::DisjointQubits; settings.initialLayout = InitialLayout::Static; settings.considerFidelity = true; + settings.lookahead = false; nonFidelityMapper->map(settings); nonFidelityMapper->dumpResult(GetParam() + "_heuristic_london_nofidelity.qasm"); @@ -932,6 +935,7 @@ TEST(HeuristicTestFidelity, RemapSingleQubit) { settings.postMappingOptimizations = false; settings.verbose = true; settings.swapOnFirstLayer = true; + settings.lookahead = false; mapper->map(settings); mapper->dumpResult("remap_single_qubit_mapped.qasm"); mapper->printResult(std::cout); @@ -1017,6 +1021,7 @@ TEST(HeuristicTestFidelity, QubitRideAlong) { settings.postMappingOptimizations = false; settings.verbose = true; settings.swapOnFirstLayer = true; + settings.lookahead = false; mapper->map(settings); mapper->dumpResult("qubit_ride_along_mapped.qasm"); mapper->printResult(std::cout); @@ -1079,6 +1084,7 @@ TEST(HeuristicTestFidelity, SingleQubitsCompete) { settings.postMappingOptimizations = false; settings.verbose = true; settings.swapOnFirstLayer = true; + settings.lookahead = false; mapper->map(settings); mapper->dumpResult("single_qubits_compete.qasm"); mapper->printResult(std::cout); @@ -1196,6 +1202,7 @@ TEST(HeuristicTestFidelity, LayerSplitting) { settings.preMappingOptimizations = false; settings.postMappingOptimizations = false; settings.swapOnFirstLayer = true; + settings.lookahead = false; settings.automaticLayerSplits = true; settings.automaticLayerSplitsNodeLimit = 1; // force splittings after 1st expanded node until layers are From c9591ec1a932e6cdfe3135afc3fe4d2d80c2d146 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 7 Dec 2023 23:19:53 +0100 Subject: [PATCH 060/108] adding reference for iterative bidirectional routing --- include/configuration/Configuration.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index 777d4cfa3..eee577c41 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -48,6 +48,11 @@ struct Configuration { // optimized for time-efficiency) without actually inserting any swaps; // this gradually improves the initial layout; after all passes are done, // one final full routing pass is performed + // + // G. Li, Y. Ding, and Y. Xie, "Tackling the qubit mapping problem for + // NISQ-era quantum devices", Proc. 24th Int. Conf. on Architectural Support + // for Program. Languages and Oper. Syst. (ASPLOS) + // https://arxiv.org/abs/1809.02573 bool iterativeBidirectionalRouting = false; std::size_t iterativeBidirectionalRoutingPasses = 0; From 7ffb18fa40fec8f8f272ec0537d15c73c9d076ba Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 7 Dec 2023 23:45:10 +0100 Subject: [PATCH 061/108] fixing code coverage exclusion of visualization module --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3624aa2f6..9929a0146 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -153,7 +153,7 @@ filterwarnings = [ [tool.coverage] run.source = ["mqt.qmap"] -run.omit = ["visualization/*"] +run.exclude = ["mqt.qmap.visualization.*"] report.exclude_also = [ '\.\.\.', 'if TYPE_CHECKING:', From 3d4c23f4e480dccb2a99f9519cedb64f05adb5b4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 22:46:00 +0000 Subject: [PATCH 062/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/configuration/Configuration.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/configuration/Configuration.hpp b/include/configuration/Configuration.hpp index eee577c41..50958c59f 100644 --- a/include/configuration/Configuration.hpp +++ b/include/configuration/Configuration.hpp @@ -49,8 +49,8 @@ struct Configuration { // this gradually improves the initial layout; after all passes are done, // one final full routing pass is performed // - // G. Li, Y. Ding, and Y. Xie, "Tackling the qubit mapping problem for - // NISQ-era quantum devices", Proc. 24th Int. Conf. on Architectural Support + // G. Li, Y. Ding, and Y. Xie, "Tackling the qubit mapping problem for + // NISQ-era quantum devices", Proc. 24th Int. Conf. on Architectural Support // for Program. Languages and Oper. Syst. (ASPLOS) // https://arxiv.org/abs/1809.02573 bool iterativeBidirectionalRouting = false; From 41c1175d57fad9931ebafdea2422844f83df9d09 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 8 Dec 2023 00:41:36 +0100 Subject: [PATCH 063/108] another fix to coverage omit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9929a0146..bf02c2a39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -153,7 +153,7 @@ filterwarnings = [ [tool.coverage] run.source = ["mqt.qmap"] -run.exclude = ["mqt.qmap.visualization.*"] +run.omit = ["*/visualization/*"] report.exclude_also = [ '\.\.\.', 'if TYPE_CHECKING:', From 4b967931618fd11df081131f9cff399b3441e634 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 8 Dec 2023 22:06:03 +0100 Subject: [PATCH 064/108] remove code duplication in DataLogger's JSON generation --- src/DataLogger.cpp | 101 ++++++---------------------------------- test/test_heuristic.cpp | 36 ++++++++------ 2 files changed, 35 insertions(+), 102 deletions(-) diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index 73f655bce..9e142d38a 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -244,95 +244,20 @@ void DataLogger::logMappingResult(MappingResults& result) { } // prepare json data - nlohmann::json json; - auto& circuit = json["input_circuit"]; - circuit["name"] = result.input.name; - circuit["qubits"] = result.input.qubits; - circuit["gates"] = result.input.gates; - circuit["single_qubit_gates"] = result.input.singleQubitGates; - circuit["cnots"] = result.input.cnots; - - auto& mappedCirc = json["output_circuit"]; - mappedCirc["name"] = result.output.name; - mappedCirc["qubits"] = result.output.qubits; - mappedCirc["gates"] = result.output.gates; - mappedCirc["single_qubit_gates"] = result.output.singleQubitGates; - mappedCirc["cnots"] = result.output.cnots; - - auto& config = json["config"]; - config["method"] = toString(result.config.method); - config["pre_mapping_optimizations"] = result.config.preMappingOptimizations; - config["post_mapping_optimizations"] = result.config.postMappingOptimizations; - config["add_measurements_to_mapped_circuit"] = - result.config.addMeasurementsToMappedCircuit; - config["layering"] = toString(result.config.layering); - config["initial_layout"] = toString(result.config.initialLayout); - if (result.config.useTeleportation) { - auto& teleportation = config["teleportation"]; - teleportation["qubits"] = result.config.teleportationQubits; - teleportation["seed"] = result.config.teleportationSeed; - teleportation["fake"] = result.config.teleportationFake; - } else { - config["teleportation"] = false; - } - config["timeout"] = result.config.timeout; - + nlohmann::json json = result.json(); auto& stats = json["statistics"]; - stats["timeout"] = result.timeout; - stats["mapping_time"] = result.time; - stats["layers"] = result.input.layers; - stats["swaps"] = result.output.swaps; - stats["total_fidelity"] = result.output.totalFidelity; - stats["total_log_fidelity"] = result.output.totalLogFidelity; - stats["additional_gates"] = - static_cast>( - result.output.gates) - - static_cast>( - result.input.gates); - - if (result.config.method == Method::Exact) { - config["encoding"] = result.config.encoding; - config["commander_grouping"] = result.config.commanderGrouping; - config["subgraph"] = result.config.subgraph; - config["use_subsets"] = result.config.useSubsets; - - stats["direction_reverse"] = result.output.directionReverse; - if (result.config.includeWCNF && !result.wcnf.empty()) { - stats["WCNF"] = result.wcnf; - } - } else if (result.config.method == Method::Heuristic) { - if (result.config.lookahead) { - auto& lookahead = config["lookahead"]; - lookahead["nr_lookaheads"] = result.config.nrLookaheads; - lookahead["first_factor"] = result.config.firstLookaheadFactor; - lookahead["factor"] = result.config.lookaheadFactor; - } else { - config["lookahead"] = false; - } - config["admissible_heuristic"] = result.config.admissibleHeuristic; - config["consider_fidelity"] = result.config.considerFidelity; - - stats["teleportations"] = result.output.teleportations; - auto& benchmark = stats["benchmark"]; - benchmark["expanded_nodes"] = result.heuristicBenchmark.expandedNodes; - benchmark["generated_nodes"] = result.heuristicBenchmark.generatedNodes; - benchmark["time_per_node"] = result.heuristicBenchmark.timePerNode; - benchmark["average_branching_factor"] = - result.heuristicBenchmark.averageBranchingFactor; - benchmark["effective_branching_factor"] = - result.heuristicBenchmark.effectiveBranchingFactor; - for (std::size_t i = 0; i < result.layerHeuristicBenchmark.size(); ++i) { - auto& layerBenchmark = result.layerHeuristicBenchmark.at(i); - auto& jsonLayerBenchmark = benchmark["layers"][i]; - jsonLayerBenchmark["expanded_nodes"] = layerBenchmark.expandedNodes; - jsonLayerBenchmark["generated_nodes"] = layerBenchmark.generatedNodes; - jsonLayerBenchmark["solution_depth"] = layerBenchmark.solutionDepth; - jsonLayerBenchmark["time_per_node"] = layerBenchmark.timePerNode; - jsonLayerBenchmark["average_branching_factor"] = - layerBenchmark.averageBranchingFactor; - jsonLayerBenchmark["effective_branching_factor"] = - layerBenchmark.effectiveBranchingFactor; - } + auto& benchmark = stats["benchmark"]; + for (std::size_t i = 0; i < result.layerHeuristicBenchmark.size(); ++i) { + auto& layerBenchmark = result.layerHeuristicBenchmark.at(i); + auto& jsonLayerBenchmark = benchmark["layers"][i]; + jsonLayerBenchmark["expanded_nodes"] = layerBenchmark.expandedNodes; + jsonLayerBenchmark["generated_nodes"] = layerBenchmark.generatedNodes; + jsonLayerBenchmark["solution_depth"] = layerBenchmark.solutionDepth; + jsonLayerBenchmark["time_per_node"] = layerBenchmark.timePerNode; + jsonLayerBenchmark["average_branching_factor"] = + layerBenchmark.averageBranchingFactor; + jsonLayerBenchmark["effective_branching_factor"] = + layerBenchmark.effectiveBranchingFactor; } // write json data to file diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 6931a67bb..00b7b0c0f 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -361,30 +361,39 @@ TEST(Functionality, DataLogger) { const auto& configJson = resultJson["config"]; EXPECT_EQ(configJson["add_measurements_to_mapped_circuit"], settings.addMeasurementsToMappedCircuit); - EXPECT_EQ(configJson["admissible_heuristic"], settings.admissibleHeuristic); - EXPECT_EQ(configJson["consider_fidelity"], settings.considerFidelity); - EXPECT_EQ(configJson["initial_layout"], toString(settings.initialLayout)); - EXPECT_EQ(configJson["layering"], toString(settings.layering)); + EXPECT_EQ(configJson["debug"], settings.debug); + EXPECT_EQ(configJson["verbose"], settings.verbose); + EXPECT_EQ(configJson["layering_strategy"], toString(settings.layering)); EXPECT_EQ(configJson["method"], toString(settings.method)); EXPECT_EQ(configJson["post_mapping_optimizations"], settings.postMappingOptimizations); EXPECT_EQ(configJson["pre_mapping_optimizations"], settings.preMappingOptimizations); - EXPECT_EQ(configJson["teleportation"], settings.useTeleportation); - EXPECT_EQ(configJson["timeout"], settings.timeout); - const auto& lookaheadJson = configJson["lookahead"]; - EXPECT_EQ(lookaheadJson["factor"], settings.lookaheadFactor); - EXPECT_EQ(lookaheadJson["first_factor"], settings.firstLookaheadFactor); - EXPECT_EQ(lookaheadJson["nr_lookaheads"], settings.nrLookaheads); - - const auto& inCircJson = resultJson["input_circuit"]; + const auto& heuristicSettingsJson = configJson["settings"]; + EXPECT_EQ(heuristicSettingsJson["admissible_heuristic"], settings.admissibleHeuristic); + EXPECT_EQ(heuristicSettingsJson["consider_fidelity"], settings.considerFidelity); + EXPECT_EQ(heuristicSettingsJson["initial_layout"], toString(settings.initialLayout)); + if (settings.lookahead) { + const auto& lookaheadJson = heuristicSettingsJson["lookahead"]; + EXPECT_EQ(lookaheadJson["factor"], settings.lookaheadFactor); + EXPECT_EQ(lookaheadJson["first_factor"], settings.firstLookaheadFactor); + EXPECT_EQ(lookaheadJson["lookaheads"], settings.nrLookaheads); + } + if (settings.useTeleportation) { + const auto& teleportationJson = heuristicSettingsJson["teleportation"]; + EXPECT_EQ(teleportationJson["qubits"], settings.teleportationQubits); + EXPECT_EQ(teleportationJson["seed"], settings.teleportationSeed); + EXPECT_EQ(teleportationJson["fake"], settings.teleportationFake); + } + + const auto& inCircJson = resultJson["circuit"]; EXPECT_EQ(inCircJson["cnots"], results.input.cnots); EXPECT_EQ(inCircJson["gates"], results.input.gates); EXPECT_EQ(inCircJson["name"], results.input.name); EXPECT_EQ(inCircJson["qubits"], results.input.qubits); EXPECT_EQ(inCircJson["single_qubit_gates"], results.input.singleQubitGates); - const auto& outCircJson = resultJson["output_circuit"]; + const auto& outCircJson = resultJson["mapped_circuit"]; EXPECT_EQ(outCircJson["cnots"], results.output.cnots); EXPECT_EQ(outCircJson["gates"], results.output.gates); EXPECT_EQ(outCircJson["name"], results.output.name); @@ -401,7 +410,6 @@ TEST(Functionality, DataLogger) { EXPECT_EQ(statJson["mapping_time"], results.time); EXPECT_EQ(statJson["swaps"], results.output.swaps); EXPECT_EQ(statJson["teleportations"], results.output.teleportations); - EXPECT_EQ(statJson["timeout"], results.timeout); EXPECT_EQ(statJson["total_fidelity"], results.output.totalFidelity); EXPECT_EQ(statJson["total_log_fidelity"], results.output.totalLogFidelity); From 93be1c1c697136f39bca2818e12f672d47447915 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:09:30 +0000 Subject: [PATCH 065/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DataLogger.cpp | 12 ++++++------ test/test_heuristic.cpp | 11 +++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index 9e142d38a..8b427a9e6 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -244,13 +244,13 @@ void DataLogger::logMappingResult(MappingResults& result) { } // prepare json data - nlohmann::json json = result.json(); - auto& stats = json["statistics"]; - auto& benchmark = stats["benchmark"]; + nlohmann::json json = result.json(); + auto& stats = json["statistics"]; + auto& benchmark = stats["benchmark"]; for (std::size_t i = 0; i < result.layerHeuristicBenchmark.size(); ++i) { - auto& layerBenchmark = result.layerHeuristicBenchmark.at(i); - auto& jsonLayerBenchmark = benchmark["layers"][i]; - jsonLayerBenchmark["expanded_nodes"] = layerBenchmark.expandedNodes; + auto& layerBenchmark = result.layerHeuristicBenchmark.at(i); + auto& jsonLayerBenchmark = benchmark["layers"][i]; + jsonLayerBenchmark["expanded_nodes"] = layerBenchmark.expandedNodes; jsonLayerBenchmark["generated_nodes"] = layerBenchmark.generatedNodes; jsonLayerBenchmark["solution_depth"] = layerBenchmark.solutionDepth; jsonLayerBenchmark["time_per_node"] = layerBenchmark.timePerNode; diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 00b7b0c0f..c85622ce8 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -370,9 +370,12 @@ TEST(Functionality, DataLogger) { EXPECT_EQ(configJson["pre_mapping_optimizations"], settings.preMappingOptimizations); const auto& heuristicSettingsJson = configJson["settings"]; - EXPECT_EQ(heuristicSettingsJson["admissible_heuristic"], settings.admissibleHeuristic); - EXPECT_EQ(heuristicSettingsJson["consider_fidelity"], settings.considerFidelity); - EXPECT_EQ(heuristicSettingsJson["initial_layout"], toString(settings.initialLayout)); + EXPECT_EQ(heuristicSettingsJson["admissible_heuristic"], + settings.admissibleHeuristic); + EXPECT_EQ(heuristicSettingsJson["consider_fidelity"], + settings.considerFidelity); + EXPECT_EQ(heuristicSettingsJson["initial_layout"], + toString(settings.initialLayout)); if (settings.lookahead) { const auto& lookaheadJson = heuristicSettingsJson["lookahead"]; EXPECT_EQ(lookaheadJson["factor"], settings.lookaheadFactor); @@ -385,7 +388,7 @@ TEST(Functionality, DataLogger) { EXPECT_EQ(teleportationJson["seed"], settings.teleportationSeed); EXPECT_EQ(teleportationJson["fake"], settings.teleportationFake); } - + const auto& inCircJson = resultJson["circuit"]; EXPECT_EQ(inCircJson["cnots"], results.input.cnots); EXPECT_EQ(inCircJson["gates"], results.input.gates); From 5d3d9eac94cae2e1c81ff65486b073ea6c268a87 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 8 Dec 2023 22:52:42 +0100 Subject: [PATCH 066/108] save considerFidelity and admissibleHeuristic in Node as member --- include/heuristic/HeuristicMapper.hpp | 54 ++++++++++++++------------- src/heuristic/HeuristicMapper.cpp | 24 +++++------- test/test_heuristic.cpp | 35 +++++++++-------- 3 files changed, 54 insertions(+), 59 deletions(-) diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index afc43beb5..8873cc49d 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -30,15 +30,11 @@ class HeuristicMapper : public Mapper { * swaps, mappings and costs */ struct Node { - /** current fixed cost (for non-fidelity-aware mapping cost of all swaps - * already added) */ - double 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.; + /** gates (pair of logical qubits) currently mapped next to each other */ + std::set validMappedTwoQubitGates = {}; + /** 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> swaps = {}; /** * containing the logical qubit currently mapped to each physical qubit. * `qubits[physical_qubit] = logical_qubit` @@ -53,30 +49,39 @@ class HeuristicMapper : public Mapper { * The inverse of `qubits` */ std::array 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> swaps = {}; + /** current fixed cost (for non-fidelity-aware mapping cost of all swaps + * already added) */ + double 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.; /** number of swaps used to get from mapping after last layer to the current * mapping */ std::size_t nswaps = 0; /** depth in search tree (starting with 0 at the root) */ std::size_t depth = 0; - /** gates (pair of logical qubits) currently mapped next to each other */ - std::set validMappedTwoQubitGates = {}; std::size_t parent = 0; std::size_t id; + /** true if all qubit pairs are mapped next to each other on the + * architecture */ + bool done = true; + /** controls if fidelity-aware heuristic should be used */ + bool considerFidelity = false; + /** controls if admissible heuristic should be used */ + bool admissibleHeuristic = true; - explicit Node(std::size_t nodeId) : id(nodeId){}; + explicit Node(std::size_t nodeId, const bool considerFid = false, const bool admissibleHeur = true) : id(nodeId), considerFidelity(considerFid), admissibleHeuristic(admissibleHeur) {}; Node(std::size_t nodeId, std::size_t parentId, const std::array& q, const std::array& loc, const std::vector>& sw = {}, - const double initCostFixed = 0, const std::size_t searchDepth = 0) + const double initCostFixed = 0, const std::size_t searchDepth = 0, + const bool considerFid = false, const bool admissibleHeur = true) : costFixed(initCostFixed), depth(searchDepth), parent(parentId), - id(nodeId) { + id(nodeId), considerFidelity(considerFid), admissibleHeuristic(admissibleHeur) { std::copy(q.begin(), q.end(), qubits.begin()); std::copy(loc.begin(), loc.end(), locations.begin()); std::copy(sw.begin(), sw.end(), std::back_inserter(swaps)); @@ -102,8 +107,7 @@ class HeuristicMapper : public Mapper { */ void applySWAP(const Edge& swap, Architecture& arch, const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity, - bool considerFidelity); + const TwoQubitMultiplicity& twoQubitGateMultiplicity); /** * @brief applies an in-place teleportation of 2 qubits in `qubits` and @@ -121,8 +125,7 @@ class HeuristicMapper : public Mapper { void recalculateFixedCost( const Architecture& arch, const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity, - bool considerFidelity); + const TwoQubitMultiplicity& twoQubitGateMultiplicity); /** * @brief calculates the heuristic cost of the current mapping in the node @@ -142,8 +145,7 @@ class HeuristicMapper : public Mapper { const Architecture& arch, const SingleQubitMultiplicity& singleQubitGateMultiplicity, const TwoQubitMultiplicity& twoQubitGateMultiplicity, - const std::unordered_set& consideredQubits, - bool admissibleHeuristic, bool considerFidelity); + const std::unordered_set& consideredQubits); std::ostream& print(std::ostream& out) const { out << "{\n"; diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 4d6d6a692..9cd715e48 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -545,7 +545,7 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { singleQubitMultiplicities.at(layer); const TwoQubitMultiplicity& twoQubitMultiplicity = twoQubitMultiplicities.at(layer); - Node node(nextNodeId++); + Node node(nextNodeId++, considerFidelity, config.admissibleHeuristic); Node bestDoneNode(0); bool done = false; @@ -554,10 +554,9 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { node.locations = locations; node.qubits = qubits; node.recalculateFixedCost(*architecture, singleQubitMultiplicity, - twoQubitMultiplicity, config.considerFidelity); + twoQubitMultiplicity); node.updateHeuristicCost(*architecture, singleQubitMultiplicity, - twoQubitMultiplicity, consideredQubits, - config.admissibleHeuristic, config.considerFidelity); + twoQubitMultiplicity, consideredQubits); if (config.dataLoggingEnabled()) { dataLogger->logSearchNode(layer, node.id, node.parent, node.costFixed, @@ -747,21 +746,19 @@ void HeuristicMapper::expandNodeAddOneSwap( const auto& config = results.config; Node newNode = Node(nextNodeId++, node.id, node.qubits, node.locations, - node.swaps, node.costFixed, node.depth + 1); + node.swaps, node.costFixed, node.depth + 1, node.considerFidelity, node.admissibleHeuristic); if (architecture->isEdgeConnected(swap) || architecture->isEdgeConnected(Edge{swap.second, swap.first})) { newNode.applySWAP(swap, *architecture, singleQubitMultiplicities.at(layer), - twoQubitMultiplicities.at(layer), - config.considerFidelity); + twoQubitMultiplicities.at(layer)); } else { newNode.applyTeleportation(swap, *architecture); } newNode.updateHeuristicCost( *architecture, singleQubitMultiplicities.at(layer), - twoQubitMultiplicities.at(layer), consideredQubits, - results.config.admissibleHeuristic, results.config.considerFidelity); + twoQubitMultiplicities.at(layer), consideredQubits); // calculate heuristics for the cost of the following layers if (config.lookahead) { @@ -841,8 +838,7 @@ void HeuristicMapper::lookahead(const std::size_t layer, void HeuristicMapper::Node::applySWAP( const Edge& swap, Architecture& arch, const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity, - bool considerFidelity) { + const TwoQubitMultiplicity& twoQubitGateMultiplicity) { ++nswaps; swaps.emplace_back(); const auto q1 = qubits.at(swap.first); @@ -993,8 +989,7 @@ void HeuristicMapper::Node::applyTeleportation(const Edge& swap, void HeuristicMapper::Node::recalculateFixedCost( const Architecture& arch, const SingleQubitMultiplicity& singleQubitGateMultiplicity, - const TwoQubitMultiplicity& twoQubitGateMultiplicity, - bool considerFidelity) { + const TwoQubitMultiplicity& twoQubitGateMultiplicity) { costFixed = 0; if (considerFidelity) { // adding costs of single qubit gates @@ -1065,8 +1060,7 @@ void HeuristicMapper::Node::updateHeuristicCost( const Architecture& arch, const SingleQubitMultiplicity& singleQubitGateMultiplicity, const TwoQubitMultiplicity& twoQubitGateMultiplicity, - const std::unordered_set& consideredQubits, - bool admissibleHeuristic, bool considerFidelity) { + const std::unordered_set& consideredQubits) { costHeur = 0.; done = true; diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 00b7b0c0f..a703b41e1 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -28,20 +28,19 @@ TEST(Functionality, NodeCostCalculation) { {Exchange(0, 1, qc::OpType::Teleportation)}, {Exchange(1, 2, qc::OpType::SWAP)}}; - HeuristicMapper::Node node(0, 0, qubits, locations, swaps, 5.); - EXPECT_NEAR(node.costFixed, 5., tolerance); - node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits, - true, false); - EXPECT_NEAR(node.costHeur, - COST_UNIDIRECTIONAL_SWAP * 2 + COST_DIRECTION_REVERSE, tolerance); - node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits, - false, false); + HeuristicMapper::Node node(0, 0, qubits, locations, swaps, 5., 0, false, false); + node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits); EXPECT_NEAR(node.costHeur, COST_UNIDIRECTIONAL_SWAP * 14 + COST_DIRECTION_REVERSE * 3, tolerance); - node.applySWAP({3, 4}, arch, empty1Mult, multiplicity, false); - node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits, - true, false); + + node = HeuristicMapper::Node(0, 0, qubits, locations, swaps, 5., 0, false, true); + EXPECT_NEAR(node.costFixed, 5., tolerance); + node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits); + EXPECT_NEAR(node.costHeur, + COST_UNIDIRECTIONAL_SWAP * 2 + COST_DIRECTION_REVERSE, tolerance); + node.applySWAP({3, 4}, arch, empty1Mult, multiplicity); + node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits); EXPECT_NEAR(node.costFixed, 5. + COST_UNIDIRECTIONAL_SWAP, tolerance); EXPECT_NEAR(node.costHeur, COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE, tolerance); @@ -57,7 +56,7 @@ TEST(Functionality, NodeCostCalculation) { tolerance); EXPECT_NEAR(node.getTotalFixedCost(), 7. + COST_UNIDIRECTIONAL_SWAP, tolerance); - node.recalculateFixedCost(arch, empty1Mult, multiplicity, false); + node.recalculateFixedCost(arch, empty1Mult, multiplicity); EXPECT_NEAR(node.costFixed, COST_TELEPORTATION + COST_UNIDIRECTIONAL_SWAP * 2, tolerance); EXPECT_NEAR(node.costHeur, COST_UNIDIRECTIONAL_SWAP + COST_DIRECTION_REVERSE, @@ -217,10 +216,10 @@ TEST(Functionality, HeuristicAdmissibility) { std::stack permStack{}; auto initNode = - HeuristicMapper::Node(0, 0, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}); - initNode.recalculateFixedCost(architecture, empty1Mult, multiplicity, false); + HeuristicMapper::Node(0, 0, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {}, 0., 0, false, true); + initNode.recalculateFixedCost(architecture, empty1Mult, multiplicity); initNode.updateHeuristicCost(architecture, empty1Mult, multiplicity, - consideredQubits, true, false); + consideredQubits); nodeStack.emplace_back(initNode); permStack.emplace(perms.size()); @@ -240,10 +239,10 @@ TEST(Functionality, HeuristicAdmissibility) { --permStack.top(); const auto perm = perms[permStack.top()]; auto newNode = HeuristicMapper::Node(1, 0, node.qubits, node.locations, - node.swaps, node.costFixed); - newNode.applySWAP(perm, architecture, empty1Mult, multiplicity, false); + node.swaps, node.costFixed, node.depth + 1, false, true); + newNode.applySWAP(perm, architecture, empty1Mult, multiplicity); newNode.updateHeuristicCost(architecture, empty1Mult, multiplicity, - consideredQubits, true, false); + consideredQubits); nodeStack.emplace_back(newNode); permStack.emplace(perms.size()); } From 9be859e261cfd2bfa857e450fca219f37ffa7668 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:53:33 +0000 Subject: [PATCH 067/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/heuristic/HeuristicMapper.hpp | 14 +++++++++----- src/heuristic/HeuristicMapper.cpp | 3 ++- test/test_heuristic.cpp | 17 ++++++++++------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index 8873cc49d..c29df7447 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -62,9 +62,9 @@ class HeuristicMapper : public Mapper { * mapping */ std::size_t nswaps = 0; /** depth in search tree (starting with 0 at the root) */ - std::size_t depth = 0; - std::size_t parent = 0; - std::size_t id; + std::size_t depth = 0; + std::size_t parent = 0; + std::size_t id; /** true if all qubit pairs are mapped next to each other on the * architecture */ bool done = true; @@ -73,7 +73,10 @@ class HeuristicMapper : public Mapper { /** controls if admissible heuristic should be used */ bool admissibleHeuristic = true; - explicit Node(std::size_t nodeId, const bool considerFid = false, const bool admissibleHeur = true) : id(nodeId), considerFidelity(considerFid), admissibleHeuristic(admissibleHeur) {}; + explicit Node(std::size_t nodeId, const bool considerFid = false, + const bool admissibleHeur = true) + : id(nodeId), considerFidelity(considerFid), + admissibleHeuristic(admissibleHeur){}; Node(std::size_t nodeId, std::size_t parentId, const std::array& q, const std::array& loc, @@ -81,7 +84,8 @@ class HeuristicMapper : public Mapper { const double initCostFixed = 0, const std::size_t searchDepth = 0, const bool considerFid = false, const bool admissibleHeur = true) : costFixed(initCostFixed), depth(searchDepth), parent(parentId), - id(nodeId), considerFidelity(considerFid), admissibleHeuristic(admissibleHeur) { + id(nodeId), considerFidelity(considerFid), + admissibleHeuristic(admissibleHeur) { std::copy(q.begin(), q.end(), qubits.begin()); std::copy(loc.begin(), loc.end(), locations.begin()); std::copy(sw.begin(), sw.end(), std::back_inserter(swaps)); diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 9cd715e48..15302fa6b 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -746,7 +746,8 @@ void HeuristicMapper::expandNodeAddOneSwap( const auto& config = results.config; Node newNode = Node(nextNodeId++, node.id, node.qubits, node.locations, - node.swaps, node.costFixed, node.depth + 1, node.considerFidelity, node.admissibleHeuristic); + node.swaps, node.costFixed, node.depth + 1, + node.considerFidelity, node.admissibleHeuristic); if (architecture->isEdgeConnected(swap) || architecture->isEdgeConnected(Edge{swap.second, swap.first})) { diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 03fe7ae23..41122ff3e 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -28,13 +28,15 @@ TEST(Functionality, NodeCostCalculation) { {Exchange(0, 1, qc::OpType::Teleportation)}, {Exchange(1, 2, qc::OpType::SWAP)}}; - HeuristicMapper::Node node(0, 0, qubits, locations, swaps, 5., 0, false, false); + HeuristicMapper::Node node(0, 0, qubits, locations, swaps, 5., 0, false, + false); node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits); EXPECT_NEAR(node.costHeur, COST_UNIDIRECTIONAL_SWAP * 14 + COST_DIRECTION_REVERSE * 3, tolerance); - - node = HeuristicMapper::Node(0, 0, qubits, locations, swaps, 5., 0, false, true); + + node = + HeuristicMapper::Node(0, 0, qubits, locations, swaps, 5., 0, false, true); EXPECT_NEAR(node.costFixed, 5., tolerance); node.updateHeuristicCost(arch, empty1Mult, multiplicity, consideredQubits); EXPECT_NEAR(node.costHeur, @@ -215,8 +217,8 @@ TEST(Functionality, HeuristicAdmissibility) { nodeStack.reserve(depthLimit); std::stack permStack{}; - auto initNode = - HeuristicMapper::Node(0, 0, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {}, 0., 0, false, true); + auto initNode = HeuristicMapper::Node( + 0, 0, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {}, 0., 0, false, true); initNode.recalculateFixedCost(architecture, empty1Mult, multiplicity); initNode.updateHeuristicCost(architecture, empty1Mult, multiplicity, consideredQubits); @@ -238,8 +240,9 @@ TEST(Functionality, HeuristicAdmissibility) { } --permStack.top(); const auto perm = perms[permStack.top()]; - auto newNode = HeuristicMapper::Node(1, 0, node.qubits, node.locations, - node.swaps, node.costFixed, node.depth + 1, false, true); + auto newNode = + HeuristicMapper::Node(1, 0, node.qubits, node.locations, node.swaps, + node.costFixed, node.depth + 1, false, true); newNode.applySWAP(perm, architecture, empty1Mult, multiplicity); newNode.updateHeuristicCost(architecture, empty1Mult, multiplicity, consideredQubits); From 254b291fbe765823238e76059c5c02ad535b0a5e Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 8 Dec 2023 23:01:11 +0100 Subject: [PATCH 068/108] generate zeroMatrix ad-hoc --- include/Architecture.hpp | 5 ++--- src/Architecture.cpp | 8 -------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/include/Architecture.hpp b/include/Architecture.hpp index 0d4652ce8..5a9a2851d 100644 --- a/include/Architecture.hpp +++ b/include/Architecture.hpp @@ -275,7 +275,8 @@ class Architecture { throw QMAPException("No fidelity data available."); } if (skipEdges >= fidelityDistanceTables.size()) { - return zeroMatrix; + static Matrix defaultMatrix(nqubits, std::vector(nqubits, 0.0)); + return defaultMatrix; } return fidelityDistanceTables.at(skipEdges); } @@ -391,7 +392,6 @@ class Architecture { void reset() { name = ""; nqubits = 0; - zeroMatrix.clear(); couplingMap.clear(); distanceTable.clear(); isBidirectional = true; @@ -477,7 +477,6 @@ class Architecture { protected: std::string name; std::uint16_t nqubits = 0; - Matrix zeroMatrix = {}; CouplingMap couplingMap = {}; CouplingMap currentTeleportations = {}; bool isBidirectional = true; diff --git a/src/Architecture.cpp b/src/Architecture.cpp index 1ec008bdd..57a9b75b7 100644 --- a/src/Architecture.cpp +++ b/src/Architecture.cpp @@ -44,8 +44,6 @@ void Architecture::loadCouplingMap(std::istream&& is) { if (std::getline(is, line)) { if (std::regex_search(line, m, rNqubits)) { nqubits = static_cast(std::stoul(m.str(1))); - zeroMatrix.clear(); - zeroMatrix.resize(nqubits, std::vector(nqubits, 0.0)); } else { throw QMAPException("No qubit count found in coupling map file: " + line); } @@ -68,8 +66,6 @@ void Architecture::loadCouplingMap(std::istream&& is) { void Architecture::loadCouplingMap(std::uint16_t nQ, const CouplingMap& cm) { nqubits = nQ; - zeroMatrix.clear(); - zeroMatrix.resize(nqubits, std::vector(nqubits, 0.0)); couplingMap = cm; properties.clear(); name = "generic_" + std::to_string(nQ); @@ -157,8 +153,6 @@ void Architecture::loadProperties(std::istream&& is) { properties.setNqubits(qubitNumber); if (!isArchitectureAvailable()) { nqubits = qubitNumber; - zeroMatrix.clear(); - zeroMatrix.resize(nqubits, std::vector(nqubits, 0.0)); createDistanceTable(); } @@ -173,8 +167,6 @@ void Architecture::loadProperties(const Properties& props) { } } nqubits = props.getNqubits(); - zeroMatrix.clear(); - zeroMatrix.resize(nqubits, std::vector(nqubits, 0.0)); name = "generic_" + std::to_string(nqubits); createDistanceTable(); } From eb3c8b53cac8f0ae83b0a0a4864611308c17b2ea Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 22:01:42 +0000 Subject: [PATCH 069/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Architecture.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Architecture.cpp b/src/Architecture.cpp index 57a9b75b7..1c65cbf95 100644 --- a/src/Architecture.cpp +++ b/src/Architecture.cpp @@ -65,7 +65,7 @@ void Architecture::loadCouplingMap(std::istream&& is) { } void Architecture::loadCouplingMap(std::uint16_t nQ, const CouplingMap& cm) { - nqubits = nQ; + nqubits = nQ; couplingMap = cm; properties.clear(); name = "generic_" + std::to_string(nQ); @@ -167,7 +167,7 @@ void Architecture::loadProperties(const Properties& props) { } } nqubits = props.getNqubits(); - name = "generic_" + std::to_string(nqubits); + name = "generic_" + std::to_string(nqubits); createDistanceTable(); } properties = props; From fbd09a1e65913f14a7f4451d1f810c8d06468c75 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 14 Dec 2023 00:16:50 +0100 Subject: [PATCH 070/108] replace infinities with max double --- src/Architecture.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Architecture.cpp b/src/Architecture.cpp index 1c65cbf95..23ee38a35 100644 --- a/src/Architecture.cpp +++ b/src/Architecture.cpp @@ -188,7 +188,7 @@ void Architecture::createDistanceTable() { isBidirectional = true; Matrix edgeWeights( nqubits, - std::vector(nqubits, std::numeric_limits::infinity())); + std::vector(nqubits, std::numeric_limits::max())); for (const auto& edge : couplingMap) { if (couplingMap.find({edge.second, edge.first}) == couplingMap.end()) { isBidirectional = false; @@ -210,11 +210,11 @@ void Architecture::createFidelityTable() { twoQubitFidelityCosts.clear(); twoQubitFidelityCosts.resize( nqubits, - std::vector(nqubits, std::numeric_limits::infinity())); + std::vector(nqubits, std::numeric_limits::max())); swapFidelityCosts.clear(); swapFidelityCosts.resize( nqubits, - std::vector(nqubits, std::numeric_limits::infinity())); + std::vector(nqubits, std::numeric_limits::max())); singleQubitFidelities.resize(nqubits, 1.0); singleQubitFidelityCosts.resize(nqubits, 0.0); From 50b1abc085f536e39c91893f987af3a4f81f7893 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 14 Dec 2023 02:11:44 +0100 Subject: [PATCH 071/108] split routing and pseudo-routing into 2 functions --- include/heuristic/HeuristicMapper.hpp | 16 ++++--- src/heuristic/HeuristicMapper.cpp | 64 ++++++++++++++------------- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index c29df7447..ef9c0837f 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -225,14 +225,20 @@ class HeuristicMapper : public Mapper { /** * @brief Routes the input circuit, i.e. inserts SWAPs to meet topology * constraints and optimize fidelity if activated + */ + void routeCircuit(); + + /** + * @brief Performes pseudo-routing on the input circuit, i.e. rearranges the + * qubit layout layer by layer to meet topology constraints without actually + * inserting SWAPs (leaves all global data unchanged except for `qubits` and + * `locations`, which hold the final layout) + * + * used for iterative bidirectional routing * * @param reverse if true, the circuit is routed from the end to the beginning - * (this will not produce a valid mapping, use only with pseudo routing!) - * @param pseudoRouting if true, routing will only be simulated without - * altering the circuit or modifying any other global data except for `qubits` - * and `locations`, which will hold the final qubit layout afterwards */ - void routeCircuit(bool reverse = false, bool pseudoRouting = false); + void pseudoRouteCircuit(bool reverse = false); /** * @brief search for an optimal mapping/set of swaps using A*-search and the diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 15302fa6b..85c84ae24 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -71,13 +71,13 @@ void HeuristicMapper::map(const Configuration& configuration) { << "Iterative bidirectional routing (forward pass " << i << "):" << std::endl; } - routeCircuit(false, true); + pseudoRouteCircuit(false); if (config.verbose) { std::clog << std::endl << "Iterative bidirectional routing (backward pass " << i << "):" << std::endl; } - routeCircuit(true, true); + pseudoRouteCircuit(true); if (config.verbose) { std::clog << std::endl << "Main routing:" << std::endl; @@ -308,11 +308,8 @@ void HeuristicMapper::mapToMinDistance(const std::uint16_t source, qc::QuantumComputation::findAndSWAP(target, *pos, qcMapped.outputPermutation); } -void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { - assert(!reverse || - pseudoRouting); // reverse routing is only supported for pseudo routing - - // save original global data for restoring it later if pseudo routing is used +void HeuristicMapper::pseudoRouteCircuit(bool reverse) { + // save original global data for restoring it later const auto originalResults = results; const auto originalLayers = layers; const auto originalSingleQubitMultiplicities = singleQubitMultiplicities; @@ -321,15 +318,10 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { const auto originalActiveQubits1QGates = activeQubits1QGates; const auto originalActiveQubits2QGates = activeQubits2QGates; - auto& config = results.config; - if (pseudoRouting) { - config.dataLoggingPath = ""; // disable data logging for pseudo routing - config.debug = false; - } - - std::size_t gateidx = 0; - std::vector gatesToAdjust{}; - results.output.gates = 0U; + auto& config = results.config; + config.dataLoggingPath = ""; // disable data logging for pseudo routing + config.debug = false; + for (std::size_t i = 0; i < layers.size(); ++i) { auto layerIndex = (reverse ? layers.size() - i - 1 : i); const Node result = aStarMap(layerIndex, reverse); @@ -341,9 +333,33 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { printLocations(std::clog); printQubits(std::clog); } + } + + // restore original global data + results = originalResults; + layers = originalLayers; + singleQubitMultiplicities = originalSingleQubitMultiplicities; + twoQubitMultiplicities = originalTwoQubitMultiplicities; + activeQubits = originalActiveQubits; + activeQubits1QGates = originalActiveQubits1QGates; + activeQubits2QGates = originalActiveQubits2QGates; +} - if (pseudoRouting) { - continue; +void HeuristicMapper::routeCircuit() { + auto& config = results.config; + + std::size_t gateidx = 0; + std::vector gatesToAdjust{}; + results.output.gates = 0U; + for (std::size_t layerIndex = 0; layerIndex < layers.size(); ++layerIndex) { + const Node result = aStarMap(layerIndex, false); + + qubits = result.qubits; + locations = result.locations; + + if (config.verbose) { + printLocations(std::clog); + printQubits(std::clog); } // initial layer needs no swaps @@ -446,18 +462,6 @@ void HeuristicMapper::routeCircuit(bool reverse, bool pseudoRouting) { } } - if (pseudoRouting) { - // restore original global data - results = originalResults; - layers = originalLayers; - singleQubitMultiplicities = originalSingleQubitMultiplicities; - twoQubitMultiplicities = originalTwoQubitMultiplicities; - activeQubits = originalActiveQubits; - activeQubits1QGates = originalActiveQubits1QGates; - activeQubits2QGates = originalActiveQubits2QGates; - return; - } - // infer output permutation from qubit locations qcMapped.outputPermutation.clear(); for (std::size_t i = 0U; i < architecture->getNqubits(); ++i) { From 2def4ffdd6758026f72bfdee56055a70b48c788c Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 14 Dec 2023 02:39:44 +0100 Subject: [PATCH 072/108] add test for invalid settings --- src/heuristic/HeuristicMapper.cpp | 33 ++++++++++++------------------ test/test_heuristic.cpp | 34 ++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 85c84ae24..731b741d9 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -5,6 +5,8 @@ #include "heuristic/HeuristicMapper.hpp" +#include "utils.hpp" + #include void HeuristicMapper::map(const Configuration& configuration) { @@ -17,35 +19,26 @@ void HeuristicMapper::map(const Configuration& configuration) { auto& config = results.config; if (config.layering == Layering::OddGates || config.layering == Layering::QubitTriangle) { - std::cerr << "Layering strategy " << toString(config.layering) - << " not suitable for heuristic mapper!" << std::endl; - return; + throw QMAPException("Layering strategy " + toString(config.layering) + + " not suitable for heuristic mapper!"); } if (config.considerFidelity && !architecture->isFidelityAvailable()) { - std::cerr << "No calibration data available for this architecture!" - << std::endl; - return; + throw QMAPException("No calibration data available for this architecture!"); } if (config.considerFidelity && config.lookahead) { - std::cerr << "Lookahead is not yet supported for heuristic mapper using " - "fidelity-aware mapping!" - << std::endl; - return; + throw QMAPException("Lookahead is not yet supported for heuristic mapper " + "using fidelity-aware mapping!"); } if (config.considerFidelity && config.initialLayout == InitialLayout::Dynamic) { - std::cerr << "Initial layout strategy " << toString(config.initialLayout) - << " not yet supported for heuristic mapper using fidelity-aware " - "mapping!" - << std::endl; - return; + throw QMAPException("Initial layout strategy " + + toString(config.initialLayout) + + " not yet supported for heuristic mapper using " + "fidelity-aware mapping!"); } if (config.considerFidelity && config.teleportationQubits > 0) { - std::cerr - << "Teleportation is not yet supported for heuristic mapper using " - "fidelity-aware mapping!." - << std::endl; - return; + throw QMAPException("Teleportation is not yet supported for heuristic " + "mapper using fidelity-aware mapping!"); } const auto start = std::chrono::steady_clock::now(); initResults(); diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 41122ff3e..cc5726b5d 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -165,6 +165,34 @@ TEST(Functionality, EmptyDump) { EXPECT_THROW(mapper.dumpResult("test.dummy"), QMAPException); } +TEST(Functionality, InvalidSettings) { + qc::QuantumComputation qc{1}; + qc.x(0); + Architecture arch{1, {}}; + auto props = Architecture::Properties(); + props.setSingleQubitErrorRate(0, "x", 0.1); + arch.loadProperties(props); + HeuristicMapper mapper(qc, arch); + auto config = Configuration{}; + config.method = Method::Heuristic; + config.layering = Layering::OddGates; + EXPECT_THROW(mapper.map(config), QMAPException); + config.layering = Layering::QubitTriangle; + EXPECT_THROW(mapper.map(config), QMAPException); + config.layering = Layering::IndividualGates; + config.considerFidelity = true; + config.lookahead = true; + EXPECT_THROW(mapper.map(config), QMAPException); + config.lookahead = false; + config.initialLayout = InitialLayout::Dynamic; + EXPECT_THROW(mapper.map(config), QMAPException); + config.initialLayout = InitialLayout::Static; + config.teleportationQubits = 2; + EXPECT_THROW(mapper.map(config), QMAPException); + config.teleportationQubits = 0; + EXPECT_NO_THROW(mapper.map(config)); +} + TEST(Functionality, NoMeasurmentsAdded) { using namespace qc::literals; // construct circuit @@ -885,11 +913,7 @@ TEST_P(HeuristicTestFidelity, NoFidelity) { settings.initialLayout = InitialLayout::Static; settings.considerFidelity = true; settings.lookahead = false; - nonFidelityMapper->map(settings); - nonFidelityMapper->dumpResult(GetParam() + - "_heuristic_london_nofidelity.qasm"); - nonFidelityMapper->printResult(std::cout); - SUCCEED() << "Mapping successful"; + EXPECT_THROW(nonFidelityMapper->map(settings), QMAPException); } TEST(HeuristicTestFidelity, RemapSingleQubit) { From 12560a69de2547171420a0be3a12e42b02ebc083 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 14 Dec 2023 02:43:55 +0100 Subject: [PATCH 073/108] use iterative bidirectional routing in 5Q tests --- test/test_heuristic.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index cc5726b5d..d0e1d1571 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -671,6 +671,10 @@ class HeuristicTest5Q : public testing::TestWithParam { ibmqYorktownMapper = std::make_unique(qc, ibmqYorktown); ibmqLondonMapper = std::make_unique(qc, ibmqLondon); settings.debug = true; + settings.verbose = true; + + settings.iterativeBidirectionalRouting = true; + settings.iterativeBidirectionalRoutingPasses = 3; } }; From 55ae55b9bf631c4cbfa7a64eb7ceacec746a9551 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 02:18:23 +0000 Subject: [PATCH 074/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/heuristic/HeuristicMapper.hpp | 10 +++--- src/Architecture.cpp | 5 ++- src/heuristic/HeuristicMapper.cpp | 8 ++--- .../visualization/visualize_search_graph.py | 34 ++++++++++--------- test/test_heuristic.cpp | 20 +++++------ 5 files changed, 39 insertions(+), 38 deletions(-) diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index ef9c0837f..9e4e4262c 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -227,13 +227,13 @@ class HeuristicMapper : public Mapper { * constraints and optimize fidelity if activated */ void routeCircuit(); - + /** - * @brief Performes pseudo-routing on the input circuit, i.e. rearranges the - * qubit layout layer by layer to meet topology constraints without actually - * inserting SWAPs (leaves all global data unchanged except for `qubits` and + * @brief Performes pseudo-routing on the input circuit, i.e. rearranges the + * qubit layout layer by layer to meet topology constraints without actually + * inserting SWAPs (leaves all global data unchanged except for `qubits` and * `locations`, which hold the final layout) - * + * * used for iterative bidirectional routing * * @param reverse if true, the circuit is routed from the end to the beginning diff --git a/src/Architecture.cpp b/src/Architecture.cpp index 23ee38a35..23cec6287 100644 --- a/src/Architecture.cpp +++ b/src/Architecture.cpp @@ -186,9 +186,8 @@ Architecture::Architecture(const std::uint16_t nQ, const CouplingMap& cm, void Architecture::createDistanceTable() { isBidirectional = true; - Matrix edgeWeights( - nqubits, - std::vector(nqubits, std::numeric_limits::max())); + Matrix edgeWeights(nqubits, std::vector( + nqubits, std::numeric_limits::max())); for (const auto& edge : couplingMap) { if (couplingMap.find({edge.second, edge.first}) == couplingMap.end()) { isBidirectional = false; diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 731b741d9..51efc16ec 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -20,7 +20,7 @@ void HeuristicMapper::map(const Configuration& configuration) { if (config.layering == Layering::OddGates || config.layering == Layering::QubitTriangle) { throw QMAPException("Layering strategy " + toString(config.layering) + - " not suitable for heuristic mapper!"); + " not suitable for heuristic mapper!"); } if (config.considerFidelity && !architecture->isFidelityAvailable()) { throw QMAPException("No calibration data available for this architecture!"); @@ -314,7 +314,7 @@ void HeuristicMapper::pseudoRouteCircuit(bool reverse) { auto& config = results.config; config.dataLoggingPath = ""; // disable data logging for pseudo routing config.debug = false; - + for (std::size_t i = 0; i < layers.size(); ++i) { auto layerIndex = (reverse ? layers.size() - i - 1 : i); const Node result = aStarMap(layerIndex, reverse); @@ -327,7 +327,7 @@ void HeuristicMapper::pseudoRouteCircuit(bool reverse) { printQubits(std::clog); } } - + // restore original global data results = originalResults; layers = originalLayers; @@ -345,7 +345,7 @@ void HeuristicMapper::routeCircuit() { std::vector gatesToAdjust{}; results.output.gates = 0U; for (std::size_t layerIndex = 0; layerIndex < layers.size(); ++layerIndex) { - const Node result = aStarMap(layerIndex, false); + const Node result = aStarMap(layerIndex, false); qubits = result.qubits; locations = result.locations; diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index abdb6a2ee..e2d149b1a 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -374,7 +374,7 @@ def _prepare_search_graph_scatter_data( stem_x.extend((nx, nx, None)) stem_y.extend((ny, ny, None)) stem_z.extend((min_nz, max_nz, None)) - if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: # noqa: PLR0916 + if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: min_x = nx max_x = nx min_y = ny @@ -411,7 +411,7 @@ def _prepare_search_graph_scatter_data( else: node_color[i].append(curr_color) - if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: # noqa: PLR0916 + if min_x is None or max_x is None or min_y is None or max_y is None or min_z is None or max_z is None: msg = "No nodes in search graph." raise ValueError(msg) @@ -599,22 +599,24 @@ def _draw_swap_arrows( sw0_considered = layout[sw[0]] in considered_qubit_colors sw1_considered = layout[sw[1]] in considered_qubit_colors if shared_swaps and sw0_considered and sw1_considered: - props_list.append( - { - "color": considered_qubit_colors[layout[sw[0]]], - "straight": sw[0] < sw[1], - "color2": considered_qubit_colors[layout[sw[1]]], - } - ) + props_list.append({ + "color": considered_qubit_colors[layout[sw[0]]], + "straight": sw[0] < sw[1], + "color2": considered_qubit_colors[layout[sw[1]]], + }) else: if sw0_considered: - props_list.append( - {"color": considered_qubit_colors[layout[sw[0]]], "straight": sw[0] < sw[1], "color2": None} - ) + props_list.append({ + "color": considered_qubit_colors[layout[sw[0]]], + "straight": sw[0] < sw[1], + "color2": None, + }) if sw1_considered: - props_list.append( - {"color": considered_qubit_colors[layout[sw[1]]], "straight": sw[0] >= sw[1], "color2": None} - ) + props_list.append({ + "color": considered_qubit_colors[layout[sw[1]]], + "straight": sw[0] >= sw[1], + "color2": None, + }) layout[sw[0]], layout[sw[1]] = layout[sw[1]], layout[sw[0]] list_of_arch_arrows = [] @@ -920,7 +922,7 @@ def _lookahead_penalty_lambda(n: SearchNode) -> float: def _cost_string_to_lambda(cost_string: str) -> Callable[[SearchNode], float] | None: - return _cost_string_lambdas.get(cost_string, None) + return _cost_string_lambdas.get(cost_string) default_plotly_settings: _PlotlySettings = { diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index d0e1d1571..33d8a8410 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -168,25 +168,25 @@ TEST(Functionality, EmptyDump) { TEST(Functionality, InvalidSettings) { qc::QuantumComputation qc{1}; qc.x(0); - Architecture arch{1, {}}; - auto props = Architecture::Properties(); + Architecture arch{1, {}}; + auto props = Architecture::Properties(); props.setSingleQubitErrorRate(0, "x", 0.1); arch.loadProperties(props); HeuristicMapper mapper(qc, arch); - auto config = Configuration{}; - config.method = Method::Heuristic; - config.layering = Layering::OddGates; + auto config = Configuration{}; + config.method = Method::Heuristic; + config.layering = Layering::OddGates; EXPECT_THROW(mapper.map(config), QMAPException); config.layering = Layering::QubitTriangle; EXPECT_THROW(mapper.map(config), QMAPException); - config.layering = Layering::IndividualGates; + config.layering = Layering::IndividualGates; config.considerFidelity = true; - config.lookahead = true; + config.lookahead = true; EXPECT_THROW(mapper.map(config), QMAPException); - config.lookahead = false; + config.lookahead = false; config.initialLayout = InitialLayout::Dynamic; EXPECT_THROW(mapper.map(config), QMAPException); - config.initialLayout = InitialLayout::Static; + config.initialLayout = InitialLayout::Static; config.teleportationQubits = 2; EXPECT_THROW(mapper.map(config), QMAPException); config.teleportationQubits = 0; @@ -672,7 +672,7 @@ class HeuristicTest5Q : public testing::TestWithParam { ibmqLondonMapper = std::make_unique(qc, ibmqLondon); settings.debug = true; settings.verbose = true; - + settings.iterativeBidirectionalRouting = true; settings.iterativeBidirectionalRoutingPasses = 3; } From 4c624e74f2efd1a2ad3c929d8d35c0e3529be165 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 14 Dec 2023 03:21:00 +0100 Subject: [PATCH 075/108] fix typo in doc string --- include/heuristic/HeuristicMapper.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/heuristic/HeuristicMapper.hpp b/include/heuristic/HeuristicMapper.hpp index 9e4e4262c..8df353fc4 100644 --- a/include/heuristic/HeuristicMapper.hpp +++ b/include/heuristic/HeuristicMapper.hpp @@ -229,7 +229,7 @@ class HeuristicMapper : public Mapper { void routeCircuit(); /** - * @brief Performes pseudo-routing on the input circuit, i.e. rearranges the + * @brief Performs pseudo-routing on the input circuit, i.e. rearranges the * qubit layout layer by layer to meet topology constraints without actually * inserting SWAPs (leaves all global data unchanged except for `qubits` and * `locations`, which hold the final layout) From 12359174a221b0cbf647f6244f18453042f9877d Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 14 Dec 2023 16:42:10 +0100 Subject: [PATCH 076/108] add test DataLoggerAfterClose --- include/DataLogger.hpp | 6 +++ src/DataLogger.cpp | 5 +++ test/test_heuristic.cpp | 87 ++++++++++++++++++++++++++++------------- 3 files changed, 71 insertions(+), 27 deletions(-) diff --git a/include/DataLogger.hpp b/include/DataLogger.hpp index 544a3fdee..417214cab 100644 --- a/include/DataLogger.hpp +++ b/include/DataLogger.hpp @@ -54,9 +54,15 @@ class DataLogger { 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(); diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index 8b427a9e6..9d30bee7d 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -157,6 +157,10 @@ void DataLogger::logFinalizeLayer( }; void DataLogger::splitLayer() { + if (deactivated) { + return; + } + const std::size_t layerIndex = searchNodesLogFiles.size() - 1; if (searchNodesLogFiles.at(layerIndex).is_open()) { std::cerr << "[data-logging] Error: layer " << layerIndex @@ -273,4 +277,5 @@ void DataLogger::close() { searchNodesLogFiles.at(i).close(); } } + deactivated = true; } diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 33d8a8410..aed428855 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -279,6 +279,33 @@ TEST(Functionality, HeuristicAdmissibility) { } } +TEST(Functionality, DataLoggerAfterClose) { + std::string dataLoggingPath = "test_log/"; + qc::QuantumComputation qc{1}; + qc.x(0); + Architecture arch{1, {}}; + std::unique_ptr dataLogger = std::make_unique(dataLoggingPath, arch, qc); + dataLogger->clearLog(); + dataLogger->close(); + + dataLogger->logArchitecture(); + dataLogger->logInputCircuit(qc); + dataLogger->logOutputCircuit(qc); + dataLogger->logSearchNode(0, 0, 0, 0., 0., 0., {}, false, {}, 0); + qc::CompoundOperation compOp(0); + dataLogger->logFinalizeLayer(0, compOp, {}, {}, {}, 0, 0., 0., 0., {}, {}, 0); + dataLogger->splitLayer(); + MappingResults result; + dataLogger->logMappingResult(result); + + // count files and subdirectories in data logging path + std::size_t fileCount = 0; + for ([[maybe_unused]] const auto& _ : std::filesystem::directory_iterator(dataLoggingPath)) { + ++fileCount; + } + EXPECT_EQ(fileCount, 0); +} + TEST(Functionality, DataLogger) { // setting up example architecture and circuit Architecture architecture{}; @@ -304,6 +331,7 @@ TEST(Functionality, DataLogger) { qc::QuantumComputation qc{4, 4}; qc.cx(1, 0); qc.cx(3, 2); + qc.barrier({0, 1, 2, 3}); qc.setName("test_circ"); Configuration settings{}; @@ -320,7 +348,12 @@ TEST(Functionality, DataLogger) { settings.considerFidelity = false; settings.useTeleportation = false; // setting data logging path to enable data logging - settings.dataLoggingPath = "test_log/"; + settings.dataLoggingPath = "test_log"; + + // remove directory at data logging path if it already exists + if (std::filesystem::exists(settings.dataLoggingPath)) { + std::filesystem::remove_all(settings.dataLoggingPath); + } auto mapper = std::make_unique(qc, architecture); mapper->map(settings); @@ -328,10 +361,10 @@ TEST(Functionality, DataLogger) { MappingResults& results = mapper->getResults(); // comparing logged architecture information with original architecture object - auto archFile = std::ifstream(settings.dataLoggingPath + "architecture.json"); + auto archFile = std::ifstream(settings.dataLoggingPath + "/architecture.json"); if (!archFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath - << "architecture.json"; + << "/architecture.json"; } const auto archJson = nlohmann::json::parse(archFile); EXPECT_EQ(archJson["name"], architecture.getName()); @@ -382,10 +415,10 @@ TEST(Functionality, DataLogger) { // comparing logged mapping result with mapping result object auto resultFile = - std::ifstream(settings.dataLoggingPath + "mapping_result.json"); + std::ifstream(settings.dataLoggingPath + "/mapping_result.json"); if (!resultFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath - << "mapping_result.json"; + << "/mapping_result.json"; } const auto resultJson = nlohmann::json::parse(resultFile); const auto& configJson = resultJson["config"]; @@ -476,10 +509,10 @@ TEST(Functionality, DataLogger) { // comparing logged input and output circuits with input circuit object and // mapped circuit object - auto inputQasmFile = std::ifstream(settings.dataLoggingPath + "input.qasm"); + auto inputQasmFile = std::ifstream(settings.dataLoggingPath + "/input.qasm"); if (!inputQasmFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath - << "input.qasm"; + << "/input.qasm"; } std::stringstream inputFileBuffer; inputFileBuffer << inputQasmFile.rdbuf(); @@ -487,10 +520,10 @@ TEST(Functionality, DataLogger) { qc.dumpOpenQASM(inputQasmBuffer); EXPECT_EQ(inputFileBuffer.str(), inputQasmBuffer.str()); - auto outputQasmFile = std::ifstream(settings.dataLoggingPath + "output.qasm"); + auto outputQasmFile = std::ifstream(settings.dataLoggingPath + "/output.qasm"); if (!outputQasmFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath - << "output.qasm"; + << "/output.qasm"; } std::stringstream outputFileBuffer; outputFileBuffer << outputQasmFile.rdbuf(); @@ -501,10 +534,10 @@ TEST(Functionality, DataLogger) { // checking logged search graph info against known values (correct qubit // number, valid layouts, correct data types in all csv fields, etc.) for (std::size_t i = 0; i < results.input.layers; ++i) { - auto layerFile = std::ifstream(settings.dataLoggingPath + "layer_" + + auto layerFile = std::ifstream(settings.dataLoggingPath + "/layer_" + std::to_string(i) + ".json"); if (!layerFile.is_open()) { - FAIL() << "Could not open file " << settings.dataLoggingPath << "layer_" + FAIL() << "Could not open file " << settings.dataLoggingPath << "/layer_" << i << ".json"; } const auto layerJson = nlohmann::json::parse(layerFile); @@ -514,10 +547,10 @@ TEST(Functionality, DataLogger) { architecture.getNqubits()); auto layerNodeFile = std::ifstream( - settings.dataLoggingPath + "nodes_layer_" + std::to_string(i) + ".csv"); + settings.dataLoggingPath + "/nodes_layer_" + std::to_string(i) + ".csv"); if (!layerNodeFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "/nodes_layer_" << i << ".csv"; } std::string line; bool foundFinalNode = false; @@ -542,7 +575,7 @@ TEST(Functionality, DataLogger) { nodeIds.insert(nodeId); } else { FAIL() << "Missing value for node id in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "/nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { parentId = std::stoull(col); @@ -551,42 +584,42 @@ TEST(Functionality, DataLogger) { } } else { FAIL() << "Missing value for parent node id in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + << settings.dataLoggingPath << "/nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { costFixed = std::stod(col); } else { FAIL() << "Missing value for fixed cost in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "/nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { costHeur = std::stod(col); } else { FAIL() << "Missing value for heuristic cost in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + << settings.dataLoggingPath << "/nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { lookaheadPenalty = std::stod(col); } else { FAIL() << "Missing value for lookahead penalty in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + << settings.dataLoggingPath << "/nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { isValidMapping = std::stoull(col); if (isValidMapping > 1) { FAIL() << "Non-boolean value " << isValidMapping << " for isValidMapping in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "/nodes_layer_" << i << ".csv"; } } else { FAIL() << "Missing value for isValidMapping in " - << settings.dataLoggingPath << "nodes_layer_" << i << ".csv"; + << settings.dataLoggingPath << "/nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { depth = std::stoull(col); } else { FAIL() << "Missing value for depth in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "/nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { std::stringstream qubitMapBuffer(col); @@ -599,7 +632,7 @@ TEST(Functionality, DataLogger) { EXPECT_EQ(layout.size(), architecture.getNqubits()); } else { FAIL() << "Missing value for layout in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "/nodes_layer_" << i << ".csv"; } if (std::getline(lineStream, col, ';')) { std::stringstream swapBuffer(col); @@ -627,24 +660,24 @@ TEST(Functionality, DataLogger) { } if (!foundFinalNode) { FAIL() << "Could not find final node in " << settings.dataLoggingPath - << "nodes_layer_" << i << ".csv"; + << "/nodes_layer_" << i << ".csv"; } } // checking if files for non-existing layers are not created auto afterLastLayerFile = - std::ifstream(settings.dataLoggingPath + "layer_" + + std::ifstream(settings.dataLoggingPath + "/layer_" + std::to_string(results.input.layers) + ".json"); if (afterLastLayerFile.is_open()) { - FAIL() << "File " << settings.dataLoggingPath << "layer_" + FAIL() << "File " << settings.dataLoggingPath << "/layer_" << results.input.layers << ".json should not exist, as there are not that many layers"; } auto afterLastLayerNodesFile = - std::ifstream(settings.dataLoggingPath + "nodes_layer_" + + std::ifstream(settings.dataLoggingPath + "/nodes_layer_" + std::to_string(results.input.layers) + ".csv"); if (afterLastLayerNodesFile.is_open()) { - FAIL() << "File " << settings.dataLoggingPath << "nodes_layer_" + FAIL() << "File " << settings.dataLoggingPath << "/nodes_layer_" << results.input.layers << ".csv should not exist, as there are not that many layers"; } From 0bbf301b582b10934a40427d3f33cd081e832e7c Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 14 Dec 2023 17:00:19 +0100 Subject: [PATCH 077/108] add test FidelityDistanceNoFidelity --- test/test_architecture.cpp | 38 +++++++++++++++++++++++++++++++++++--- test/test_heuristic.cpp | 1 - 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/test/test_architecture.cpp b/test/test_architecture.cpp index ffbfe1bff..55273bceb 100644 --- a/test/test_architecture.cpp +++ b/test/test_architecture.cpp @@ -480,6 +480,9 @@ TEST(TestArchitecture, FidelityDistanceBidirectionalTest) { matrixNear(architecture.getFidelityDistanceTable(5), zeroMatrix, 1e-6)); EXPECT_TRUE( matrixNear(architecture.getFidelityDistanceTable(6), zeroMatrix, 1e-6)); + + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 7)), QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(7, 0)), QMAPException); } TEST(TestArchitecture, FidelityDistanceSemiBidirectionalTest) { @@ -743,9 +746,7 @@ TEST(TestArchitecture, FidelityDistanceSemiBidirectionalTest) { TEST(TestArchitecture, FidelitySwapCostTest) { const double tolerance = 1e-6; - Architecture architecture{}; const CouplingMap cm = {{0, 1}, {1, 2}, {2, 1}, {2, 3}, {2, 4}, {4, 2}}; - architecture.loadCouplingMap(5, cm); auto props = Architecture::Properties(); props.setSingleQubitErrorRate(0, "x", 0.11); @@ -761,7 +762,7 @@ TEST(TestArchitecture, FidelitySwapCostTest) { props.setTwoQubitErrorRate(2, 4, 0.4); props.setTwoQubitErrorRate(4, 2, 0.4); - architecture.loadProperties(props); + Architecture architecture(5, cm, props); const Matrix swapFidCost = architecture.getSwapFidelityCosts(); @@ -803,6 +804,12 @@ TEST(TestArchitecture, FidelitySwapCostTest) { EXPECT_GT(swapFidCost[4][1], 1e20); EXPECT_NEAR(swapFidCost[4][2], -3 * std::log2(1 - 0.4), tolerance); EXPECT_GT(swapFidCost[4][3], 1e20); + + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCost(5)), QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(5, 0)), QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(0, 5)), QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(5, 0)), QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(0, 5)), QMAPException); } TEST(TestArchitecture, FidelityDistanceCheapestPathTest) { @@ -849,6 +856,31 @@ TEST(TestArchitecture, FidelityDistanceCheapestPathTest) { -3 * 3 * std::log2(1 - 0.1) - 2 * 2 * std::log2(1 - 0.1), 1e-6); } +TEST(TestArchitecture, FidelityDistanceNoFidelity) { + Architecture architecture(4, {{0, 1}, {1, 2}, {1, 3}}); + + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable()), QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(0)), QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(1)), QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(2)), QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(3)), QMAPException); + + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2)), QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 0)), QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 1)), QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 2)), QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 3)), QMAPException); + + EXPECT_THROW(static_cast(architecture.getFidelityTable()), QMAPException); + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelities()), QMAPException); + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCosts()), QMAPException); + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCost(0)), QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCosts()), QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(0, 1)), QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCosts()), QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(0, 1)), QMAPException); +} + TEST(TestArchitecture, DistanceCheapestPathTest) { // tests if the distance measure actually finds the cheapest path and // not just the shortest diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index aed428855..a49d6e3f6 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -331,7 +331,6 @@ TEST(Functionality, DataLogger) { qc::QuantumComputation qc{4, 4}; qc.cx(1, 0); qc.cx(3, 2); - qc.barrier({0, 1, 2, 3}); qc.setName("test_circ"); Configuration settings{}; From 5f242bc6ad66607bfd4fb118c40db4820b189e81 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:08:33 +0000 Subject: [PATCH 078/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DataLogger.cpp | 2 +- test/test_architecture.cpp | 87 ++++++++++++++++++++++++-------------- test/test_heuristic.cpp | 27 +++++++----- 3 files changed, 73 insertions(+), 43 deletions(-) diff --git a/src/DataLogger.cpp b/src/DataLogger.cpp index 9d30bee7d..e20c5d2e1 100644 --- a/src/DataLogger.cpp +++ b/src/DataLogger.cpp @@ -160,7 +160,7 @@ void DataLogger::splitLayer() { if (deactivated) { return; } - + const std::size_t layerIndex = searchNodesLogFiles.size() - 1; if (searchNodesLogFiles.at(layerIndex).is_open()) { std::cerr << "[data-logging] Error: layer " << layerIndex diff --git a/test/test_architecture.cpp b/test/test_architecture.cpp index 55273bceb..441bb0627 100644 --- a/test/test_architecture.cpp +++ b/test/test_architecture.cpp @@ -480,9 +480,11 @@ TEST(TestArchitecture, FidelityDistanceBidirectionalTest) { matrixNear(architecture.getFidelityDistanceTable(5), zeroMatrix, 1e-6)); EXPECT_TRUE( matrixNear(architecture.getFidelityDistanceTable(6), zeroMatrix, 1e-6)); - - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 7)), QMAPException); - EXPECT_THROW(static_cast(architecture.fidelityDistance(7, 0)), QMAPException); + + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 7)), + QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(7, 0)), + QMAPException); } TEST(TestArchitecture, FidelityDistanceSemiBidirectionalTest) { @@ -762,7 +764,7 @@ TEST(TestArchitecture, FidelitySwapCostTest) { props.setTwoQubitErrorRate(2, 4, 0.4); props.setTwoQubitErrorRate(4, 2, 0.4); - Architecture architecture(5, cm, props); + Architecture architecture(5, cm, props); const Matrix swapFidCost = architecture.getSwapFidelityCosts(); @@ -804,12 +806,17 @@ TEST(TestArchitecture, FidelitySwapCostTest) { EXPECT_GT(swapFidCost[4][1], 1e20); EXPECT_NEAR(swapFidCost[4][2], -3 * std::log2(1 - 0.4), tolerance); EXPECT_GT(swapFidCost[4][3], 1e20); - - EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCost(5)), QMAPException); - EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(5, 0)), QMAPException); - EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(0, 5)), QMAPException); - EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(5, 0)), QMAPException); - EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(0, 5)), QMAPException); + + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCost(5)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(5, 0)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(0, 5)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(5, 0)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(0, 5)), + QMAPException); } TEST(TestArchitecture, FidelityDistanceCheapestPathTest) { @@ -858,27 +865,45 @@ TEST(TestArchitecture, FidelityDistanceCheapestPathTest) { TEST(TestArchitecture, FidelityDistanceNoFidelity) { Architecture architecture(4, {{0, 1}, {1, 2}, {1, 3}}); - - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable()), QMAPException); - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(0)), QMAPException); - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(1)), QMAPException); - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(2)), QMAPException); - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(3)), QMAPException); - - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2)), QMAPException); - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 0)), QMAPException); - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 1)), QMAPException); - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 2)), QMAPException); - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 3)), QMAPException); - - EXPECT_THROW(static_cast(architecture.getFidelityTable()), QMAPException); - EXPECT_THROW(static_cast(architecture.getSingleQubitFidelities()), QMAPException); - EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCosts()), QMAPException); - EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCost(0)), QMAPException); - EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCosts()), QMAPException); - EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(0, 1)), QMAPException); - EXPECT_THROW(static_cast(architecture.getSwapFidelityCosts()), QMAPException); - EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(0, 1)), QMAPException); + + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(0)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(1)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(2)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(3)), + QMAPException); + + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2)), + QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 0)), + QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 1)), + QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 2)), + QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 3)), + QMAPException); + + EXPECT_THROW(static_cast(architecture.getFidelityTable()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelities()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCosts()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCost(0)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCosts()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(0, 1)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCosts()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(0, 1)), + QMAPException); } TEST(TestArchitecture, DistanceCheapestPathTest) { diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index a49d6e3f6..8ec150547 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -280,14 +280,15 @@ TEST(Functionality, HeuristicAdmissibility) { } TEST(Functionality, DataLoggerAfterClose) { - std::string dataLoggingPath = "test_log/"; + std::string dataLoggingPath = "test_log/"; qc::QuantumComputation qc{1}; qc.x(0); - Architecture arch{1, {}}; - std::unique_ptr dataLogger = std::make_unique(dataLoggingPath, arch, qc); + Architecture arch{1, {}}; + std::unique_ptr dataLogger = + std::make_unique(dataLoggingPath, arch, qc); dataLogger->clearLog(); dataLogger->close(); - + dataLogger->logArchitecture(); dataLogger->logInputCircuit(qc); dataLogger->logOutputCircuit(qc); @@ -297,10 +298,11 @@ TEST(Functionality, DataLoggerAfterClose) { dataLogger->splitLayer(); MappingResults result; dataLogger->logMappingResult(result); - + // count files and subdirectories in data logging path std::size_t fileCount = 0; - for ([[maybe_unused]] const auto& _ : std::filesystem::directory_iterator(dataLoggingPath)) { + for ([[maybe_unused]] const auto& _ : + std::filesystem::directory_iterator(dataLoggingPath)) { ++fileCount; } EXPECT_EQ(fileCount, 0); @@ -348,7 +350,7 @@ TEST(Functionality, DataLogger) { settings.useTeleportation = false; // setting data logging path to enable data logging settings.dataLoggingPath = "test_log"; - + // remove directory at data logging path if it already exists if (std::filesystem::exists(settings.dataLoggingPath)) { std::filesystem::remove_all(settings.dataLoggingPath); @@ -360,7 +362,8 @@ TEST(Functionality, DataLogger) { MappingResults& results = mapper->getResults(); // comparing logged architecture information with original architecture object - auto archFile = std::ifstream(settings.dataLoggingPath + "/architecture.json"); + auto archFile = + std::ifstream(settings.dataLoggingPath + "/architecture.json"); if (!archFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << "/architecture.json"; @@ -519,7 +522,8 @@ TEST(Functionality, DataLogger) { qc.dumpOpenQASM(inputQasmBuffer); EXPECT_EQ(inputFileBuffer.str(), inputQasmBuffer.str()); - auto outputQasmFile = std::ifstream(settings.dataLoggingPath + "/output.qasm"); + auto outputQasmFile = + std::ifstream(settings.dataLoggingPath + "/output.qasm"); if (!outputQasmFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << "/output.qasm"; @@ -545,8 +549,9 @@ TEST(Functionality, DataLogger) { EXPECT_EQ(layerJson["single_qubit_multiplicity"].size(), architecture.getNqubits()); - auto layerNodeFile = std::ifstream( - settings.dataLoggingPath + "/nodes_layer_" + std::to_string(i) + ".csv"); + auto layerNodeFile = + std::ifstream(settings.dataLoggingPath + "/nodes_layer_" + + std::to_string(i) + ".csv"); if (!layerNodeFile.is_open()) { FAIL() << "Could not open file " << settings.dataLoggingPath << "/nodes_layer_" << i << ".csv"; From cd784bdc19be2fe72cbc39106da36d276e17ea02 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 14 Dec 2023 17:52:09 +0100 Subject: [PATCH 079/108] fix lint warnings --- include/Architecture.hpp | 2 +- include/utils.hpp | 8 ++++---- src/Mapper.cpp | 2 +- test/test_architecture.cpp | 6 +++--- test/test_heuristic.cpp | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/Architecture.hpp b/include/Architecture.hpp index 5a9a2851d..69bcbe73a 100644 --- a/include/Architecture.hpp +++ b/include/Architecture.hpp @@ -275,7 +275,7 @@ class Architecture { throw QMAPException("No fidelity data available."); } if (skipEdges >= fidelityDistanceTables.size()) { - static Matrix defaultMatrix(nqubits, std::vector(nqubits, 0.0)); + const static Matrix defaultMatrix(nqubits, std::vector(nqubits, 0.0)); return defaultMatrix; } return fidelityDistanceTables.at(skipEdges); diff --git a/include/utils.hpp b/include/utils.hpp index 26404a014..75d019299 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -90,9 +90,9 @@ class Dijkstra { * @param removeLastEdge if set to true, the cost for the last swap on any * path is removed (i.e. distance is given for moving "next to" qubit) */ - static void buildTable(const std::uint16_t n, const CouplingMap& couplingMap, + static void buildTable(std::uint16_t n, const CouplingMap& couplingMap, Matrix& distanceTable, const Matrix& edgeWeights, - const double reversalCost, const bool removeLastEdge); + double reversalCost, bool removeLastEdge); /** * @brief builds a 3d matrix containing the distance tables giving the minimal * distances between 2 qubit when upto k edges can be skipped. @@ -117,8 +117,8 @@ class Dijkstra { protected: static void dijkstra(const CouplingMap& couplingMap, std::vector& nodes, - const std::uint16_t start, const Matrix& edgeWeights, - const double reversalCost); + std::uint16_t start, const Matrix& edgeWeights, + double reversalCost); struct NodeComparator { bool operator()(const Node* x, const Node* y) { diff --git a/src/Mapper.cpp b/src/Mapper.cpp index 26ede7570..7c61d6007 100644 --- a/src/Mapper.cpp +++ b/src/Mapper.cpp @@ -270,7 +270,7 @@ void Mapper::splitLayer(std::size_t index, Architecture& arch) { // 2Q-gates bool even = false; - for (const auto edge : twoQubitMultiplicity) { + for (const auto& edge : twoQubitMultiplicity) { if (even) { twoQubitMultiplicity0.insert(edge); activeQubits0.emplace(edge.first.first); diff --git a/test/test_architecture.cpp b/test/test_architecture.cpp index 55273bceb..37f3360c4 100644 --- a/test/test_architecture.cpp +++ b/test/test_architecture.cpp @@ -762,9 +762,9 @@ TEST(TestArchitecture, FidelitySwapCostTest) { props.setTwoQubitErrorRate(2, 4, 0.4); props.setTwoQubitErrorRate(4, 2, 0.4); - Architecture architecture(5, cm, props); + const Architecture architecture(5, cm, props); - const Matrix swapFidCost = architecture.getSwapFidelityCosts(); + const Matrix& swapFidCost = architecture.getSwapFidelityCosts(); EXPECT_EQ(swapFidCost.size(), 5); EXPECT_EQ(swapFidCost[0].size(), 5); @@ -857,7 +857,7 @@ TEST(TestArchitecture, FidelityDistanceCheapestPathTest) { } TEST(TestArchitecture, FidelityDistanceNoFidelity) { - Architecture architecture(4, {{0, 1}, {1, 2}, {1, 3}}); + const Architecture architecture(4, {{0, 1}, {1, 2}, {1, 3}}); EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable()), QMAPException); EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(0)), QMAPException); diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index a49d6e3f6..ad61e1c52 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -280,7 +280,7 @@ TEST(Functionality, HeuristicAdmissibility) { } TEST(Functionality, DataLoggerAfterClose) { - std::string dataLoggingPath = "test_log/"; + const std::string dataLoggingPath = "test_log/"; qc::QuantumComputation qc{1}; qc.x(0); Architecture arch{1, {}}; @@ -292,7 +292,7 @@ TEST(Functionality, DataLoggerAfterClose) { dataLogger->logInputCircuit(qc); dataLogger->logOutputCircuit(qc); dataLogger->logSearchNode(0, 0, 0, 0., 0., 0., {}, false, {}, 0); - qc::CompoundOperation compOp(0); + const qc::CompoundOperation compOp(0); dataLogger->logFinalizeLayer(0, compOp, {}, {}, {}, 0, 0., 0., 0., {}, {}, 0); dataLogger->splitLayer(); MappingResults result; From d6f41262761ce3d34a0fe2736ef3b087bfbaf1b0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:54:48 +0000 Subject: [PATCH 080/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/Architecture.hpp | 3 +- test/test_architecture.cpp | 60 +++++++++++++++++++++++++------------- test/test_heuristic.cpp | 2 +- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/include/Architecture.hpp b/include/Architecture.hpp index 69bcbe73a..a7f5086ae 100644 --- a/include/Architecture.hpp +++ b/include/Architecture.hpp @@ -275,7 +275,8 @@ class Architecture { throw QMAPException("No fidelity data available."); } if (skipEdges >= fidelityDistanceTables.size()) { - const static Matrix defaultMatrix(nqubits, std::vector(nqubits, 0.0)); + const static Matrix defaultMatrix(nqubits, + std::vector(nqubits, 0.0)); return defaultMatrix; } return fidelityDistanceTables.at(skipEdges); diff --git a/test/test_architecture.cpp b/test/test_architecture.cpp index e80865ff6..3e54c16fe 100644 --- a/test/test_architecture.cpp +++ b/test/test_architecture.cpp @@ -865,27 +865,45 @@ TEST(TestArchitecture, FidelityDistanceCheapestPathTest) { TEST(TestArchitecture, FidelityDistanceNoFidelity) { const Architecture architecture(4, {{0, 1}, {1, 2}, {1, 3}}); - - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable()), QMAPException); - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(0)), QMAPException); - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(1)), QMAPException); - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(2)), QMAPException); - EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(3)), QMAPException); - - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2)), QMAPException); - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 0)), QMAPException); - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 1)), QMAPException); - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 2)), QMAPException); - EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 3)), QMAPException); - - EXPECT_THROW(static_cast(architecture.getFidelityTable()), QMAPException); - EXPECT_THROW(static_cast(architecture.getSingleQubitFidelities()), QMAPException); - EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCosts()), QMAPException); - EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCost(0)), QMAPException); - EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCosts()), QMAPException); - EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(0, 1)), QMAPException); - EXPECT_THROW(static_cast(architecture.getSwapFidelityCosts()), QMAPException); - EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(0, 1)), QMAPException); + + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(0)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(1)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(2)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getFidelityDistanceTable(3)), + QMAPException); + + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2)), + QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 0)), + QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 1)), + QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 2)), + QMAPException); + EXPECT_THROW(static_cast(architecture.fidelityDistance(0, 2, 3)), + QMAPException); + + EXPECT_THROW(static_cast(architecture.getFidelityTable()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelities()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCosts()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSingleQubitFidelityCost(0)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCosts()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getTwoQubitFidelityCost(0, 1)), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCosts()), + QMAPException); + EXPECT_THROW(static_cast(architecture.getSwapFidelityCost(0, 1)), + QMAPException); } TEST(TestArchitecture, DistanceCheapestPathTest) { diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index f6dac122b..7ac29df71 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -280,7 +280,7 @@ TEST(Functionality, HeuristicAdmissibility) { } TEST(Functionality, DataLoggerAfterClose) { - const std::string dataLoggingPath = "test_log/"; + const std::string dataLoggingPath = "test_log/"; qc::QuantumComputation qc{1}; qc.x(0); Architecture arch{1, {}}; From 4eb984773b4dc18c5dd2a6ae6c8ee44d8557bafe Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 00:42:24 +0100 Subject: [PATCH 081/108] fix case style for DEFAULT_MATRIX --- include/Architecture.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Architecture.hpp b/include/Architecture.hpp index a7f5086ae..98931cb36 100644 --- a/include/Architecture.hpp +++ b/include/Architecture.hpp @@ -275,9 +275,9 @@ class Architecture { throw QMAPException("No fidelity data available."); } if (skipEdges >= fidelityDistanceTables.size()) { - const static Matrix defaultMatrix(nqubits, + const static Matrix DEFAULT_MATRIX(nqubits, std::vector(nqubits, 0.0)); - return defaultMatrix; + return DEFAULT_MATRIX; } return fidelityDistanceTables.at(skipEdges); } From b9fe0ffb2f277f0165fc7a9ead08d2d2d6cd6a78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 23:43:29 +0000 Subject: [PATCH 082/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/Architecture.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Architecture.hpp b/include/Architecture.hpp index 98931cb36..b924e3618 100644 --- a/include/Architecture.hpp +++ b/include/Architecture.hpp @@ -276,7 +276,7 @@ class Architecture { } if (skipEdges >= fidelityDistanceTables.size()) { const static Matrix DEFAULT_MATRIX(nqubits, - std::vector(nqubits, 0.0)); + std::vector(nqubits, 0.0)); return DEFAULT_MATRIX; } return fidelityDistanceTables.at(skipEdges); From 8a99bfae643cc250f279cc4826789a79e4e34284 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 01:16:50 +0100 Subject: [PATCH 083/108] enabling dynamic initial layout for fidelity aware mapping --- src/heuristic/HeuristicMapper.cpp | 11 ++--------- test/test_heuristic.cpp | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 51efc16ec..b29d02955 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -29,13 +29,6 @@ void HeuristicMapper::map(const Configuration& configuration) { throw QMAPException("Lookahead is not yet supported for heuristic mapper " "using fidelity-aware mapping!"); } - if (config.considerFidelity && - config.initialLayout == InitialLayout::Dynamic) { - throw QMAPException("Initial layout strategy " + - toString(config.initialLayout) + - " not yet supported for heuristic mapper using " - "fidelity-aware mapping!"); - } if (config.considerFidelity && config.teleportationQubits > 0) { throw QMAPException("Teleportation is not yet supported for heuristic " "mapper using fidelity-aware mapping!"); @@ -111,8 +104,8 @@ void HeuristicMapper::staticInitialMapping() { locations.at(gate.target) = static_cast(q1); qcMapped.initialLayout.at(q0) = static_cast(gate.control); qcMapped.initialLayout.at(q1) = static_cast(gate.target); - qcMapped.outputPermutation.at(q0) = - static_cast(gate.control); + qcMapped.outputPermutation.at(q0) = static_cast( + gate.control); qcMapped.outputPermutation.at(q1) = static_cast(gate.target); break; } diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 7ac29df71..c8216daba 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -155,7 +155,6 @@ TEST(Functionality, HeuristicBenchmark) { TEST(Functionality, EmptyDump) { qc::QuantumComputation qc{1}; - qc.x(0); Architecture arch{1, {}}; HeuristicMapper mapper(qc, arch); mapper.dumpResult("test.qasm"); @@ -867,7 +866,7 @@ class HeuristicTest20QTeleport INSTANTIATE_TEST_SUITE_P( HeuristicTeleport, HeuristicTest20QTeleport, - testing::Combine(testing::Values(1, 2, 3, 1337, 1338, 3147), + testing::Combine(testing::Values(0, 1, 2, 3, 1337, 1338, 3147), testing::Values("ising_model_10", "rd73_140", "cnt3-5_179", "qft_16", "z4_268")), [](const testing::TestParamInfo& inf) { @@ -948,6 +947,18 @@ TEST_P(HeuristicTestFidelity, Static) { SUCCEED() << "Mapping successful"; } +TEST_P(HeuristicTestFidelity, Dynamic) { + Configuration settings{}; + settings.layering = Layering::DisjointQubits; + settings.initialLayout = InitialLayout::Dynamic; + settings.considerFidelity = true; + settings.lookahead = false; + mapper->map(settings); + mapper->dumpResult(GetParam() + "_heuristic_london_fidelity_static.qasm"); + mapper->printResult(std::cout); + SUCCEED() << "Mapping successful"; +} + TEST_P(HeuristicTestFidelity, NoFidelity) { Configuration settings{}; settings.layering = Layering::DisjointQubits; From 2dda1a300adc126083f3fb6bfeb1491e94900fa7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 00:17:32 +0000 Subject: [PATCH 084/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/heuristic/HeuristicMapper.cpp | 4 ++-- test/test_heuristic.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index b29d02955..ad170eba4 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -104,8 +104,8 @@ void HeuristicMapper::staticInitialMapping() { locations.at(gate.target) = static_cast(q1); qcMapped.initialLayout.at(q0) = static_cast(gate.control); qcMapped.initialLayout.at(q1) = static_cast(gate.target); - qcMapped.outputPermutation.at(q0) = static_cast( - gate.control); + qcMapped.outputPermutation.at(q0) = + static_cast(gate.control); qcMapped.outputPermutation.at(q1) = static_cast(gate.target); break; } diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index c8216daba..25329e869 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -155,8 +155,8 @@ TEST(Functionality, HeuristicBenchmark) { TEST(Functionality, EmptyDump) { qc::QuantumComputation qc{1}; - Architecture arch{1, {}}; - HeuristicMapper mapper(qc, arch); + Architecture arch{1, {}}; + HeuristicMapper mapper(qc, arch); mapper.dumpResult("test.qasm"); mapper.map({}); EXPECT_NO_THROW(mapper.dumpResult("test.qasm");); From 8aa5ed3bb88db580705eb6bbedc898b245a657bd Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 01:31:58 +0100 Subject: [PATCH 085/108] fix empty dump test --- test/test_heuristic.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 25329e869..2ac43effa 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -155,6 +155,7 @@ TEST(Functionality, HeuristicBenchmark) { TEST(Functionality, EmptyDump) { qc::QuantumComputation qc{1}; + qc.x(0); Architecture arch{1, {}}; HeuristicMapper mapper(qc, arch); mapper.dumpResult("test.qasm"); From c0f6572698b557dba9a9f4cdd666e79a1eaee1dc Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 01:58:28 +0100 Subject: [PATCH 086/108] add python test_parameters --- test/python/test_compile.py | 101 ++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index d2aa01d14..9ffb5c6e5 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -113,3 +113,104 @@ def test_calibration_from_file(example_circuit: QuantumCircuit) -> None: result = verify(example_circuit, example_circuit_mapped) assert result.considered_equivalent() is True + + +def test_parameters(example_circuit: QuantumCircuit) -> None: + """Test that parameters to compile are properly parsed and passed to the backend.""" + arch = qmap.Architecture(3, {(0, 1), (1, 0), (1, 2), (2, 1)}) + visualizer = qmap.visualization.SearchVisualizer() + _, results = qmap.compile( + example_circuit, + arch=arch, + method="exact", + encoding="commander", + commander_grouping="fixed3", + use_bdd=False, + swap_reduction="coupling_limit", + swap_limit=0, + include_WCNF=False, + use_subsets=True, + subgraph=None, + add_measurements_to_mapped_circuit=True + ) + assert results.configuration.method == qmap.Method.heuristic + assert results.configuration.encoding == qmap.Encoding.commander + assert results.configuration.commander_grouping == qmap.CommanderGrouping.fixed3 + assert results.configuration.use_bdd is False + assert results.configuration.swap_reduction == qmap.SwapReduction.coupling_limit + assert results.configuration.swap_limit == 0 + assert results.configuration.include_WCNF is False + assert results.configuration.use_subsets is True + assert results.configuration.subgraph == set() + assert results.configuration.add_measurements_to_mapped_circuit is True + + _, results = qmap.compile( + example_circuit, + arch=arch, + method="heuristic", + consider_fidelity=False, + initial_layout="dynamic", + iterative_bidirectional_routing_passes=1, + layering="individual_gates", + automatic_layer_splits_node_limit=5000, + lookaheads=15, + lookahead_factor=0.5, + use_teleportation=True, + teleportation_fake=False, + teleportation_seed=0, + pre_mapping_optimizations=True, + post_mapping_optimizations=True, + verbose=True, + debug=True, + visualizer=visualizer + ) + assert results.configuration.method == qmap.Method.heuristic + assert results.configuration.consider_fidelity is False + assert results.configuration.initial_layout == qmap.InitialLayout.dynamic + assert results.configuration.iterative_bidirectional_routing is True + assert results.configuration.iterative_bidirectional_routing_passes == 1 + assert results.configuration.layering == qmap.Layering.individual_gates + assert results.configuration.automatic_layer_splits is True + assert results.configuration.automatic_layer_splits_node_limit == 5000 + assert results.configuration.lookaheads == 15 + assert results.configuration.lookahead is True + assert results.configuration.lookahead_factor == 0.5 + assert results.configuration.use_teleportation is True + assert results.configuration.teleportation_fake is False + assert results.configuration.teleportation_seed == 0 + assert results.configuration.pre_mapping_optimizations is True + assert results.configuration.post_mapping_optimizations is True + assert results.configuration.verbose is True + assert results.configuration.debug is True + assert results.configuration.data_logging_path == visualizer.data_logging_path + + _, results = qmap.compile( + example_circuit, + arch=arch, + method="heuristic", + consider_fidelity=True, + initial_layout="identity", + iterative_bidirectional_routing_passes=None, + layering="disjoint_qubits", + automatic_layer_splits_node_limit=None, + lookaheads=None, + use_teleportation=False, + pre_mapping_optimizations=False, + post_mapping_optimizations=False, + verbose=False, + debug=False + ) + assert results.configuration.method == qmap.Method.heuristic + assert results.configuration.consider_fidelity is True + assert results.configuration.initial_layout == qmap.InitialLayout.identity + assert results.configuration.iterative_bidirectional_routing is False + assert results.configuration.layering == qmap.Layering.disjoint_qubits + assert results.configuration.automatic_layer_splits is False + assert results.configuration.lookaheads == 0 + assert results.configuration.lookahead is False + assert results.configuration.use_teleportation is False + assert results.configuration.pre_mapping_optimizations is False + assert results.configuration.post_mapping_optimizations is False + assert results.configuration.verbose is False + assert results.configuration.debug is False + assert results.configuration.data_logging_path == "" \ No newline at end of file From 42553a0a41dabf4e8955d4435ed57481208ed212 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 00:59:02 +0000 Subject: [PATCH 087/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_compile.py | 24 ++++++++++++------------ test/test_heuristic.cpp | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index 9ffb5c6e5..91a937f3c 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -120,8 +120,8 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: arch = qmap.Architecture(3, {(0, 1), (1, 0), (1, 2), (2, 1)}) visualizer = qmap.visualization.SearchVisualizer() _, results = qmap.compile( - example_circuit, - arch=arch, + example_circuit, + arch=arch, method="exact", encoding="commander", commander_grouping="fixed3", @@ -131,7 +131,7 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: include_WCNF=False, use_subsets=True, subgraph=None, - add_measurements_to_mapped_circuit=True + add_measurements_to_mapped_circuit=True, ) assert results.configuration.method == qmap.Method.heuristic assert results.configuration.encoding == qmap.Encoding.commander @@ -143,10 +143,10 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: assert results.configuration.use_subsets is True assert results.configuration.subgraph == set() assert results.configuration.add_measurements_to_mapped_circuit is True - + _, results = qmap.compile( - example_circuit, - arch=arch, + example_circuit, + arch=arch, method="heuristic", consider_fidelity=False, initial_layout="dynamic", @@ -162,7 +162,7 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: post_mapping_optimizations=True, verbose=True, debug=True, - visualizer=visualizer + visualizer=visualizer, ) assert results.configuration.method == qmap.Method.heuristic assert results.configuration.consider_fidelity is False @@ -183,10 +183,10 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: assert results.configuration.verbose is True assert results.configuration.debug is True assert results.configuration.data_logging_path == visualizer.data_logging_path - + _, results = qmap.compile( - example_circuit, - arch=arch, + example_circuit, + arch=arch, method="heuristic", consider_fidelity=True, initial_layout="identity", @@ -198,7 +198,7 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: pre_mapping_optimizations=False, post_mapping_optimizations=False, verbose=False, - debug=False + debug=False, ) assert results.configuration.method == qmap.Method.heuristic assert results.configuration.consider_fidelity is True @@ -213,4 +213,4 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: assert results.configuration.post_mapping_optimizations is False assert results.configuration.verbose is False assert results.configuration.debug is False - assert results.configuration.data_logging_path == "" \ No newline at end of file + assert results.configuration.data_logging_path == "" diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 2ac43effa..d04acb58a 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -156,8 +156,8 @@ TEST(Functionality, HeuristicBenchmark) { TEST(Functionality, EmptyDump) { qc::QuantumComputation qc{1}; qc.x(0); - Architecture arch{1, {}}; - HeuristicMapper mapper(qc, arch); + Architecture arch{1, {}}; + HeuristicMapper mapper(qc, arch); mapper.dumpResult("test.qasm"); mapper.map({}); EXPECT_NO_THROW(mapper.dumpResult("test.qasm");); From b0f49d59cfa6e9a90854fa2f640aa836c2804687 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 02:00:26 +0100 Subject: [PATCH 088/108] fix ruff warnings --- test/python/test_compile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index 9ffb5c6e5..1813de957 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -213,4 +213,4 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: assert results.configuration.post_mapping_optimizations is False assert results.configuration.verbose is False assert results.configuration.debug is False - assert results.configuration.data_logging_path == "" \ No newline at end of file + assert not results.configuration.data_logging_path \ No newline at end of file From 48456ccab506edb3543fbd2794e7879e85b94948 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 02:11:38 +0100 Subject: [PATCH 089/108] fix test InvalidSettings --- test/test_heuristic.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index d04acb58a..b0f4b2ec9 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -184,9 +184,6 @@ TEST(Functionality, InvalidSettings) { config.lookahead = true; EXPECT_THROW(mapper.map(config), QMAPException); config.lookahead = false; - config.initialLayout = InitialLayout::Dynamic; - EXPECT_THROW(mapper.map(config), QMAPException); - config.initialLayout = InitialLayout::Static; config.teleportationQubits = 2; EXPECT_THROW(mapper.map(config), QMAPException); config.teleportationQubits = 0; From c51f43e838d3cf13fb183b895862758459c64420 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 01:12:10 +0000 Subject: [PATCH 090/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_compile.py | 2 +- test/test_heuristic.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index 24b098cca..7c93ae5eb 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -213,4 +213,4 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: assert results.configuration.post_mapping_optimizations is False assert results.configuration.verbose is False assert results.configuration.debug is False - assert not results.configuration.data_logging_path \ No newline at end of file + assert not results.configuration.data_logging_path diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index b0f4b2ec9..6b0bebd8c 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -183,7 +183,7 @@ TEST(Functionality, InvalidSettings) { config.considerFidelity = true; config.lookahead = true; EXPECT_THROW(mapper.map(config), QMAPException); - config.lookahead = false; + config.lookahead = false; config.teleportationQubits = 2; EXPECT_THROW(mapper.map(config), QMAPException); config.teleportationQubits = 0; From 069e0ccab8c38db7bc3f376dad7183d216c028ff Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 02:27:07 +0100 Subject: [PATCH 091/108] fix python test_parameters --- test/python/test_compile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index 24b098cca..6565833d3 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -133,7 +133,7 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: subgraph=None, add_measurements_to_mapped_circuit=True, ) - assert results.configuration.method == qmap.Method.heuristic + assert results.configuration.method == qmap.Method.exact assert results.configuration.encoding == qmap.Encoding.commander assert results.configuration.commander_grouping == qmap.CommanderGrouping.fixed3 assert results.configuration.use_bdd is False From 04c3fd2a016d8025bcd4f9dec69494d89b927f46 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 02:38:45 +0100 Subject: [PATCH 092/108] remove swap_limit from test_parameters --- test/python/test_compile.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index 40eacdd2b..2e2fef1fa 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -127,7 +127,6 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: commander_grouping="fixed3", use_bdd=False, swap_reduction="coupling_limit", - swap_limit=0, include_WCNF=False, use_subsets=True, subgraph=None, @@ -138,7 +137,6 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: assert results.configuration.commander_grouping == qmap.CommanderGrouping.fixed3 assert results.configuration.use_bdd is False assert results.configuration.swap_reduction == qmap.SwapReduction.coupling_limit - assert results.configuration.swap_limit == 0 assert results.configuration.include_WCNF is False assert results.configuration.use_subsets is True assert results.configuration.subgraph == set() From 068681108d392415dd0ea9144a3f2329535e0bf5 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 02:48:16 +0100 Subject: [PATCH 093/108] add error rates to test_parameters --- test/python/test_compile.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index 2e2fef1fa..846b918fe 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -117,7 +117,15 @@ def test_calibration_from_file(example_circuit: QuantumCircuit) -> None: def test_parameters(example_circuit: QuantumCircuit) -> None: """Test that parameters to compile are properly parsed and passed to the backend.""" - arch = qmap.Architecture(3, {(0, 1), (1, 0), (1, 2), (2, 1)}) + properties = qmap.Architecture.Properties() + properties.set_single_qubit_error(0, 'x', 0.01) + properties.set_single_qubit_error(1, 'x', 0.01) + properties.set_single_qubit_error(2, 'x', 0.01) + properties.set_two_qubit_error(0, 1, 0.02, 'cx') + properties.set_two_qubit_error(1, 0, 0.02, 'cx') + properties.set_two_qubit_error(1, 2, 0.02, 'cx') + properties.set_two_qubit_error(2, 1, 0.02, 'cx') + arch = qmap.Architecture(3, {(0, 1), (1, 0), (1, 2), (2, 1)}, properties) visualizer = qmap.visualization.SearchVisualizer() _, results = qmap.compile( example_circuit, From 31656811769a7302dac2908629009ecc008ad322 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 01:48:45 +0000 Subject: [PATCH 094/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_compile.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index 846b918fe..ce6b0abab 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -118,13 +118,13 @@ def test_calibration_from_file(example_circuit: QuantumCircuit) -> None: def test_parameters(example_circuit: QuantumCircuit) -> None: """Test that parameters to compile are properly parsed and passed to the backend.""" properties = qmap.Architecture.Properties() - properties.set_single_qubit_error(0, 'x', 0.01) - properties.set_single_qubit_error(1, 'x', 0.01) - properties.set_single_qubit_error(2, 'x', 0.01) - properties.set_two_qubit_error(0, 1, 0.02, 'cx') - properties.set_two_qubit_error(1, 0, 0.02, 'cx') - properties.set_two_qubit_error(1, 2, 0.02, 'cx') - properties.set_two_qubit_error(2, 1, 0.02, 'cx') + properties.set_single_qubit_error(0, "x", 0.01) + properties.set_single_qubit_error(1, "x", 0.01) + properties.set_single_qubit_error(2, "x", 0.01) + properties.set_two_qubit_error(0, 1, 0.02, "cx") + properties.set_two_qubit_error(1, 0, 0.02, "cx") + properties.set_two_qubit_error(1, 2, 0.02, "cx") + properties.set_two_qubit_error(2, 1, 0.02, "cx") arch = qmap.Architecture(3, {(0, 1), (1, 0), (1, 2), (2, 1)}, properties) visualizer = qmap.visualization.SearchVisualizer() _, results = qmap.compile( From 96a2fd811b24f063ac5c0c1188b3bf8b2432cb38 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 16:36:15 +0100 Subject: [PATCH 095/108] explicit cleanup of tmp directory in test_parameters --- test/python/test_compile.py | 82 ++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index ce6b0abab..0a62317c1 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -126,7 +126,6 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: properties.set_two_qubit_error(1, 2, 0.02, "cx") properties.set_two_qubit_error(2, 1, 0.02, "cx") arch = qmap.Architecture(3, {(0, 1), (1, 0), (1, 2), (2, 1)}, properties) - visualizer = qmap.visualization.SearchVisualizer() _, results = qmap.compile( example_circuit, arch=arch, @@ -149,46 +148,47 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: assert results.configuration.use_subsets is True assert results.configuration.subgraph == set() assert results.configuration.add_measurements_to_mapped_circuit is True - - _, results = qmap.compile( - example_circuit, - arch=arch, - method="heuristic", - consider_fidelity=False, - initial_layout="dynamic", - iterative_bidirectional_routing_passes=1, - layering="individual_gates", - automatic_layer_splits_node_limit=5000, - lookaheads=15, - lookahead_factor=0.5, - use_teleportation=True, - teleportation_fake=False, - teleportation_seed=0, - pre_mapping_optimizations=True, - post_mapping_optimizations=True, - verbose=True, - debug=True, - visualizer=visualizer, - ) - assert results.configuration.method == qmap.Method.heuristic - assert results.configuration.consider_fidelity is False - assert results.configuration.initial_layout == qmap.InitialLayout.dynamic - assert results.configuration.iterative_bidirectional_routing is True - assert results.configuration.iterative_bidirectional_routing_passes == 1 - assert results.configuration.layering == qmap.Layering.individual_gates - assert results.configuration.automatic_layer_splits is True - assert results.configuration.automatic_layer_splits_node_limit == 5000 - assert results.configuration.lookaheads == 15 - assert results.configuration.lookahead is True - assert results.configuration.lookahead_factor == 0.5 - assert results.configuration.use_teleportation is True - assert results.configuration.teleportation_fake is False - assert results.configuration.teleportation_seed == 0 - assert results.configuration.pre_mapping_optimizations is True - assert results.configuration.post_mapping_optimizations is True - assert results.configuration.verbose is True - assert results.configuration.debug is True - assert results.configuration.data_logging_path == visualizer.data_logging_path + + with qmap.visualization.SearchVisualizer() as visualizer: + _, results = qmap.compile( + example_circuit, + arch=arch, + method="heuristic", + consider_fidelity=False, + initial_layout="dynamic", + iterative_bidirectional_routing_passes=1, + layering="individual_gates", + automatic_layer_splits_node_limit=5000, + lookaheads=15, + lookahead_factor=0.5, + use_teleportation=True, + teleportation_fake=False, + teleportation_seed=0, + pre_mapping_optimizations=True, + post_mapping_optimizations=True, + verbose=True, + debug=True, + visualizer=visualizer, + ) + assert results.configuration.method == qmap.Method.heuristic + assert results.configuration.consider_fidelity is False + assert results.configuration.initial_layout == qmap.InitialLayout.dynamic + assert results.configuration.iterative_bidirectional_routing is True + assert results.configuration.iterative_bidirectional_routing_passes == 1 + assert results.configuration.layering == qmap.Layering.individual_gates + assert results.configuration.automatic_layer_splits is True + assert results.configuration.automatic_layer_splits_node_limit == 5000 + assert results.configuration.lookaheads == 15 + assert results.configuration.lookahead is True + assert results.configuration.lookahead_factor == 0.5 + assert results.configuration.use_teleportation is True + assert results.configuration.teleportation_fake is False + assert results.configuration.teleportation_seed == 0 + assert results.configuration.pre_mapping_optimizations is True + assert results.configuration.post_mapping_optimizations is True + assert results.configuration.verbose is True + assert results.configuration.debug is True + assert results.configuration.data_logging_path == visualizer.data_logging_path _, results = qmap.compile( example_circuit, From f9b4480caa5300f2607e3e3d24ea6a4d40376762 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 15:39:09 +0000 Subject: [PATCH 096/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_compile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/test_compile.py b/test/python/test_compile.py index 0a62317c1..513e49875 100644 --- a/test/python/test_compile.py +++ b/test/python/test_compile.py @@ -148,7 +148,7 @@ def test_parameters(example_circuit: QuantumCircuit) -> None: assert results.configuration.use_subsets is True assert results.configuration.subgraph == set() assert results.configuration.add_measurements_to_mapped_circuit is True - + with qmap.visualization.SearchVisualizer() as visualizer: _, results = qmap.compile( example_circuit, From 334e5e3df4408e78dcdad532dbd10724e0afeb20 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 20:08:22 +0100 Subject: [PATCH 097/108] add InvalidCircuits test --- test/test_heuristic.cpp | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 6b0bebd8c..23c3aba97 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -223,6 +223,26 @@ TEST(Functionality, NoMeasurmentsAdded) { EXPECT_NE(qcMapped.back()->getType(), qc::Measure); } +TEST(Functionality, InvalidCircuits) { + Configuration config{}; + config.method = Method::Heuristic; + + // architecture not connected + qc::QuantumComputation qc{2U}; + qc.cx({0}, 1); + Architecture arch{2U, {}}; + HeuristicMapper mapper(qc, arch); + EXPECT_THROW(mapper.map(config), QMAPException); + + // gate with >1 control + qc::QuantumComputation qc3{3U}; + qc::StandardOperation op = qc::StandardOperation(3, {{0}, {1}}, qc::Qubit{2}); + qc3.emplace_back(op.clone()); + Architecture arch2{3U, {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 0}, {0, 2}}}; + HeuristicMapper mapper3(qc3, arch2); + EXPECT_THROW(mapper3.map(config), QMAPException); +} + TEST(Functionality, HeuristicAdmissibility) { Architecture architecture{}; const CouplingMap cm = {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 3}, @@ -278,19 +298,26 @@ TEST(Functionality, HeuristicAdmissibility) { TEST(Functionality, DataLoggerAfterClose) { const std::string dataLoggingPath = "test_log/"; - qc::QuantumComputation qc{1}; + qc::QuantumComputation qc{3}; qc.x(0); - Architecture arch{1, {}}; + Architecture arch{3, {}}; std::unique_ptr dataLogger = std::make_unique(dataLoggingPath, arch, qc); - dataLogger->clearLog(); + const qc::CompoundOperation compOp(3); + Exchange teleport(0, 2, 1, qc::OpType::Teleportation); + dataLogger->logSearchNode(0, 0, 0, 0., 0., 0., {}, false, {{teleport}}, 0); + dataLogger->logSearchNode(1, 0, 0, 0., 0., 0., {}, false, {}, 0); + dataLogger->splitLayer(); + dataLogger->logFinalizeLayer(0, compOp, {}, {}, {}, 0, 0., 0., 0., {}, {}, 0); + dataLogger->logFinalizeLayer(0, compOp, {}, {}, {}, 0, 0., 0., 0., {}, {}, 0); + dataLogger->logSearchNode(0, 0, 0, 0., 0., 0., {}, false, {}, 0); dataLogger->close(); + dataLogger->clearLog(); dataLogger->logArchitecture(); dataLogger->logInputCircuit(qc); dataLogger->logOutputCircuit(qc); dataLogger->logSearchNode(0, 0, 0, 0., 0., 0., {}, false, {}, 0); - const qc::CompoundOperation compOp(0); dataLogger->logFinalizeLayer(0, compOp, {}, {}, {}, 0, 0., 0., 0., {}, {}, 0); dataLogger->splitLayer(); MappingResults result; @@ -879,6 +906,7 @@ TEST_P(HeuristicTest20QTeleport, Teleportation) { Configuration settings{}; settings.initialLayout = InitialLayout::Dynamic; settings.debug = true; + settings.verbose = true; settings.teleportationQubits = std::min( (arch.getNqubits() - qc.getNqubits()) & ~1U, static_cast(8)); settings.teleportationSeed = std::get<0>(GetParam()); From 69cece55c4473493d331244019f5248a8f4a30a8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 19:10:56 +0000 Subject: [PATCH 098/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_heuristic.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 23c3aba97..6356c0702 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -226,19 +226,19 @@ TEST(Functionality, NoMeasurmentsAdded) { TEST(Functionality, InvalidCircuits) { Configuration config{}; config.method = Method::Heuristic; - + // architecture not connected qc::QuantumComputation qc{2U}; qc.cx({0}, 1); - Architecture arch{2U, {}}; + Architecture arch{2U, {}}; HeuristicMapper mapper(qc, arch); EXPECT_THROW(mapper.map(config), QMAPException); - + // gate with >1 control qc::QuantumComputation qc3{3U}; qc::StandardOperation op = qc::StandardOperation(3, {{0}, {1}}, qc::Qubit{2}); qc3.emplace_back(op.clone()); - Architecture arch2{3U, {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 0}, {0, 2}}}; + Architecture arch2{3U, {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 0}, {0, 2}}}; HeuristicMapper mapper3(qc3, arch2); EXPECT_THROW(mapper3.map(config), QMAPException); } @@ -304,7 +304,7 @@ TEST(Functionality, DataLoggerAfterClose) { std::unique_ptr dataLogger = std::make_unique(dataLoggingPath, arch, qc); const qc::CompoundOperation compOp(3); - Exchange teleport(0, 2, 1, qc::OpType::Teleportation); + Exchange teleport(0, 2, 1, qc::OpType::Teleportation); dataLogger->logSearchNode(0, 0, 0, 0., 0., 0., {}, false, {{teleport}}, 0); dataLogger->logSearchNode(1, 0, 0, 0., 0., 0., {}, false, {}, 0); dataLogger->splitLayer(); From 870af6c7090d9c476ddd36660b831126f4fd7aed Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 15 Dec 2023 20:29:39 +0100 Subject: [PATCH 099/108] fix lint warning --- test/test_heuristic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index 6356c0702..f9967e279 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -236,7 +236,7 @@ TEST(Functionality, InvalidCircuits) { // gate with >1 control qc::QuantumComputation qc3{3U}; - qc::StandardOperation op = qc::StandardOperation(3, {{0}, {1}}, qc::Qubit{2}); + const qc::StandardOperation op = qc::StandardOperation(3, {{0}, {1}}, qc::Qubit{2}); qc3.emplace_back(op.clone()); Architecture arch2{3U, {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 0}, {0, 2}}}; HeuristicMapper mapper3(qc3, arch2); From b52a1c0f700d5d9483925c11c0154f1bc64db204 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Dec 2023 19:32:02 +0000 Subject: [PATCH 100/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_heuristic.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_heuristic.cpp b/test/test_heuristic.cpp index f9967e279..952984006 100644 --- a/test/test_heuristic.cpp +++ b/test/test_heuristic.cpp @@ -235,8 +235,9 @@ TEST(Functionality, InvalidCircuits) { EXPECT_THROW(mapper.map(config), QMAPException); // gate with >1 control - qc::QuantumComputation qc3{3U}; - const qc::StandardOperation op = qc::StandardOperation(3, {{0}, {1}}, qc::Qubit{2}); + qc::QuantumComputation qc3{3U}; + const qc::StandardOperation op = + qc::StandardOperation(3, {{0}, {1}}, qc::Qubit{2}); qc3.emplace_back(op.clone()); Architecture arch2{3U, {{0, 1}, {1, 0}, {1, 2}, {2, 1}, {2, 0}, {0, 2}}}; HeuristicMapper mapper3(qc3, arch2); From f99324978fcde3cd5f0be77a408c70f2df7c8c25 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Thu, 21 Dec 2023 23:36:56 +0100 Subject: [PATCH 101/108] remove redundant comments in visualization --- .../qmap/visualization/search_visualizer.py | 58 +++---------- .../visualization/visualize_search_graph.py | 86 +++++-------------- 2 files changed, 31 insertions(+), 113 deletions(-) diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py index e665cf56e..fd2bb94cf 100644 --- a/src/mqt/qmap/visualization/search_visualizer.py +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -54,7 +54,7 @@ def close(self) -> None: def visualize_search_graph( self, - layer: int | Literal["interactive"] = "interactive", # 'interactive' (slider menu) | index + layer: int | Literal["interactive"] = "interactive", architecture_node_positions: MutableMapping[int, tuple[float, float]] | None = None, architecture_layout: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"] = "sfdp", search_node_layout: Literal[ @@ -71,68 +71,30 @@ def visualize_search_graph( draw_search_edges: bool = True, search_edges_width: float = 0.5, search_edges_color: str = "#888", - search_edges_dash: str = "solid", # 'solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot', string containing a dash length list in pixels or percentages (e.g. '5px 10px 2px 2px', '5, 10, 2, 2', '10% 20% 40%') + search_edges_dash: str = "solid", tapered_search_layer_heights: bool = True, show_layout: Literal["hover", "click"] | None = "hover", show_swaps: bool = True, - show_shared_swaps: bool = True, # if one swap moves 2 considered qubits -> combine into one two-colored and two-headed arrow - color_valid_mapping: str | None = "green", # static HTML color (e.g. 'blue' or '#0000FF') - color_final_node: str | None = "red", # static HTML color (e.g. 'blue' or '#0000FF') + show_shared_swaps: bool = True, + color_valid_mapping: str | None = "green", + color_final_node: str | None = "red", search_node_color: str | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]) = "total_cost", - # 'total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty' | static HTML color (e.g. 'blue' or '#0000FF') | - # function that takes a SearchNode and returns a float (e.g. lambda n: n.fixed_cost + n.heuristic_cost) - # node fields: {"fixed_cost": float, "heuristic_cost": float, "lookahead_penalty": float, "is_valid_mapping": bool, - # "final": bool, "depth": int, "layout": tuple[int, ...], "swaps": tuple[tuple[int, int], ...]} - # or list of the above if 3d graph is used and multiple points per node are defined in search_node_height (lengths need to match) - # if in that case no is list is provided all points per node will be the same color - prioritize_search_node_color: bool - | list[ - bool - ] = False, # if True, search_node_color will be prioritized over color_valid_mapping and color_final_node - search_node_color_scale: str | list[str] = "YlGnBu", # https://plotly.com/python/builtin-colorscales/ - # aggrnyl agsunset blackbody bluered blues blugrn bluyl brwnyl - # bugn bupu burg burgyl cividis darkmint electric emrld - # gnbu greens greys hot inferno jet magenta magma - # mint orrd oranges oryel peach pinkyl plasma plotly3 - # pubu pubugn purd purp purples purpor rainbow rdbu - # rdpu redor reds sunset sunsetdark teal tealgrn turbo - # viridis ylgn ylgnbu ylorbr ylorrd algae amp deep - # dense gray haline ice matter solar speed tempo - # thermal turbid armyrose brbg earth fall geyser prgn - # piyg picnic portland puor rdgy rdylbu rdylgn spectral - # tealrose temps tropic balance curl delta oxy edge - # hsv icefire phase twilight mrybm mygbm armylg falllg + prioritize_search_node_color: bool | list[bool] = False, + search_node_color_scale: str | list[str] = "YlGnBu", search_node_invert_color_scale: bool | list[bool] = True, search_node_colorbar_title: str | list[str | None] | None = None, search_node_colorbar_spacing: float = 0.06, search_node_height: str | (Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]) = "total_cost", - # just as with search_node_color (without color strings), but possible to specify a list to draw multiple point per node on different heights - # only applicable if use3d is True draw_stems: bool = False, stems_width: float = 0.7, stems_color: str = "#444", - stems_dash: str = "solid", # 'solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot', string containing a dash length list in pixels or percentages (e.g. '5px 10px 2px 2px', '5, 10, 2, 2', '10% 20% 40%') + stems_dash: str = "solid", show_search_progression: bool = True, search_progression_step: int = 10, - search_progression_speed: float = 2, # steps per second + search_progression_speed: float = 2, plotly_settings: MutableMapping[str, MutableMapping[str, object]] | None = None, - # { - # 'layout': settings for plotly.graph_objects.Layout (of subplots figure) - # 'arrows': settings for plotly.graph_objects.layout.Annotation - # 'stats_legend': settings for plotly.graph_objects.layout.Annotation - # 'search_nodes': settings for plotly.graph_objects.Scatter resp. ...Scatter3d - # 'search_edges': settings for plotly.graph_objects.Scatter resp. ...Scatter3d - # 'architecture_nodes': settings for plotly.graph_objects.Scatter - # 'architecture_edges': settings for plotly.graph_objects.Scatter - # 'architecture_edge_labels': settings for plotly.graph_objects.Scatter - # 'search_xaxis': settings for plotly.graph_objects.layout.XAxis resp. ...layout.scene.XAxis - # 'search_yaxis': settings for plotly.graph_objects.layout.YAxis resp. ...layout.scene.YAxis - # 'search_zaxis': settings for plotly.graph_objects.layout.scene.ZAxis - # 'architecture_xaxis': settings for plotly.graph_objects.layout.XAxis - # 'architecture_yaxis': settings for plotly.graph_objects.layout.YAxis - # } ) -> Widget: """Creates a widget to visualize the search graph. @@ -159,7 +121,7 @@ def visualize_search_graph( show_shared_swaps (bool): Indicate a shared swap by 1 arrow with 2 heads, otherwise 2 arrows in opposite direction are drawn for the 1 shared swap. Defaults to True. color_valid_mapping (str | None): Color to use for search nodes containing a valid qubit layout (in CSS format). Defaults to "green". color_final_node (str | None): Color to use for the final solution search node (in CSS format). Defaults to "red". - search_node_color (str | Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by ``search_node_color_scale`` , or a preset data feature ('total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty'). In case a 3D search graph is used with multiple point per search node, each point's color can be controlled individually via a list. Defaults to "total_cost". + search_node_color (str | Callable[[SearchNode], float] | list[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by ``search_node_color_scale`` , or a preset data feature ('total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty'). In case a 3D search graph is used with multiple points per search node, each point's color can be controlled individually via a list. Defaults to "total_cost". prioritize_search_node_color (bool | list[ bool ]): If search_node_color should be prioritized over color_valid_mapping and color_final_node. Defaults to False. search_node_color_scale (str | list[str]): Color scale to be used for converting float data features to search node colors. (See https://plotly.com/python/builtin-colorscales/ for valid values). Defaults to "YlGnBu". search_node_invert_color_scale (bool | list[bool]): If the color scale should be inverted. Defaults to True. diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index e2d149b1a..e247fbe3a 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -173,7 +173,7 @@ def _layout_search_graph( search_graph, root, origin=(0, 0), scalex=60, scaley=-1 ) else: - pos = graphviz_layout(search_graph, prog=method, root=search_graph.nodes[0]) + pos = graphviz_layout(search_graph, prog=method, root=root) if not tapered_layer_heights: return pos @@ -309,17 +309,11 @@ def _prepare_search_graph_scatter_data( search_pos: MutableMapping[int, Position], use3d: bool, search_node_color: Sequence[str | Callable[[SearchNode], float]], - # 'total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty' | static HTML color (e.g. 'blue' or '#0000FF') | - # function that takes a node parameter dict and returns a float (e.g. lambda node: node['total_cost'] + node['fixed_cost']) - # node parameter dict: {"fixed_cost": float, "heuristic_cost": float, "lookahead_penalty": float, "is_valid_mapping": bool, - # "final": bool, "depth": int, "layout": Sequence[int], "swaps": Sequence[tuple[int, int]]} - # or list of the above if 3d graph is used and multiple points per node are defined in search_node_height (lengths need to match) - # if in that case no is list is provided all points per node will be the same color prioritize_search_node_color: Sequence[bool], search_node_height: Sequence[Callable[[SearchNode], float]], - color_valid_mapping: str | None, # static HTML color (e.g. 'blue' or '#0000FF') - color_final_node: str | None, # static HTML color (e.g. 'blue' or '#0000FF') - draw_stems: bool, # only applicable for 3D plots + color_valid_mapping: str | None, + color_final_node: str | None, + draw_stems: bool, draw_edges: bool, ) -> tuple[ Sequence[float | None], @@ -337,7 +331,7 @@ def _prepare_search_graph_scatter_data( float, float, float, - float, + float ]: # edge_x, edge_y, edge_z, node_x, node_y, node_z, node_color, stem_x, stem_y, stem_z, min_x, max_x, min_y, max_y, min_z, max_z edge_x: list[float | None] = [] edge_y: list[float | None] = [] @@ -695,7 +689,7 @@ def _visualize_layout( arch_x_arrow_spacing: float, arch_y_arrow_spacing: float, show_shared_swaps: bool, - layout_node_trace_index: int, # current_node_layout_visualized + layout_node_trace_index: int, search_node_trace: go.Scatter | None, plotly_settings: _PlotlySettings, ) -> None: @@ -786,7 +780,7 @@ def _load_layer_data( float, # search_min_y float, # search_max_y float, # search_min_z - float, # search_max_z + float # search_max_z ]: if not Path(f"{data_logging_path}layer_{layer}.json").exists(): msg = f"No data at {data_logging_path}layer_{layer}.json" @@ -1034,7 +1028,7 @@ def _visualize_search_graph_check_parameters( Sequence[bool], # search_node_invert_color_scale Sequence[bool], # prioritize_search_node_color Sequence[str], # search_node_colorbar_title - _PlotlySettings, # plotly_settings + _PlotlySettings # plotly_settings ]: if not isinstance(data_logging_path, str): msg = "data_logging_path must be a string" # type: ignore[unreachable] @@ -1491,7 +1485,7 @@ def _visualize_search_graph_check_parameters( def visualize_search_graph( data_logging_path: str, - layer: int | Literal["interactive"] = "interactive", # 'interactive' (slider menu) | index + layer: int | Literal["interactive"] = "interactive", architecture_node_positions: MutableMapping[int, Position] | None = None, architecture_layout: Literal["dot", "neato", "fdp", "sfdp", "circo", "twopi", "osage", "patchwork"] = "sfdp", search_node_layout: Literal[ @@ -1508,71 +1502,31 @@ def visualize_search_graph( draw_search_edges: bool = True, search_edges_width: float = 0.5, search_edges_color: str = "#888", - search_edges_dash: str = "solid", # 'solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot', string containing a dash length list in pixels or percentages (e.g. '5px 10px 2px 2px', '5, 10, 2, 2', '10% 20% 40%') + search_edges_dash: str = "solid", tapered_search_layer_heights: bool = True, show_layout: Literal["hover", "click"] | None = "hover", show_swaps: bool = True, - show_shared_swaps: bool = True, # if one swap moves 2 considered qubits -> combine into one two-colored and two-headed arrow - color_valid_mapping: str | None = "green", # static HTML color (e.g. 'blue' or '#0000FF') - color_final_node: str | None = "red", # static HTML color (e.g. 'blue' or '#0000FF') + show_shared_swaps: bool = True, + color_valid_mapping: str | None = "green", + color_final_node: str | None = "red", search_node_color: str | (Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]) = "total_cost", - # 'total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty' | static HTML color (e.g. 'blue' or '#0000FF') | - # function that takes a SearchNode and returns a float (e.g. lambda n: n.fixed_cost + n.heuristic_cost) - # node fields: {"fixed_cost": float, "heuristic_cost": float, "lookahead_penalty": float, "is_valid_mapping": bool, - # "final": bool, "depth": int, "layout": Sequence[int], "swaps": Sequence[tuple[int, int]]} - # or list of the above if 3d graph is used and multiple points per node are defined in search_node_height (lengths need to match) - # if in that case no is list is provided all points per node will be the same color - prioritize_search_node_color: bool - | Sequence[ - bool - ] = False, # if True, search_node_color will be prioritized over color_valid_mapping and color_final_node + prioritize_search_node_color: bool | Sequence[bool] = False, search_node_color_scale: Colorscale - | Sequence[Colorscale] = "YlGnBu", # https://plotly.com/python/builtin-colorscales/ - # aggrnyl agsunset blackbody bluered blues blugrn bluyl brwnyl - # bugn bupu burg burgyl cividis darkmint electric emrld - # gnbu greens greys hot inferno jet magenta magma - # mint orrd oranges oryel peach pinkyl plasma plotly3 - # pubu pubugn purd purp purples purpor rainbow rdbu - # rdpu redor reds sunset sunsetdark teal tealgrn turbo - # viridis ylgn ylgnbu ylorbr ylorrd algae amp deep - # dense gray haline ice matter solar speed tempo - # thermal turbid armyrose brbg earth fall geyser prgn - # piyg picnic portland puor rdgy rdylbu rdylgn spectral - # tealrose temps tropic balance curl delta oxy edge - # hsv icefire phase twilight mrybm mygbm armylg falllg + | Sequence[Colorscale] = "YlGnBu", search_node_invert_color_scale: bool | Sequence[bool] = True, search_node_colorbar_title: str | Sequence[str | None] | None = None, search_node_colorbar_spacing: float = 0.06, search_node_height: str | (Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]) = "total_cost", - # just as with search_node_color (without color strings), but possible to specify a list to draw multiple point per node on different heights - # only applicable if use3d is True draw_stems: bool = False, stems_width: float = 0.7, stems_color: str = "#444", - stems_dash: str = "solid", # 'solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot', string containing a dash length list in pixels or percentages (e.g. '5px 10px 2px 2px', '5, 10, 2, 2', '10% 20% 40%') + stems_dash: str = "solid", show_search_progression: bool = True, search_progression_step: int = 10, - search_progression_speed: float = 2, # steps per second + search_progression_speed: float = 2, plotly_settings: MutableMapping[str, MutableMapping[str, object]] | None = None, - # { - # 'layout': settings for plotly.graph_objects.Layout (of subplots figure) - # 'arrows': settings for plotly.graph_objects.layout.Annotation - # 'stats_legend': settings for plotly.graph_objects.layout.Annotation - # 'search_nodes': settings for plotly.graph_objects.Scatter resp. ...Scatter3d - # 'search_edges': settings for plotly.graph_objects.Scatter resp. ...Scatter3d - # 'architecture_nodes': settings for plotly.graph_objects.Scatter - # 'architecture_edges': settings for plotly.graph_objects.Scatter - # 'architecture_edge_labels': settings for plotly.graph_objects.Scatter - # 'search_xaxis': settings for plotly.graph_objects.layout.XAxis resp. ...layout.scene.XAxis - # 'search_yaxis': settings for plotly.graph_objects.layout.YAxis resp. ...layout.scene.YAxis - # 'search_zaxis': settings for plotly.graph_objects.layout.scene.ZAxis - # 'architecture_xaxis': settings for plotly.graph_objects.layout.XAxis - # 'architecture_yaxis': settings for plotly.graph_objects.layout.YAxis - # } - # TODO: show archticture edge labels (and make text adjustable) - # TODO: make hover text of search (especially for multiple points per node!) and architecture nodes adjustable ) -> Widget: """Creates a widget to visualize a search graph. @@ -1600,7 +1554,7 @@ def visualize_search_graph( show_shared_swaps (bool): Indicate a shared swap by 1 arrow with 2 heads, otherwise 2 arrows in opposite direction are drawn for the 1 shared swap. Defaults to True. color_valid_mapping (str | None): Color to use for search nodes containing a valid qubit layout (in CSS format). Defaults to "green". color_final_node (str | None): Color to use for the final solution search node (in CSS format). Defaults to "red". - search_node_color (str | Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by `search_node_color_scale`, or a preset data feature ('total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty'). In case a 3D search graph is used with multiple point per search node, each point's color can be controlled individually via a list. Defaults to "total_cost". + search_node_color (str | Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]): Color to be used for search nodes. Either a static color (in CSS format) or function mapping a mqt.qmap.visualization.SearchNode to a float value, which in turn gets translated into a color by `search_node_color_scale`, or a preset data feature ('total_cost' | 'fixed_cost' | 'heuristic_cost' | 'lookahead_penalty'). In case a 3D search graph is used with multiple points per search node, each point's color can be controlled individually via a list. Defaults to "total_cost". prioritize_search_node_color (bool | Sequence[ bool ]): If search_node_color should be prioritized over color_valid_mapping and color_final_node. Defaults to False. search_node_color_scale (str | Sequence[str]): Color scale to be used for converting float data features to search node colors. (See https://plotly.com/python/builtin-colorscales/ for valid values). Defaults to "YlGnBu". search_node_invert_color_scale (bool | Sequence[bool]): If the color scale should be inverted. Defaults to True. @@ -1613,7 +1567,7 @@ def visualize_search_graph( stems_dash (str): Dashing of stems in 3D search graphs (in CSS format). Defaults to "solid". show_search_progression (bool): If the search progression should be animated. Defaults to True. search_progression_step (int): Step size (in number of nodes added) of search progression animation. Defaults to 10. - search_progression_speed (float): Speed of the search progression animation. Defaults to 2. + search_progression_speed (float): Speed of the search progression animation in steps per second. Defaults to 2. plotly_settings (MutableMapping[str, MutableMapping[str, any]] | None): Plotly configuration dictionaries to be passed through. Defaults to None. .. code-block:: text @@ -1639,6 +1593,8 @@ def visualize_search_graph( Returns: Widget: An interactive IPython widget to visualize the search graph. """ + # TODO: show archticture edge labels (and make text adjustable) + # TODO: make hover text of search (especially for multiple points per node!) and architecture nodes adjustable # check and process all parameters if plotly_settings is None: plotly_settings = {} From 31532dde7ba9df8f47e0f4c3c4c99620e8168f2b Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 22 Dec 2023 00:01:31 +0100 Subject: [PATCH 102/108] add graphviz to dependencies --- docs/source/Mapping.ipynb | 6 +++--- docs/source/library/Visualization.rst | 7 ------- pyproject.toml | 3 ++- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/docs/source/Mapping.ipynb b/docs/source/Mapping.ipynb index b8c5935e9..cf1be1768 100644 --- a/docs/source/Mapping.ipynb +++ b/docs/source/Mapping.ipynb @@ -285,8 +285,7 @@ "\n", "visualizer = qmap.visualization.SearchVisualizer()\n", "qc_mapped, res = qmap.compile(qc, arch, method=\"heuristic\", visualizer=visualizer)\n", - "arch_node_pos = {0: (0, 0), 1: (0, 1), 2: (0, 2), 3: (1, 2), 4: (1, 1), 5: (1, 0)}\n", - "visualizer.visualize_search_graph(architecture_node_positions=arch_node_pos)" + "visualizer.visualize_search_graph()" ] }, { @@ -312,7 +311,8 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3" + "pygments_lexer": "ipython3", + "version": "3.8.5" } }, "nbformat": 4, diff --git a/docs/source/library/Visualization.rst b/docs/source/library/Visualization.rst index 5cb1859b0..cae23bd8e 100644 --- a/docs/source/library/Visualization.rst +++ b/docs/source/library/Visualization.rst @@ -15,13 +15,6 @@ The recommended way to visualize search graphs of a mapping process is via a :cl .. 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 `_ 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: diff --git a/pyproject.toml b/pyproject.toml index 203735177..b3146e933 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,8 @@ visualization = [ "plotly>=5.15.0", "networkx>=2.5", "walkerlayout>=1.0.2", - "ipywidgets>=7.6.5" + "ipywidgets>=7.6.5", + "graphviz" ] dev = ["mqt.qmap[coverage, docs, visualization]"] From e281b038850ba63351ceddb846009340705b9afb Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 22 Dec 2023 00:17:52 +0100 Subject: [PATCH 103/108] optimizing splittable check --- src/heuristic/HeuristicMapper.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index ad170eba4..79661af3e 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -557,13 +557,12 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { const auto start = std::chrono::steady_clock::now(); std::size_t expandedNodes = 0; - - const bool splittable = isLayerSplittable(layer); + + const bool splittable = config.automaticLayerSplits ? isLayerSplittable(layer) : false; while (!nodes.empty() && (!done || nodes.top().getTotalCost() < bestDoneNode.getTotalFixedCost())) { - if (splittable && config.automaticLayerSplits && - expandedNodes >= config.automaticLayerSplitsNodeLimit) { + if (splittable && expandedNodes >= config.automaticLayerSplitsNodeLimit) { if (config.dataLoggingEnabled()) { qc::CompoundOperation compOp(architecture->getNqubits()); for (const auto& gate : layers.at(layer)) { From 6f25e827019b1a155f0210ab3e8c22158cb94b84 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 23:18:30 +0000 Subject: [PATCH 104/108] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/Mapping.ipynb | 3 +-- src/heuristic/HeuristicMapper.cpp | 5 +++-- src/mqt/qmap/visualization/visualize_search_graph.py | 9 ++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/source/Mapping.ipynb b/docs/source/Mapping.ipynb index cf1be1768..0cf616612 100644 --- a/docs/source/Mapping.ipynb +++ b/docs/source/Mapping.ipynb @@ -311,8 +311,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" + "pygments_lexer": "ipython3" } }, "nbformat": 4, diff --git a/src/heuristic/HeuristicMapper.cpp b/src/heuristic/HeuristicMapper.cpp index 79661af3e..0cfde2dd4 100644 --- a/src/heuristic/HeuristicMapper.cpp +++ b/src/heuristic/HeuristicMapper.cpp @@ -557,8 +557,9 @@ HeuristicMapper::Node HeuristicMapper::aStarMap(size_t layer, bool reverse) { const auto start = std::chrono::steady_clock::now(); std::size_t expandedNodes = 0; - - const bool splittable = config.automaticLayerSplits ? isLayerSplittable(layer) : false; + + const bool splittable = + config.automaticLayerSplits ? isLayerSplittable(layer) : false; while (!nodes.empty() && (!done || nodes.top().getTotalCost() < bestDoneNode.getTotalFixedCost())) { diff --git a/src/mqt/qmap/visualization/visualize_search_graph.py b/src/mqt/qmap/visualization/visualize_search_graph.py index e247fbe3a..ae9d71498 100644 --- a/src/mqt/qmap/visualization/visualize_search_graph.py +++ b/src/mqt/qmap/visualization/visualize_search_graph.py @@ -331,7 +331,7 @@ def _prepare_search_graph_scatter_data( float, float, float, - float + float, ]: # edge_x, edge_y, edge_z, node_x, node_y, node_z, node_color, stem_x, stem_y, stem_z, min_x, max_x, min_y, max_y, min_z, max_z edge_x: list[float | None] = [] edge_y: list[float | None] = [] @@ -780,7 +780,7 @@ def _load_layer_data( float, # search_min_y float, # search_max_y float, # search_min_z - float # search_max_z + float, # search_max_z ]: if not Path(f"{data_logging_path}layer_{layer}.json").exists(): msg = f"No data at {data_logging_path}layer_{layer}.json" @@ -1028,7 +1028,7 @@ def _visualize_search_graph_check_parameters( Sequence[bool], # search_node_invert_color_scale Sequence[bool], # prioritize_search_node_color Sequence[str], # search_node_colorbar_title - _PlotlySettings # plotly_settings + _PlotlySettings, # plotly_settings ]: if not isinstance(data_logging_path, str): msg = "data_logging_path must be a string" # type: ignore[unreachable] @@ -1512,8 +1512,7 @@ def visualize_search_graph( search_node_color: str | (Callable[[SearchNode], float] | Sequence[str | Callable[[SearchNode], float]]) = "total_cost", prioritize_search_node_color: bool | Sequence[bool] = False, - search_node_color_scale: Colorscale - | Sequence[Colorscale] = "YlGnBu", + search_node_color_scale: Colorscale | Sequence[Colorscale] = "YlGnBu", search_node_invert_color_scale: bool | Sequence[bool] = True, search_node_colorbar_title: str | Sequence[str | None] | None = None, search_node_colorbar_spacing: float = 0.06, From fa2ed08adff475a915780c8bbcc0e724e2ac742e Mon Sep 17 00:00:00 2001 From: Elias Leon Foramitti Date: Fri, 22 Dec 2023 00:20:13 +0100 Subject: [PATCH 105/108] Apply suggestions from code review Co-authored-by: Lukas Burgholzer --- pyproject.toml | 2 +- src/mqt/qmap/compile.py | 2 +- src/mqt/qmap/visualization/search_visualizer.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b3146e933..78c6b120b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,7 +70,7 @@ visualization = [ "ipywidgets>=7.6.5", "graphviz" ] -dev = ["mqt.qmap[coverage, docs, visualization]"] +dev = ["mqt.qmap[coverage, docs]"] [project.urls] Homepage = "https://github.com/cda-tum/mqt-qmap" diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 0a4503148..8f22e39ca 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -12,7 +12,7 @@ from qiskit.providers.models import BackendProperties from qiskit.transpiler.target import Target - from .visualization.search_visualizer import SearchVisualizer + from .visualization import SearchVisualizer from .load_architecture import load_architecture from .load_calibration import load_calibration diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py index fd2bb94cf..a79a1b420 100644 --- a/src/mqt/qmap/visualization/search_visualizer.py +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -27,7 +27,7 @@ def __init__(self, data_logging_path: str | None = None) -> None: Defaults to None, in which case a temporary folder will be created. """ if data_logging_path is not None: - self.data_logging_path: str | None = data_logging_path + self.data_logging_path = data_logging_path self.data_logging_tmp_dir: TemporaryDirectory[str] | None = None else: self.data_logging_tmp_dir = TemporaryDirectory() From 7b7d43e4ab821e87b8aa9848d3a25f64118b4a13 Mon Sep 17 00:00:00 2001 From: Elias Foramitti Date: Fri, 22 Dec 2023 00:25:30 +0100 Subject: [PATCH 106/108] readd data_logging_path type hint --- src/mqt/qmap/visualization/search_visualizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/qmap/visualization/search_visualizer.py b/src/mqt/qmap/visualization/search_visualizer.py index a79a1b420..fd2bb94cf 100644 --- a/src/mqt/qmap/visualization/search_visualizer.py +++ b/src/mqt/qmap/visualization/search_visualizer.py @@ -27,7 +27,7 @@ def __init__(self, data_logging_path: str | None = None) -> None: Defaults to None, in which case a temporary folder will be created. """ if data_logging_path is not None: - self.data_logging_path = data_logging_path + self.data_logging_path: str | None = data_logging_path self.data_logging_tmp_dir: TemporaryDirectory[str] | None = None else: self.data_logging_tmp_dir = TemporaryDirectory() From 82c51f0cad96abf5a6feeffb89ad03b9ac2f0bdd Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 22 Dec 2023 20:03:55 +0100 Subject: [PATCH 107/108] =?UTF-8?q?=F0=9F=94=A7=20ensure=20Graphviz=20is?= =?UTF-8?q?=20installed=20in=20the=20RtD=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .readthedocs.yaml | 2 ++ pyproject.toml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 8585a89b1..99421e561 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -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" diff --git a/pyproject.toml b/pyproject.toml index 78c6b120b..a38480d67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,6 @@ visualization = [ "networkx>=2.5", "walkerlayout>=1.0.2", "ipywidgets>=7.6.5", - "graphviz" ] dev = ["mqt.qmap[coverage, docs]"] From aebd2050c7d9c903808100d20ef5b2464ac783b5 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 22 Dec 2023 20:38:48 +0100 Subject: [PATCH 108/108] =?UTF-8?q?=F0=9F=93=9D=20add=20back=20note=20abou?= =?UTF-8?q?t=20graphviz?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/library/Visualization.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/source/library/Visualization.rst b/docs/source/library/Visualization.rst index cae23bd8e..5cb1859b0 100644 --- a/docs/source/library/Visualization.rst +++ b/docs/source/library/Visualization.rst @@ -15,6 +15,13 @@ The recommended way to visualize search graphs of a mapping process is via a :cl .. 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 `_ 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: