Skip to content
Draft
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ releases may include breaking changes.

### Added

- ✨ Add a `fuse-two-qubit-unitary-runs` pass for fusing compile-time two-qubit
unitary windows via Weyl/KAK resynthesis ([#1865]) ([**@simon1hofmann**])
- ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter
([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**])
- 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function
([#1826]) ([**@ystade])
([#1826]) ([**@ystade**])
- 🐳 Add dev container configuration for consistent local development
environment ([#1786]) ([**@denialhaag**])
- ✨ Add two-qubit Weyl (KAK) decomposition and native-gateset synthesis support
Expand Down Expand Up @@ -600,6 +602,7 @@ changelogs._

<!-- PR links -->

[#1865]: https://github.com/munich-quantum-toolkit/core/pull/1865
[#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848
[#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844
[#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842
Expand Down
15 changes: 15 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Euler.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include <cstdint>
#include <optional>

namespace mlir {
class RewritePatternSet;
} // namespace mlir

namespace mlir::qco::decomposition {

/**
Expand Down Expand Up @@ -99,4 +103,15 @@ synthesizeUnitary1QEuler(OpBuilder& builder, Location loc, Value qubit,
*/
void emitGPhaseIfNeeded(OpBuilder& builder, Location loc, double phase);

/**
* @brief Populates @p patterns with the single-qubit run fusion rewrite for
* @p basis (the reusable core of `fuse-single-qubit-unitary-runs`).
*
* @param skipControlledBodies When set, single-qubit gates nested in `qco.ctrl`
* bodies are left untouched.
*/
void populateFuseSingleQubitUnitaryRunsPatterns(
RewritePatternSet& patterns, EulerBasis basis,
bool skipControlledBodies = false);

} // namespace mlir::qco::decomposition
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
#include <cstdint>
#include <optional>

namespace mlir {
class Operation;
} // namespace mlir

namespace mlir::qco::decomposition {

/**
Expand Down Expand Up @@ -63,6 +67,15 @@ struct NativeGateset {
*/
[[nodiscard]] std::optional<TwoQubitNativeDecomposition>
decomposeTarget(const Matrix4x4& target) const;

/**
* @brief Whether @p op is already on this native gateset.
*
* `qco.barrier` and `qco.gphase` are always allowed. Single-qubit primitives
* and single-target `qco.ctrl` shells (with an `X`/`Z` body) are checked
* against @p gates. All other ops are rejected.
*/
[[nodiscard]] bool allowsOp(Operation* op) const;
};

} // namespace mlir::qco::decomposition
51 changes: 51 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,57 @@ def FuseSingleQubitUnitaryRuns
"Target Euler basis (zyz, zxz, xzx, xyx, u, zsxx, r).">];
}

def FuseTwoQubitUnitaryRuns
: Pass<"fuse-two-qubit-unitary-runs", "mlir::ModuleOp"> {
let dependentDialects = ["mlir::qco::QCODialect"];
let summary = "Lower QCO unitary gates to a user-specified native gate menu.";
let description = [{
Rewrites unitaries to match the comma-separated `native-gates` gateset.
`qco.barrier` and `qco.gphase` are preserved. A `qco.ctrl` is treated as a
two-qubit gate only when it has a single control and a single target; gates
acting on more than two qubits (e.g. multi-controlled `qco.ctrl`) are out of
scope and left untouched for a dedicated multi-controlled synthesis pass.

The gateset is a comma-separated list of gate tokens (order not
significant) from which the pass resolves a single-qubit Euler basis
(generic `qco.u` when `u` is present; IBM-style surface gates when all of
`x`, `sx`, and `rz`/`p` are present; IQM-style `qco.r` when `r` is present;
or a supported rotation pair chosen from `rx`, `ry`, `rz`) plus the
two-qubit entangler `cx` or `cz`.

Recognised tokens: `u`, `x`, `sx`, `rz` (or `p`), `rx`, `ry`, `r`, `cx`,
`cz`. An empty or whitespace-only gateset is a no-op, which is the intended
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
pipeline default when synthesis is not needed. An unrecognised token causes
the pass to fail.

Example gatesets (each line is one illustrative gateset; pick either `cx`
or `cz` as the entangler, or list both if both are native):
- IBM: `x,sx,rz,cx` or `x,sx,rz,cz`
- Generic single-qubit U: `u,cx` or `u,cz`
- IQM default: `r,cz` (or `r,cx` if CX is the native entangler)
- Rotation pair + entangler: `rx,rz,cx`, `rx,ry,cz`, `ry,rz,cx`, etc.
Supported pairs are exactly `rx`+`rz`, `rx`+`ry`, and `ry`+`rz`.

Stages: fuse single-qubit runs (reusing `fuse-single-qubit-unitary-runs`,
which also lowers lone off-gateset single-qubit gates); fuse two-qubit runs
and lower any remaining off-gateset two-qubit ops via Weyl synthesis; fuse
the single-qubit seams introduced by that synthesis. The pass fails if any
single- or two-qubit op remains off the native gateset afterwards; gates on
more than two qubits are left untouched and do not cause a failure.

Lowering is deterministic: `cx` is preferred over `cz`, single-qubit
factors use the resolved Euler basis, and two-qubit run replacement uses
the minimal entangler count from the synthesizer.
}];
let options = [Option<
"nativeGates", "native-gates", "std::string", "\"\"",
"Comma-separated native gateset. Empty or whitespace-only is "
"a no-op. Tokens: u, x, sx, rz (or p), rx, ry, r, cx, cz. "
"Examples: x,sx,rz,cx; u,cx; r,cz; rx,rz,cx.">];
}

//===----------------------------------------------------------------------===//

def QuantumLoopUnroll
: InterfacePass<"quantum-loop-unroll", "FunctionOpInterface"> {
let dependentDialects = ["mlir::qco::QCODialect", "mlir::scf::SCFDialect"];
Expand Down
46 changes: 46 additions & 0 deletions mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@

#include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h"

#include "mlir/Dialect/QCO/IR/QCOInterfaces.h"
#include "mlir/Dialect/QCO/IR/QCOOps.h"
#include "mlir/Dialect/QCO/Transforms/Decomposition/Euler.h"
#include "mlir/Dialect/QCO/Transforms/Decomposition/Weyl.h"
#include "mlir/Dialect/QCO/Utils/Matrix.h"

#include <llvm/ADT/StringSwitch.h>
#include <llvm/ADT/TypeSwitch.h>
#include <llvm/Support/ErrorHandling.h>
#include <mlir/IR/Operation.h>
#include <mlir/Support/LLVM.h>

#include <optional>
Expand Down Expand Up @@ -141,6 +145,48 @@ NativeGateset::decomposeTarget(const Matrix4x4& target) const {
return cachedNativeBasisDecomposer(*entangler).decomposeTarget(target);
}

static std::optional<NativeGateKind> gateKindFor(UnitaryOpInterface op) {
return llvm::TypeSwitch<Operation*, std::optional<NativeGateKind>>(
op.getOperation())
.Case<UOp>([](UOp) { return NativeGateKind::U; })
.Case<XOp>([](XOp) { return NativeGateKind::X; })
.Case<SXOp>([](SXOp) { return NativeGateKind::SX; })
.Case<RZOp>([](RZOp) { return NativeGateKind::RZ; })
.Case<RXOp>([](RXOp) { return NativeGateKind::RX; })
.Case<RYOp>([](RYOp) { return NativeGateKind::RY; })
.Case<ROp>([](ROp) { return NativeGateKind::R; })
.Default([](Operation*) { return std::nullopt; });
}

static std::optional<NativeGateKind> entanglerKindFor(CtrlOp ctrl) {
if (ctrl.getNumControls() != 1 || ctrl.getNumTargets() != 1 ||
ctrl.getNumBodyUnitaries() != 1) {
return std::nullopt;
}
return llvm::TypeSwitch<Operation*, std::optional<NativeGateKind>>(
ctrl.getBodyUnitary(0).getOperation())
.Case<XOp>([](XOp) { return NativeGateKind::CX; })
.Case<ZOp>([](ZOp) { return NativeGateKind::CZ; })
.Default([](Operation*) { return std::nullopt; });
}

bool NativeGateset::allowsOp(Operation* op) const {
return llvm::TypeSwitch<Operation*, bool>(op)
.Case<BarrierOp, GPhaseOp>([](auto) { return true; })
.Case<CtrlOp>([&](CtrlOp ctrl) {
const auto kind = entanglerKindFor(ctrl);
return kind && gates.contains(*kind);
})
.Case<UnitaryOpInterface>([&](UnitaryOpInterface unitary) {
if (!unitary.isSingleQubit()) {
return false;
}
const auto gate = gateKindFor(unitary);
return gate && gates.contains(*gate);
})
.Default([](Operation*) { return false; });
}

std::optional<NativeGateset> NativeGateset::parse(StringRef nativeGates) {
auto gates = parseGateSet(nativeGates);
if (!gates) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,13 @@ namespace {
struct FuseSingleQubitUnitaryRunsPattern final
: OpInterfaceRewritePattern<UnitaryOpInterface> {
FuseSingleQubitUnitaryRunsPattern(MLIRContext* context,
const decomposition::EulerBasis basis)
: OpInterfaceRewritePattern(context), basis(basis) {}
const decomposition::EulerBasis basis,
const bool skipControlledBodies)
: OpInterfaceRewritePattern(context), basis(basis),
skipControlledBodies(skipControlledBodies) {}

decomposition::EulerBasis basis;
bool skipControlledBodies;

/**
* @brief Whether `op` starts a run.
Expand All @@ -165,6 +168,10 @@ struct FuseSingleQubitUnitaryRunsPattern final
*/
LogicalResult matchAndRewrite(UnitaryOpInterface op,
PatternRewriter& rewriter) const override {
if (skipControlledBodies &&
(op.getOperation()->getParentOfType<CtrlOp>() != nullptr)) {
return failure();
}
if (!isRunStart(op)) {
return failure();
}
Expand Down Expand Up @@ -208,8 +215,8 @@ struct FuseSingleQubitUnitaryRunsPass final
}

RewritePatternSet patterns(&getContext());
patterns.add<FuseSingleQubitUnitaryRunsPattern>(patterns.getContext(),
*parsed);
decomposition::populateFuseSingleQubitUnitaryRunsPatterns(
patterns, *parsed, /*skipControlledBodies=*/false);

if (failed(applyPatternsGreedily(module, std::move(patterns)))) {
signalPassFailure();
Expand All @@ -220,3 +227,14 @@ struct FuseSingleQubitUnitaryRunsPass final
} // namespace

} // namespace mlir::qco

namespace mlir::qco::decomposition {

void populateFuseSingleQubitUnitaryRunsPatterns(
RewritePatternSet& patterns, const EulerBasis basis,
const bool skipControlledBodies) {
patterns.add<FuseSingleQubitUnitaryRunsPattern>(patterns.getContext(), basis,
skipControlledBodies);
}

} // namespace mlir::qco::decomposition
Loading
Loading