Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 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
3 changes: 3 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`)
([**@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
94 changes: 90 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,22 @@ 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,
"Derive a comma-separated native-gates menu from operation name "
"strings.");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

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