diff --git a/CHANGELOG.md b/CHANGELOG.md index 66fc7b4bdf..1359f94da1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ releases may include breaking changes. ### Added +- ✨ Integrate QDMI into the MLIR Compiler Collection ([#1687]) + ([**@MatthiasReumann**]) - ✨ Add support for QDMI child devices to the driver and FoMaC libraries ([#1897]) ([**@burgholzer**]) - ✨ Add typed custom property and result queries to the C++ and Python FoMaC @@ -692,6 +694,7 @@ changelogs._ [#1702]: https://github.com/munich-quantum-toolkit/core/pull/1702 [#1700]: https://github.com/munich-quantum-toolkit/core/pull/1700 [#1694]: https://github.com/munich-quantum-toolkit/core/pull/1694 +[#1687]: https://github.com/munich-quantum-toolkit/core/pull/1687 [#1676]: https://github.com/munich-quantum-toolkit/core/pull/1676 [#1675]: https://github.com/munich-quantum-toolkit/core/pull/1675 [#1674]: https://github.com/munich-quantum-toolkit/core/pull/1674 diff --git a/mlir/include/mlir/Compiler/CompilerPipeline.h b/mlir/include/mlir/Compiler/CompilerPipeline.h index 54d9f039de..aeb09ef0bb 100644 --- a/mlir/include/mlir/Compiler/CompilerPipeline.h +++ b/mlir/include/mlir/Compiler/CompilerPipeline.h @@ -10,9 +10,12 @@ #pragma once +#include "fomac/FoMaC.hpp" + #include #include +#include #include namespace mlir { @@ -49,6 +52,9 @@ struct QuantumCompilerConfig { /// Enable Hadamard lifting bool enableHadamardLifting = false; + + /// Pointer to QDMI Device to compile to. + std::shared_ptr device; }; /** @@ -66,6 +72,8 @@ struct CompilationRecord { std::string afterQCOCanon; std::string afterOptimization; std::string afterOptimizationCanon; + std::string afterTranspilation; + std::string afterTranspilationCanon; std::string afterQCConversion; std::string afterQCCanon; std::string afterQIRConversion; @@ -86,10 +94,12 @@ struct CompilationRecord { * 4. QCO cleanup pipeline * 5. Quantum optimization passes * 6. QCO cleanup pipeline - * 7. QC dialect - converted back for backend lowering - * 8. QC cleanup pipeline - * 9. QIR (Quantum Intermediate Representation) - optional final lowering - * 10. QIR cleanup pipeline + * 7. Quantum transpilation passes - optional fit to hardware constraints + * 8. QCO cleanup pipeline + * 9. QC dialect - converted back for backend lowering + * 10. QC cleanup pipeline + * 11. QIR (Quantum Intermediate Representation) - optional final lowering + * 12. QIR cleanup pipeline * * Following MLIR best practices, simplification and dead-value cleanup are * run after each major transformation stage. diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Mapping.h b/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Mapping.h index 544f1872c7..b803042514 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Mapping.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Mapping.h @@ -11,6 +11,7 @@ #pragma once #include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Support/SuperconductingDevice.h" #include #include @@ -20,12 +21,8 @@ namespace mlir::qco { -/** - * @brief Create a mapping pass instance with the given target architecture. - * @returns a pass object. - */ -std::unique_ptr -createMappingPass(const llvm::DenseSet>&, - MappingPassOptions); +/// Create a superconducting mapping pass instance. +std::unique_ptr createMappingPass(std::shared_ptr, + MappingPassOptions); } // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/Utils/Graph.h b/mlir/include/mlir/Support/Graph.h similarity index 89% rename from mlir/include/mlir/Dialect/QCO/Utils/Graph.h rename to mlir/include/mlir/Support/Graph.h index 6cd25c8144..c96bcdb329 100644 --- a/mlir/include/mlir/Dialect/QCO/Utils/Graph.h +++ b/mlir/include/mlir/Support/Graph.h @@ -22,7 +22,7 @@ #include #include -namespace mlir::qco { +namespace mlir { /// A directed graph. class Graph { @@ -61,6 +61,13 @@ class Graph { for_each(edges, [this](const auto& e) { addEdge(e.first, e.second); }); } + /// Construct graph from node identifiers and edge set. + explicit Graph(ArrayRef nodes, + const llvm::DenseSet>& edges) { + for_each(nodes, [this](const auto u) { std::ignore = adj_[u]; }); + for_each(edges, [this](const auto& e) { addEdge(e.first, e.second); }); + } + /// Add a directed edge to the internal representation of the graph. /// Implicitly adds nodes. void addEdge(size_t u, size_t v); @@ -105,4 +112,4 @@ class Graph { private: llvm::DenseMap> adj_; }; -} // namespace mlir::qco +} // namespace mlir diff --git a/mlir/include/mlir/Support/Qdmi.h b/mlir/include/mlir/Support/Qdmi.h new file mode 100644 index 0000000000..7af9047033 --- /dev/null +++ b/mlir/include/mlir/Support/Qdmi.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "fomac/FoMaC.hpp" +#include "qdmi/driver/Driver.hpp" + +#include +#include + +#include + +namespace mlir::qdmi { +namespace impl { + +struct DeviceConfig { + /// Library name/path + std::string libName; + /// Prefix for function names + std::string prefix; + /// Device session configuration + ::qdmi::DeviceSessionConfig deviceConfig; +}; + +struct Config { + /// Read and parse the given QDMI JSON config file. + /// The function expects the following JSON structure, where some attributes + /// may be omitted depending on the use-case. + /// { + /// "session": { + /// "token": "...", + /// "authFile": "...", + /// "authUrl": "...", + /// "username": "...", + /// "password": "...", + /// "projectId": "...", + /// "custom1": "...", + /// "custom2": "...", + /// "custom3": "...", + /// "custom4": "...", + /// "custom5": "..." + /// }, + /// "devices": [ + /// { + /// "libName": "...", + /// "prefix": "...", + /// "deviceConfig": { + /// "baseUrl": "...", + /// "token": "...", + /// "authFile": "...", + /// "authUrl": "...", + /// "username": "...", + /// "password": "...", + /// "custom1": "...", + /// "custom2": "...", + /// "custom3": "...", + /// "custom4": "...", + /// "custom5": "..." + /// } + /// } + /// ] + /// } + static Config read(StringRef path); + + fomac::SessionConfig session; + SmallVector devices; +}; +} // namespace impl + +/// Return parameterized session. +/// Applies the provided session config and loads the specified QDMI devices. +fomac::Session prepareSession(StringRef configPath); + +/// Return unparameterized session. +/// Applies default session config and does not load any dynamic QDMI devices. +fomac::Session prepareSession(); + +/// Output a list of all available QDMI devices to @p os. +void listAvailableDevices(fomac::Session& session, + llvm::raw_ostream& os = llvm::outs()); + +/// Find a QDMI device with the given name and return its FoMaC class. +std::shared_ptr getDevice(fomac::Session& session, + StringRef name); +} // namespace mlir::qdmi diff --git a/mlir/include/mlir/Support/SuperconductingDevice.h b/mlir/include/mlir/Support/SuperconductingDevice.h new file mode 100644 index 0000000000..9a9325c4d9 --- /dev/null +++ b/mlir/include/mlir/Support/SuperconductingDevice.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "fomac/FoMaC.hpp" +#include "mlir/Support/Graph.h" + +#include + +#include +#include + +namespace mlir { +class SuperconductingDevice { +public: + /// Construct a superconducting device from qubits and a coupling map. + /// A coupling is a directed edge from qubit u → v. Thus, for undirected + /// architectures, for each (u, v) the coupling also contains (v, u). + SuperconductingDevice(ArrayRef qubits, + const DenseSet>& coupling) + : coupling_(qubits, coupling), dist_(coupling_.getDistMatrix()), + device_(nullptr) {} + + /// Construct a superconducting device from a QDMI device. + /// Assumes that the given QDMI device is a superconducting device that has + /// a coupling map. + explicit SuperconductingDevice(std::shared_ptr device) + : coupling_(getCouplingGraph(device)), dist_(coupling_.getDistMatrix()), + device_(std::move(device)) {} + + /// Return the device's number of qubits. + [[nodiscard]] size_t nqubits() const; + + /// Return true if two qubits are adjacent. + [[nodiscard]] bool areAdjacent(size_t u, size_t v) const; + + /// Return the length of the shortest path between two qubits. + [[nodiscard]] size_t distanceBetween(size_t u, size_t v) const; + + /// Return the qubit identifiers. + [[nodiscard]] SmallVector qubits() const; + + /// Return all neighbours of a qubit. + [[nodiscard]] ArrayRef neighboursOf(size_t u) const; + + /// Return the max degree (connectivity) of any qubit of the device. + [[nodiscard]] size_t maxDegree() const; + +private: + /// Construct graph object from QDMI device. + static Graph getCouplingGraph(const std::shared_ptr& device); + + Graph coupling_; + Graph::DistanceMatrix dist_; + std::shared_ptr device_; +}; +} // namespace mlir diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 821ccda2ee..7506f9cf5c 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -14,15 +14,18 @@ #include "mlir/Conversion/QCToQCO/QCToQCO.h" #include "mlir/Conversion/QCToQIR/QIRAdaptive/QCToQIRAdaptive.h" #include "mlir/Conversion/QCToQIR/QIRBase/QCToQIRBase.h" +#include "mlir/Dialect/QCO/Transforms/Mapping/Mapping.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Support/Passes.h" #include "mlir/Support/PrettyPrinting.h" +#include "mlir/Support/SuperconductingDevice.h" #include #include #include #include +#include #include namespace mlir { @@ -100,6 +103,9 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (convertToQIR) { totalStages += 2; } + if (config_.device != nullptr) { + totalStages += 2; + } auto currentStage = 0; // Stage 1: QC import @@ -171,11 +177,42 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (record != nullptr && config_.recordIntermediates) { record->afterOptimizationCanon = captureIR(module); if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Final QCO Cleanup", ++currentStage, + prettyPrintStage(module, "Post-Optimization QCO Cleanup", ++currentStage, totalStages); } } - // Stage 7: QCO-to-QC conversion + // Stage 7: Transpilation passes (optional) + // Assume superconducting devices for now. + if (config_.device != nullptr) { + if (failed(runStage([&](PassManager& pm) { + pm.addPass(qco::createMappingPass( + std::make_shared(config_.device), + qco::MappingPassOptions{})); + }))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterTranspilation = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Transpilation Passes", ++currentStage, + totalStages); + } + } + // Stage 8: QCO cleanup (optional) + if (failed(runStage( + [&](PassManager& pm) { populateQCOCleanupPipeline(pm); }))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterTranspilationCanon = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Post-Transpilation QCO Cleanup", + ++currentStage, totalStages); + } + } + } + + // Stage 9: QCO-to-QC conversion if (failed(runStage([&](PassManager& pm) { pm.addPass(createQCOToQC()); }))) { return failure(); } @@ -186,7 +223,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, totalStages); } } - // Stage 8: QC cleanup + // Stage 10: QC cleanup if (failed( runStage([&](PassManager& pm) { populateQCCleanupPipeline(pm); }))) { return failure(); @@ -197,7 +234,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, prettyPrintStage(module, "Final QC Cleanup", ++currentStage, totalStages); } } - // Stage 9: QC-to-QIR conversion (optional) + // Stage 11: QC-to-QIR conversion (optional) if (convertToQIR) { if (failed(runStage([&](PassManager& pm) { if (config_.convertToQIRAdaptive) { @@ -215,7 +252,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, totalStages); } } - // Stage 10: QIR cleanup (optional) + // Stage 12: QIR cleanup (optional) if (failed(runStage([&](PassManager& pm) { populateQIRCleanupPipeline(pm, config_.convertToQIRAdaptive); }))) { diff --git a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt index 77f594db17..1311b62d58 100644 --- a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt @@ -19,6 +19,8 @@ add_mlir_library( MLIRArithDialect MLIRMathDialect MLIRSCFUtils + PUBLIC + MLIRSupportMQT DEPENDS MLIRQCOTransformsIncGen) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 8664675abb..4e99dbac12 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -13,12 +13,13 @@ #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Utils/Drivers.h" -#include "mlir/Dialect/QCO/Utils/Graph.h" #include "mlir/Dialect/QCO/Utils/Layout.h" #include "mlir/Dialect/QCO/Utils/WireIterator.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" #include "mlir/Dialect/QTensor/Utils/TensorIterator.h" #include "mlir/Dialect/Utils/Utils.h" +#include "mlir/Support/Graph.h" +#include "mlir/Support/SuperconductingDevice.h" #include #include @@ -48,7 +49,6 @@ #include #include #include -#include #include #include #include @@ -80,48 +80,6 @@ struct MappingPass : impl::MappingPassBase { enum class RoutingMode : bool { Cold, Hot }; - class AugmentedDevice { - public: - explicit AugmentedDevice( - const DenseSet>& couplingSet) - : coupling_(couplingSet), dist_(coupling_.getDistMatrix()) {} - - /// Return the device's number of qubits. - [[nodiscard]] size_t nqubits() const { return coupling_.getNumNodes(); } - - /// Return true if two qubits are adjacent. - [[nodiscard]] bool areAdjacent(const size_t u, const size_t v) const { - return dist_[u][v] == 1UL; - } - - /// Return the length of the shortest path between two qubits. - [[nodiscard]] size_t distanceBetween(const size_t u, const size_t v) const { - const auto dist = dist_[u][v]; - if (dist == UINT64_MAX) { - report_fatal_error("Failed to compute the distance between qubits " + - Twine(u) + " and " + Twine(v)); - } - return dist; - } - - /// Return the qubit identifiers. - [[nodiscard]] SmallVector qubits() const { - return coupling_.getNodes(); - } - - /// Return all neighbours of a qubit. - [[nodiscard]] ArrayRef neighboursOf(const size_t u) const { - return coupling_.getNeighbours(u); - } - - /// Return the max degree (connectivity) of any qubit of the device. - [[nodiscard]] size_t maxDegree() const { return coupling_.getMaxDegree(); } - - private: - Graph coupling_; - Graph::DistanceMatrix dist_; - }; - struct WireInfos { /// Return the mapped wire index of a program index. [[nodiscard]] size_t lookupIndex(const size_t prog) const { @@ -201,7 +159,7 @@ struct MappingPass : impl::MappingPassBase { /// Construct a non-root node from its parent node. Apply the given swap to /// the layout of the parent node. Node(Node* parent, const IndexPairType& swap, const Window& window, - const AugmentedDevice& device, const Parameters& params) + const SuperconductingDevice& device, const Parameters& params) : layout(parent->layout), swap(swap), parent(parent), depth(parent->depth + 1), f(0) { layout.swap(swap.first, swap.second); @@ -211,7 +169,7 @@ struct MappingPass : impl::MappingPassBase { /// Return true, if the current SWAP sequence makes all gates in the front /// executable. [[nodiscard]] bool isGoal(const IndexPairType& front, - const AugmentedDevice& device) const { + const SuperconductingDevice& device) const { const auto [hw0, hw1] = layout.getHardwareIndices(front.first, front.second); return device.areAdjacent(hw0, hw1); @@ -231,7 +189,8 @@ struct MappingPass : impl::MappingPassBase { /// between its hardware qubits. Intuitively, this is the number of SWAPs /// that a naive router would insert to route the layers (with a constant /// layout). - [[nodiscard]] float h(const Window& window, const AugmentedDevice& device, + [[nodiscard]] float h(const Window& window, + const SuperconductingDevice& device, const Parameters& params) const { float costs{0}; float decay{1.}; @@ -249,7 +208,7 @@ struct MappingPass : impl::MappingPassBase { /// Describes the graph F of arXiv:1602.05150v3. struct FGraph { - explicit FGraph(std::shared_ptr device) + explicit FGraph(std::shared_ptr device) : f_(device->qubits()), device_(std::move(device)) {}; /// Build F-graph: Add edges to F for each edge in the coupling graph. @@ -315,7 +274,7 @@ struct MappingPass : impl::MappingPassBase { } Graph f_; - std::shared_ptr device_; + std::shared_ptr device_; }; public: @@ -326,11 +285,10 @@ struct MappingPass : impl::MappingPassBase { explicit MappingPass(const MappingPassOptions& options) : MappingPassBase(options) {} - /// Construct mapping from coupling set. - explicit MappingPass(const DenseSet>& couplingSet, - const MappingPassOptions& options) - : MappingPassBase(options), - device(std::make_shared(couplingSet)) {} + /// Construct mapping pass from superconducting device. + explicit MappingPass(std::shared_ptr device, + MappingPassOptions options) + : MappingPassBase(options), device(std::move(device)) {} protected: void runOnOperation() override { @@ -365,7 +323,7 @@ struct MappingPass : impl::MappingPassBase { func.emitError() << "requires " + Twine(wires.size()) + " qubits. However, the architecture only supports " + - Twine(device->nqubits()) + "qubits."; + Twine(device->nqubits()) + " qubits."; signalPassFailure(); return; } @@ -1258,32 +1216,15 @@ struct MappingPass : impl::MappingPassBase { return success(); } - std::shared_ptr device; + std::shared_ptr device; }; } // namespace std::unique_ptr -createMappingPass(const DenseSet>& couplingSet, +createMappingPass(std::shared_ptr device, MappingPassOptions options) { - - // Verify the assumption that the coupling set is symmetric: - // For every edge (u, v) in the set, (v, u) must also be present. - - for (const auto& [u, v] : couplingSet) { - if (u == v) { - llvm::reportFatalUsageError("Found an invalid (u, u) edge."); - } - - if (!couplingSet.contains({v, u})) { - llvm::reportFatalUsageError("Expected symmetric coupling set: edge (" + - Twine(u) + ", " + Twine(v) + - ") exists but (" + Twine(v) + ", " + - Twine(u) + ") does not."); - } - } - - return std::make_unique(couplingSet, options); + return std::make_unique(std::move(device), options); } } // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Utils/Graph.cpp b/mlir/lib/Dialect/QCO/Utils/Graph.cpp index 5e3de25add..c24600b6ab 100644 --- a/mlir/lib/Dialect/QCO/Utils/Graph.cpp +++ b/mlir/lib/Dialect/QCO/Utils/Graph.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/QCO/Utils/Graph.h" +#include "mlir/Support/Graph.h" #include #include @@ -23,7 +23,7 @@ #include #include -namespace mlir::qco { +namespace mlir { void Graph::addEdge(size_t u, size_t v) { adj_[u].emplace_back(v); std::ignore = adj_[v]; // Ensure v exists in the map. @@ -145,4 +145,4 @@ std::optional> Graph::findCycle() const { return std::nullopt; } -} // namespace mlir::qco +} // namespace mlir diff --git a/mlir/lib/Support/CMakeLists.txt b/mlir/lib/Support/CMakeLists.txt index 462d791329..699395a451 100644 --- a/mlir/lib/Support/CMakeLists.txt +++ b/mlir/lib/Support/CMakeLists.txt @@ -6,14 +6,16 @@ # # Licensed under the MIT License +file(GLOB_RECURSE SUPPORT_CPP CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") + add_mlir_library( MLIRSupportMQT - IRVerification.cpp - Passes.cpp - PrettyPrinting.cpp + ${SUPPORT_CPP} ADDITIONAL_HEADER_DIRS ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Support LINK_LIBS + PRIVATE + nlohmann_json::nlohmann_json PUBLIC MLIRSupport MLIRIR @@ -27,7 +29,8 @@ add_mlir_library( MLIRQCTransforms MLIRQIRTransforms MLIRQTensorTransforms - MLIRQTensorDialect) + MLIRQTensorDialect + MQT::CoreFoMaC) mqt_mlir_target_use_project_options(MLIRSupportMQT) diff --git a/mlir/lib/Support/Qdmi.cpp b/mlir/lib/Support/Qdmi.cpp new file mode 100644 index 0000000000..a290f59d94 --- /dev/null +++ b/mlir/lib/Support/Qdmi.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Support/Qdmi.h" + +#include "fomac/FoMaC.hpp" +#include "qdmi/driver/Driver.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +template +static void getJsonStringIfExists(const nlohmann::json& jsonObj, + const std::string& field, T& target) { + if (jsonObj.contains(field) && jsonObj[field].is_string()) { + target = jsonObj[field].get(); + } +} + +mlir::qdmi::impl::Config mlir::qdmi::impl::Config::read(StringRef path) { + using json = nlohmann::json; + + llvm::ErrorOr> fileOrErr = + llvm::MemoryBuffer::getFile(path); + if (!fileOrErr) { + llvm::errs() << "Failed to open QDMI config file: " << path << "\n"; + return {}; + } + + json j; + try { + std::string content(fileOrErr->get()->getBufferStart(), + fileOrErr->get()->getBufferSize()); + j = json::parse(content); + } catch (const json::parse_error& e) { + llvm::errs() << "Failed to parse QDMI config file '" << path + << "': " << e.what() << "\n"; + return {}; + } catch (const std::exception& e) { + llvm::errs() << "Error reading QDMI config file '" << path + << "': " << e.what() << "\n"; + return {}; + } + + Config config; + + if (j.is_object() && j.contains("session")) { + getJsonStringIfExists(j["session"], "token", config.session.token); + getJsonStringIfExists(j["session"], "authFile", config.session.authFile); + getJsonStringIfExists(j["session"], "authUrl", config.session.authUrl); + getJsonStringIfExists(j["session"], "username", config.session.username); + getJsonStringIfExists(j["session"], "password", config.session.password); + getJsonStringIfExists(j["session"], "projectId", config.session.projectId); + getJsonStringIfExists(j["session"], "custom1", config.session.custom1); + getJsonStringIfExists(j["session"], "custom2", config.session.custom2); + getJsonStringIfExists(j["session"], "custom3", config.session.custom3); + getJsonStringIfExists(j["session"], "custom4", config.session.custom4); + getJsonStringIfExists(j["session"], "custom5", config.session.custom5); + } + + // Parse devices array + if (j.contains("devices") && j["devices"].is_array()) { + for (const auto& deviceJson : j["devices"]) { + if (!deviceJson.contains("libName") || + !deviceJson["libName"].is_string() || + !deviceJson.contains("prefix") || !deviceJson["prefix"].is_string()) { + continue; // Skip malformed device entries. + } + + impl::DeviceConfig device; + device.libName = deviceJson["libName"].get(); + device.prefix = deviceJson["prefix"].get(); + + // Parse deviceConfig + if (deviceJson.contains("deviceConfig") && + deviceJson["deviceConfig"].is_object()) { + const auto& dc = deviceJson["deviceConfig"]; + getJsonStringIfExists(dc, "baseUrl", device.deviceConfig.baseUrl); + getJsonStringIfExists(dc, "token", device.deviceConfig.token); + getJsonStringIfExists(dc, "authFile", device.deviceConfig.authFile); + getJsonStringIfExists(dc, "authUrl", device.deviceConfig.authUrl); + getJsonStringIfExists(dc, "username", device.deviceConfig.username); + getJsonStringIfExists(dc, "password", device.deviceConfig.password); + getJsonStringIfExists(dc, "custom1", device.deviceConfig.custom1); + getJsonStringIfExists(dc, "custom2", device.deviceConfig.custom2); + getJsonStringIfExists(dc, "custom3", device.deviceConfig.custom3); + getJsonStringIfExists(dc, "custom4", device.deviceConfig.custom4); + getJsonStringIfExists(dc, "custom5", device.deviceConfig.custom5); + } + + config.devices.push_back(device); + } + } + + return config; +} + +void mlir::qdmi::listAvailableDevices(fomac::Session& session, + llvm::raw_ostream& os) { + os << "Available QDMI devices:\n"; + for (const auto& dev : session.getDevices()) { + os << '\t' << dev.getName() << '\n'; + } +} + +std::shared_ptr mlir::qdmi::getDevice(fomac::Session& session, + StringRef name) { + const auto devices = session.getDevices(); + const auto it = std::ranges::find_if( + devices, [&](const auto& dev) { return dev.getName() == name; }); + return it != devices.end() ? std::make_shared(*it) : nullptr; +} + +fomac::Session mlir::qdmi::prepareSession(StringRef configPath) { + const auto config = impl::Config::read(configPath); + + // Load dynamic device libraries from config. + for (const auto& device : config.devices) { + ::qdmi::Driver::get().addDynamicDeviceLibrary(device.libName, device.prefix, + device.deviceConfig); + } + + return fomac::Session(config.session); +} + +fomac::Session mlir::qdmi::prepareSession() { return fomac::Session(); } diff --git a/mlir/lib/Support/SuperconductingDevice.cpp b/mlir/lib/Support/SuperconductingDevice.cpp new file mode 100644 index 0000000000..f09439f11a --- /dev/null +++ b/mlir/lib/Support/SuperconductingDevice.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Support/SuperconductingDevice.h" + +#include "fomac/FoMaC.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace mlir; + +size_t SuperconductingDevice::nqubits() const { + return coupling_.getNumNodes(); +} + +bool SuperconductingDevice::areAdjacent(size_t u, size_t v) const { + return dist_[u][v] == 1UL; +} + +size_t SuperconductingDevice::distanceBetween(size_t u, size_t v) const { + const auto dist = dist_[u][v]; + if (dist == std::numeric_limits::max()) { + report_fatal_error("Failed to compute the distance between qubits " + + Twine(u) + " and " + Twine(v)); + } + return dist; +} + +SmallVector SuperconductingDevice::qubits() const { + return coupling_.getNodes(); +} + +ArrayRef SuperconductingDevice::neighboursOf(size_t u) const { + return coupling_.getNeighbours(u); +} + +size_t SuperconductingDevice::maxDegree() const { + return coupling_.getMaxDegree(); +} + +Graph SuperconductingDevice::getCouplingGraph( + const std::shared_ptr& device) { + const auto sites = device->getSites(); + const auto siteCoupling = device->getCouplingMap(); + assert(siteCoupling.has_value() && + "expected QDMI device with a coupling map"); + + // Construct index-type based qubit vector and coupling set from QDMI sites. + // TODO: Does QDMI assume undirected edges? + + // As we can't guarantee that the site indices form a consecutive range, remap + // the indices to [0, sites.size()). + + DenseMap mapping; + mapping.reserve(sites.size()); + for (const auto [i, site] : llvm::enumerate(sites)) { + mapping.try_emplace(site.getIndex(), i); + } + + DenseSet> coupling; + coupling.reserve(siteCoupling->size()); + coupling.insert_range(llvm::map_range(*siteCoupling, [&](const auto& pair) { + const auto& [s0, s1] = pair; + return std::make_pair(mapping.at(s0.getIndex()), mapping.at(s1.getIndex())); + })); + + return Graph(to_vector(mapping.values()), coupling); +} diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index fb52a87f25..d64bdf9ff5 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -13,6 +13,7 @@ #include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" +#include "mlir/Support/Qdmi.h" #include #include @@ -43,6 +44,7 @@ #include #include +#include #include #include @@ -98,6 +100,21 @@ static llvm::cl::opt enableHadamardLifting( llvm::cl::desc("Apply Hadamard lifting during optimization"), llvm::cl::init(false)); +static llvm::cl::opt + qdmiListDevices("qdmi-list-devices", + llvm::cl::desc("List all available devices via QDMI"), + llvm::cl::init(false)); + +static llvm::cl::opt> + qdmiDevice("qdmi-device", + llvm::cl::desc("Specify a target device via QDMI"), + llvm::cl::init(std::nullopt)); + +static llvm::cl::opt> qdmiConfigPath( + "qdmi-config", + llvm::cl::desc("Specify the path to a JSON configuration file for QDMI"), + llvm::cl::init(std::nullopt)); + /** * @brief Load and parse a `.qasm` file */ @@ -179,6 +196,39 @@ int main(int argc, char** argv) { llvm::cl::ParseCommandLineOptions(argc, argv, "MQT Compiler Collection Driver\n"); + // Configure the compiler pipeline + QuantumCompilerConfig config; + config.convertToQIRBase = convertToQIRBase; + config.convertToQIRAdaptive = convertToQIRAdaptive; + config.recordIntermediates = recordIntermediates; + config.enableTiming = enableTiming; + config.enableStatistics = enableStatistics; + config.printIRAfterAllStages = printIRAfterAllStages; + config.disableMergeSingleQubitRotationGates = + disableMergeSingleQubitRotationGates; + config.enableHadamardLifting = enableHadamardLifting; + + auto session = [&] { + if (qdmiConfigPath) { + return mlir::qdmi::prepareSession(*qdmiConfigPath); + } + return mlir::qdmi::prepareSession(); + }(); + + if (qdmiListDevices) { + mlir::qdmi::listAvailableDevices(session); + return 0; + } + + if (qdmiDevice != std::nullopt) { + config.device = mlir::qdmi::getDevice(session, *qdmiDevice); + if (!config.device) { + llvm::errs() << "Device '" << qdmiDevice << "' not found!\n"; + mlir::qdmi::listAvailableDevices(session, llvm::errs()); + return 1; + } + } + // Set up MLIR context with all required dialects DialectRegistry registry; registry @@ -202,18 +252,6 @@ int main(int argc, char** argv) { return 1; } - // Configure the compiler pipeline - QuantumCompilerConfig config; - config.convertToQIRBase = convertToQIRBase; - config.convertToQIRAdaptive = convertToQIRAdaptive; - config.recordIntermediates = recordIntermediates; - config.enableTiming = enableTiming; - config.enableStatistics = enableStatistics; - config.printIRAfterAllStages = printIRAfterAllStages; - config.disableMergeSingleQubitRotationGates = - disableMergeSingleQubitRotationGates; - config.enableHadamardLifting = enableHadamardLifting; - // Run the compilation pipeline CompilationRecord record; if (const QuantumCompilerPipeline pipeline(config); @@ -235,6 +273,10 @@ int main(int argc, char** argv) { llvm::outs() << "After Optimization:\n" << record.afterOptimization << "\n"; llvm::outs() << "After Final QCO Canonicalization:\n" << record.afterOptimizationCanon << "\n"; + llvm::outs() << "After Transpilation:\n" + << record.afterTranspilation << "\n"; + llvm::outs() << "After Transpilation Canonicalization:\n" + << record.afterTranspilationCanon << "\n"; llvm::outs() << "After QCO-to-QC Conversion:\n" << record.afterQCConversion << "\n"; llvm::outs() << "After Final QC Canonicalization:\n" diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 3153febf45..532ba44f58 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -15,6 +15,7 @@ #include "mlir/Dialect/QCO/Transforms/Mapping/Mapping.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Dialect/Utils/Utils.h" +#include "mlir/Support/SuperconductingDevice.h" #include #include @@ -39,6 +40,7 @@ #include #include #include +#include #include #include @@ -46,18 +48,10 @@ using namespace mlir; using namespace mlir::qco; using namespace mlir::utils; -namespace { -struct Device { - size_t nqubits{}; - DenseSet> couplingSet; -}; -} // namespace - /// Return true, if the operations within a region fulfill the given coupling /// constraints. -static bool -isExecutable(Region& body, DenseMap& m, - const DenseSet>& couplingSet) { +static bool isExecutable(Region& body, DenseMap& m, + const std::shared_ptr& device) { for (Operation& rop : body.getOps()) { bool executable = true; TypeSwitch(&rop) @@ -76,7 +70,7 @@ isExecutable(Region& body, DenseMap& m, if (op.getNumQubits() > 1) { const auto hwA = m.at(op.getInputQubit(0)); const auto hwB = m.at(op.getInputQubit(1)); - if (!couplingSet.contains(std::make_pair(hwA, hwB))) { + if (!device->areAdjacent(hwA, hwB)) { llvm::dbgs() << "(" << hwA << ", " << hwB << ") " << "not executable: \n"; op->dump(); @@ -105,7 +99,7 @@ isExecutable(Region& body, DenseMap& m, m.try_emplace(succ, hw); } - if (!isExecutable(forOp.getRegion(), loopM, couplingSet)) { + if (!isExecutable(forOp.getRegion(), loopM, device)) { executable = false; return; } @@ -145,7 +139,7 @@ isExecutable(Region& body, DenseMap& m, for (size_t i = 0; i < 2; ++i) { Region* body = regions[i]; - if (!isExecutable(*body, mappings[i], couplingSet)) { + if (!isExecutable(*body, mappings[i], device)) { executable = false; return; } @@ -178,26 +172,30 @@ isExecutable(Region& body, DenseMap& m, } /// Return true, if the entry point fulfills the given coupling constraints. -static bool -isExecutable(func::FuncOp entry, - const DenseSet>& couplingSet) { +static bool isExecutable(func::FuncOp entry, + const std::shared_ptr& device) { DenseMap m; - return isExecutable(entry.getFunctionBody(), m, couplingSet); + return isExecutable(entry.getFunctionBody(), m, device); } -/// Return a 9x9 square-grid coupling set. -static Device getNineQubitSquareGrid() { - return {.nqubits = 9, - .couplingSet = {{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, - {1, 2}, {2, 1}, {2, 5}, {5, 2}, {3, 6}, {6, 3}, - {3, 4}, {4, 3}, {4, 7}, {7, 4}, {4, 5}, {5, 4}, - {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}}; +/// Return a 3x3 square-grid coupling set. +static std::shared_ptr getNineQubitSquareGrid() { + SmallVector qubits(9); + std::iota(qubits.begin(), qubits.end(), 0); + + DenseSet> coupling( + {{0, 3}, {3, 0}, {0, 1}, {1, 0}, {1, 4}, {4, 1}, {1, 2}, {2, 1}, + {2, 5}, {5, 2}, {3, 6}, {6, 3}, {3, 4}, {4, 3}, {4, 7}, {7, 4}, + {4, 5}, {5, 4}, {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}); + + return std::make_shared(qubits, coupling); } namespace { class MappingPassTest : public testing::Test, - public testing::WithParamInterface { + public testing::WithParamInterface< + std::shared_ptr> { protected: void SetUp() override { DialectRegistry registry; @@ -208,14 +206,6 @@ class MappingPassTest : public testing::Test, context->loadAllAvailableDialects(); } - static LogicalResult - runPass(ModuleOp m, const DenseSet>& couplingSet, - const MappingPassOptions& options) { - PassManager pm(m->getContext()); - pm.addPass(createMappingPass(couplingSet, options)); - return pm.run(m); - } - std::unique_ptr context; }; @@ -224,14 +214,19 @@ class MappingPassTest : public testing::Test, TEST_P(MappingPassTest, NoEntryPoint) { const auto& device = GetParam(); + PassManager pm(context.get()); + pm.addPass(createMappingPass(device, MappingPassOptions{})); + OwningOpRef m = ModuleOp::create(UnknownLoc::get(context.get())); - auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); - ASSERT_TRUE(res.failed()); + ASSERT_TRUE(pm.run(*m).failed()); } TEST_P(MappingPassTest, NoQubitAllocations) { const auto& device = GetParam(); + PassManager pm(context.get()); + pm.addPass(createMappingPass(device, MappingPassOptions{})); + QCOProgramBuilder builder(context.get()); builder.initialize({builder.getI1Type()}); @@ -243,14 +238,15 @@ TEST_P(MappingPassTest, NoQubitAllocations) { builder.sink(q0); auto m = builder.finalize(c0); - auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); - - ASSERT_TRUE(res.failed()); + ASSERT_TRUE(pm.run(*m).failed()); } TEST_P(MappingPassTest, NoExtractAfterInsert) { const auto& device = GetParam(); + PassManager pm(context.get()); + pm.addPass(createMappingPass(device, MappingPassOptions{})); + QCOProgramBuilder builder(context.get()); builder.initialize({builder.getI1Type()}); @@ -270,14 +266,15 @@ TEST_P(MappingPassTest, NoExtractAfterInsert) { builder.qtensorDealloc(tensor0); auto m = builder.finalize(c0); - auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); - - ASSERT_TRUE(res.failed()); + ASSERT_TRUE(pm.run(*m).failed()); } TEST_P(MappingPassTest, TooManyQubitsForArch) { const auto& device = GetParam(); - const auto size = static_cast(device.nqubits) + 1; + const auto size = static_cast(device->nqubits()) + 1; + + PassManager pm(context.get()); + pm.addPass(createMappingPass(device, MappingPassOptions{})); SmallVector bits(size); SmallVector qubits(size); @@ -300,9 +297,7 @@ TEST_P(MappingPassTest, TooManyQubitsForArch) { builder.qtensorDealloc(tensor); auto m = builder.finalize(bits); - auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); - - ASSERT_TRUE(res.failed()); + ASSERT_TRUE(pm.run(*m).failed()); } TEST_P(MappingPassTest, GHZ) { @@ -312,6 +307,9 @@ TEST_P(MappingPassTest, GHZ) { SmallVector qubits(size); SmallVector bits(size); + PassManager pm(context.get()); + pm.addPass(createMappingPass(device, MappingPassOptions{})); + QCOProgramBuilder builder(context.get()); builder.initialize(SmallVector(3, builder.getI1Type())); @@ -335,16 +333,16 @@ TEST_P(MappingPassTest, GHZ) { builder.qtensorDealloc(tensor); auto m = builder.finalize(bits); - auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); + auto res = pm.run(*m); auto entry = getEntryPoint(m.get()); ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, device.couplingSet)); + EXPECT_TRUE(isExecutable(entry, device)); } TEST_P(MappingPassTest, GHZUnrolled) { const auto& device = GetParam(); - const auto size = static_cast(device.nqubits); + const auto size = static_cast(device->nqubits()); SmallVector bits(size); @@ -352,7 +350,7 @@ TEST_P(MappingPassTest, GHZUnrolled) { pm.addNestedPass(createQuantumLoopUnroll()); pm.addPass(createCSEPass()); pm.addPass(createCanonicalizerPass()); - pm.addPass(createMappingPass(device.couplingSet, MappingPassOptions{})); + pm.addPass(createMappingPass(device, MappingPassOptions{})); QCOProgramBuilder builder(context.get()); builder.initialize(SmallVector(size, builder.getI1Type())); @@ -393,7 +391,7 @@ TEST_P(MappingPassTest, GHZUnrolled) { auto entry = getEntryPoint(m.get()); ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, device.couplingSet)); + EXPECT_TRUE(isExecutable(entry, device)); } TEST_P(MappingPassTest, GroverLike) { @@ -404,7 +402,7 @@ TEST_P(MappingPassTest, GroverLike) { SmallVector bits(size); PassManager pm(context.get()); - pm.addPass(createMappingPass(device.couplingSet, MappingPassOptions{})); + pm.addPass(createMappingPass(device, MappingPassOptions{})); QCOProgramBuilder builder(context.get()); builder.initialize(SmallVector(5, builder.getI1Type())); @@ -459,7 +457,7 @@ TEST_P(MappingPassTest, GroverLike) { auto entry = getEntryPoint(m.get()); ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, device.couplingSet)); + EXPECT_TRUE(isExecutable(entry, device)); } TEST_P(MappingPassTest, ParallelLoops) { @@ -470,7 +468,7 @@ TEST_P(MappingPassTest, ParallelLoops) { SmallVector bits(size); PassManager pm(context.get()); - pm.addPass(createMappingPass(device.couplingSet, MappingPassOptions{})); + pm.addPass(createMappingPass(device, MappingPassOptions{})); QCOProgramBuilder builder(context.get()); builder.initialize(SmallVector(size, builder.getI1Type())); @@ -539,7 +537,7 @@ TEST_P(MappingPassTest, ParallelLoops) { auto entry = getEntryPoint(m.get()); ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, device.couplingSet)); + EXPECT_TRUE(isExecutable(entry, device)); } TEST_P(MappingPassTest, Sabre) { @@ -549,6 +547,9 @@ TEST_P(MappingPassTest, Sabre) { SmallVector qubits(size); SmallVector bits(size); + PassManager pm(context.get()); + pm.addPass(createMappingPass(device, MappingPassOptions{})); + QCOProgramBuilder builder(context.get()); builder.initialize(SmallVector(6, builder.getI1Type())); @@ -606,17 +607,20 @@ TEST_P(MappingPassTest, Sabre) { builder.qtensorDealloc(tensorDown); auto m = builder.finalize(bits); - auto res = runPass(m.get(), device.couplingSet, MappingPassOptions{}); + auto res = pm.run(m.get()); auto entry = getEntryPoint(m.get()); ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, device.couplingSet)); + EXPECT_TRUE(isExecutable(entry, device)); } TEST_P(MappingPassTest, RandomOrderGHZ) { const auto& device = GetParam(); constexpr int64_t size = 9; + PassManager pm(context.get()); + pm.addPass(createMappingPass(device, MappingPassOptions{})); + SmallVector qubits(size); SmallVector bits(size); @@ -664,12 +668,11 @@ TEST_P(MappingPassTest, RandomOrderGHZ) { builder.qtensorDealloc(tensor); auto m = builder.finalize(bits); - auto res = - runPass(m.get(), device.couplingSet, MappingPassOptions{.ntrials = 1}); + auto res = pm.run(*m); auto entry = getEntryPoint(m.get()); ASSERT_TRUE(res.succeeded()); - EXPECT_TRUE(isExecutable(entry, device.couplingSet)); + EXPECT_TRUE(isExecutable(entry, device)); } INSTANTIATE_TEST_SUITE_P(NineQubitSquareGrid, MappingPassTest, diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 4b42160f9b..186ac45dce 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -333,6 +333,7 @@ Job Device::submitJob(const std::string& program, QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, program.size() + 1, program.c_str()), "Setting program"); + // Set number of shots qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_SHOTSNUM, sizeof(numShots), &numShots),