Skip to content
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ releases may include breaking changes.

### Added

- ✨ Add progressive native targeting (`QCOProgram::targetNative` / Python
`target_native` / `target_device`, `mqt-cc --coupling-map`) ([#1969])
([**@simon1hofmann**])
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- ✨ Add binary-safe QDMI program submission and retrieval to FoMaC ([#1957])
([**@burgholzer**])
- ✨ Add versioned, relocatable configuration and stable-ID registration for
Expand Down Expand Up @@ -672,6 +675,7 @@ changelogs._

<!-- PR links -->

[#1969]: https://github.com/munich-quantum-toolkit/core/pull/1969
[#1965]: https://github.com/munich-quantum-toolkit/core/pull/1965
[#1961]: https://github.com/munich-quantum-toolkit/core/pull/1961
[#1957]: https://github.com/munich-quantum-toolkit/core/pull/1957
Expand Down
103 changes: 99 additions & 4 deletions bindings/mlir/register_mlir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

#include "ir/QuantumComputation.hpp"
#include "mlir/Compiler/Programs.h"
#include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h"

#include <mlir/Support/LLVM.h>
#include <nanobind/nanobind.h>
#include <nanobind/stl/filesystem.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/pair.h> // NOLINT(misc-include-cleaner)
Expand Down Expand Up @@ -237,6 +239,48 @@ compileProgram(const nb::object& program, const mlir::ProgramFormat output,
enableStatistics));
}

[[nodiscard]] std::string
nativeGatesMenuOrThrow(const std::vector<std::string>& names) {
mlir::SmallVector<mlir::StringRef> refs;
refs.reserve(names.size());
for (const auto& name : names) {
refs.emplace_back(name);
}
const auto gateset =
mlir::qco::decomposition::NativeGateset::fromOperationNames(refs);
if (!gateset) {
throw nb::value_error(
"cannot derive a supported native-gates menu from the given "
"operation names");
}
return gateset->toMenuString();
}

[[nodiscard]] std::string nativeGatesMenuFromDevice(const nb::object& device) {
std::vector<std::string> names;
for (const auto& op : device.attr("operations")()) {
names.push_back(nb::cast<std::string>(nb::handle(op).attr("name")()));
}
return nativeGatesMenuOrThrow(names);
}

[[nodiscard]] std::vector<std::pair<size_t, size_t>>
couplingFromDevice(const nb::object& device) {
std::vector<std::pair<size_t, size_t>> edges;
const nb::object cmap = device.attr("coupling_map")();
if (cmap.is_none()) {
return edges;
}
for (const auto& pair : cmap) {
const auto edge = nb::cast<nb::tuple>(nb::handle(pair));
const auto a = static_cast<size_t>(nb::cast<int>(edge[0].attr("index")()));
const auto b = static_cast<size_t>(nb::cast<int>(edge[1].attr("index")()));
edges.emplace_back(a, b);
edges.emplace_back(b, a);
}
return edges;
}

} // namespace

NB_MODULE(MQT_CORE_MODULE_NAME, m) {
Expand Down Expand Up @@ -410,17 +454,43 @@ operations.)pb");
.def(
"place_and_route",
[](mlir::QCOProgram& value,
const std::vector<std::pair<std::size_t, std::size_t>>& coupling,
const std::size_t nlookahead, const float alpha,
const float lambda, const std::size_t niterations,
const std::size_t ntrials, const std::size_t seed) {
const std::vector<std::pair<size_t, size_t>>& coupling,
const size_t nlookahead, const float alpha, const float lambda,
const size_t niterations, const size_t ntrials,
const size_t seed) {
requireSuccess(value.placeAndRoute(std::span(coupling), nlookahead,
alpha, lambda, niterations,
ntrials, seed));
},
"coupling"_a, nb::kw_only(), "nlookahead"_a = 1, "alpha"_a = 1.F,
"lambda_"_a = 0.5F, "niterations"_a = 1, "ntrials"_a = 4,
"seed"_a = 42, "Place and route the program for a coupling graph.")
.def(
"target_native",
[](mlir::QCOProgram& value, const std::string& nativeGates,
const nb::object& coupling) {
if (coupling.is_none()) {
requireSuccess(value.targetNative(nativeGates));
} else {
const auto edges =
nb::cast<std::vector<std::pair<size_t, size_t>>>(coupling);
requireSuccess(value.targetNative(nativeGates, std::span(edges)));
}
},
nb::kw_only(), "native_gates"_a, "coupling"_a = nb::none(),
"Decompose multi-controlled gates, optionally place/route, then fuse "
"to "
"native_gates.")
.def(
"target_device",
[](mlir::QCOProgram& value, const nb::object& device) {
const auto menu = nativeGatesMenuFromDevice(device);
const auto coupling = couplingFromDevice(device);
requireSuccess(value.targetNative(menu, std::span(coupling)));
},
"device"_a,
"Target a FoMaC device: derive native menu and coupling, then run "
"target_native.")
.def(
"to_qc",
[](mlir::QCOProgram& value, const bool copy) {
Expand Down Expand Up @@ -513,6 +583,31 @@ LLVM bitcode.)pb");
&BooleanMemberAdapter<&mlir::QIRProgram::writeBitcode>::call,
"path"_a, "Write this program as LLVM bitcode.");

m.def(
"native_gates_from_operation_names", &nativeGatesMenuOrThrow, "names"_a,
R"pb(Derive a comma-separated native-gates menu from operation name strings.

Args:
names: Operation name strings (aliases such as ``u3`` / ``cnot`` are normalized).

Returns:
Comma-separated native gate menu string.

Raises:
ValueError: When no supported menu can be derived.)pb");

m.def("native_gates_from_device", &nativeGatesMenuFromDevice, "device"_a,
R"pb(Derive a comma-separated native-gates menu from a FoMaC device.

Args:
device: A FoMaC device exposing ``operations()`` with ``name()``.

Returns:
Comma-separated native gate menu string.

Raises:
ValueError: When no supported menu can be derived.)pb");

m.def("compile_program", &compileProgram, "program"_a, nb::kw_only(),
"output"_a = mlir::ProgramFormat::QC, "inplace"_a = false,
"qco_pipeline"_a = "mqt-qco-default", "enable_timing"_a = false,
Expand Down
9 changes: 9 additions & 0 deletions bindings/patterns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,12 @@ mqt.core.mlir.compile_program:
enable_statistics: bool = False,
) -> QCProgram | QCOProgram | JeffProgram | QIRProgram:
\doc

mqt.core.mlir.QCOProgram.target_native:
def target_native(
self,
*,
native_gates: str,
coupling: Sequence[tuple[int, int]] | None = None,
) -> None:
\doc
16 changes: 12 additions & 4 deletions mlir/include/mlir/Compiler/Programs.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,18 @@ class QCOProgram final : public Program {

/// Place and route the program on a coupling graph.
[[nodiscard]] bool
placeAndRoute(std::span<const std::pair<std::size_t, std::size_t>> coupling,
std::size_t nlookahead = 1, float alpha = 1.F,
float lambda = 0.5F, std::size_t niterations = 1,
std::size_t ntrials = 4, std::size_t seed = 42);
placeAndRoute(std::span<const std::pair<size_t, size_t>> coupling,
size_t nlookahead = 1, float alpha = 1.F, float lambda = 0.5F,
size_t niterations = 1, size_t ntrials = 4, size_t seed = 42);

/// Progressive native targeting: decompose multi-controlled gates,
/// optionally place/route on @p coupling (treated as undirected; reverse
/// edges are added automatically), then fuse to @p nativeGates (required,
/// non-empty, and must parse as a supported native menu). The menu is
/// validated before any IR mutation.
[[nodiscard]] bool
targetNative(std::string_view nativeGates,
std::span<const std::pair<size_t, size_t>> coupling = {});

/// Consume this program and convert it to QC.
[[nodiscard]] std::optional<QCProgram> intoQC() &&;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
#include "mlir/Dialect/QCO/Transforms/Decomposition/Euler.h"
#include "mlir/Dialect/QCO/Utils/Matrix.h"

#include <llvm/ADT/DenseSet.h>
#include <mlir/Support/LLVM.h>

#include <cstdint>
#include <optional>
#include <string>

namespace mlir {
class Operation;
Expand Down Expand Up @@ -57,7 +58,7 @@ struct TwoQubitNativeDecomposition;
* `rxx`/`ryy`/`rzx`/`rzz` at a fixed angle of π/2.
*/
struct NativeGateset {
llvm::DenseSet<NativeGateKind> gates;
DenseSet<NativeGateKind> gates;
std::optional<EulerBasis> eulerBasis;
std::optional<NativeGateKind> entangler;

Expand All @@ -70,6 +71,26 @@ struct NativeGateset {
[[nodiscard]] static std::optional<NativeGateset>
parse(StringRef nativeGates);

/**
* @brief Builds a gateset from device/backend operation names.
*
* Normalizes known aliases, ignores unrecognized names, and resolves the
* Euler basis and entangler with the same priority as @ref parse. The
* resulting @p gates set contains only the selected strategy tokens.
*
* @return Resolved gateset, or `std::nullopt` when no supported menu exists.
*/
[[nodiscard]] static std::optional<NativeGateset>
fromOperationNames(ArrayRef<StringRef> names);

/**
* @brief Comma-separated menu for the selected Euler factors and entangler.
*
* Token order is deterministic (Euler constituents, then entangler), e.g.
* `"x,sx,rz,cz"`, `"u,rxx"`, or `"u,ecr"`.
*/
[[nodiscard]] std::string toMenuString() const;

/**
* @brief Basis decomposition of @p target under this gateset, if supported.
*/
Expand Down
44 changes: 39 additions & 5 deletions mlir/lib/Compiler/Programs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h"
#include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h"
#include "mlir/Dialect/QCO/IR/QCODialect.h"
#include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h"
#include "mlir/Dialect/QCO/Transforms/Mapping/Mapping.h"
#include "mlir/Dialect/QCO/Transforms/Passes.h"
#include "mlir/Dialect/QTensor/IR/QTensorDialect.h"
Expand Down Expand Up @@ -392,11 +393,10 @@ bool QCOProgram::decomposeMultiControlled(const uint64_t minControls) {
}

bool QCOProgram::placeAndRoute(
const std::span<const std::pair<std::size_t, std::size_t>> coupling,
const std::size_t nlookahead, const float alpha, const float lambda,
const std::size_t niterations, const std::size_t ntrials,
const std::size_t seed) {
DenseSet<std::pair<std::size_t, std::size_t>> couplingSet;
const std::span<const std::pair<size_t, size_t>> coupling,
const size_t nlookahead, const float alpha, const float lambda,
const size_t niterations, const size_t ntrials, const size_t seed) {
DenseSet<std::pair<size_t, size_t>> couplingSet;
couplingSet.insert(coupling.begin(), coupling.end());
qco::MappingPassOptions options;
options.nlookahead = nlookahead;
Expand All @@ -413,6 +413,40 @@ bool QCOProgram::placeAndRoute(
"failed to place and route the QCO program"));
}

bool QCOProgram::targetNative(
const std::string_view nativeGates,
const std::span<const std::pair<size_t, size_t>> coupling) {
if (StringRef(nativeGates).trim().empty()) {
mod().emitError("the native gate menu must not be empty");
return false;
}
if (!qco::decomposition::NativeGateset::parse(nativeGates).has_value()) {
mod().emitError("unsupported native gate menu '")
<< nativeGates
<< "' (expected a recognised Euler basis plus one entangler)";
return false;
}
if (!decomposeMultiControlled(/*minControls=*/2)) {
return false;
}
if (!coupling.empty()) {
// Treat coupling as undirected: placeAndRoute requires both (u,v) and
// (v,u).
SmallVector<std::pair<size_t, size_t>> symmetric;
symmetric.reserve(coupling.size() * 2);
for (const auto& [u, v] : coupling) {
symmetric.emplace_back(u, v);
if (u != v) {
symmetric.emplace_back(v, u);
}
}
if (!placeAndRoute(symmetric)) {
return false;
}
}
return fuseTwoQubitUnitaryRuns(nativeGates);
}

std::optional<QCProgram> QCOProgram::intoQC() && {
if (failed(runPasses(
mod(), [](OpPassManager& pm) { pm.addPass(createQCOToQC()); },
Expand Down
Loading
Loading