Skip to content
Draft
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
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` 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`, `rx`, `ry`, `r`, `cx`,
`cz`. An empty or whitespace-only gateset is a no-op, which is the intended
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, 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