Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ releases may include breaking changes.
[#1807], [#1808], [#1815], [#1824], [#1869], [#1872], [#1914], [#1925],
[#1927], [#1935], [#1936], [#1938], [#1975], [#1976], [#2006], [#2014],
[#2015], [#2017], [#2026], [#2028], [#2054], [#2058], [#2125], [#2136],
[#2149], [#2150], [#2158], [#2210], [#2211]) ([**@burgholzer**],
[#2149], [#2150], [#2158], [#2210], [#2211], [#2220]) ([**@burgholzer**],
[**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**],
[**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**], [**@J4MMlE**])
- ✨ Add decision diagram-based construction, simulation, and sampling for QCO
Expand Down Expand Up @@ -851,6 +851,7 @@ for previous changelogs._
[#2257]: https://github.com/munich-quantum-toolkit/core/pull/2257
[#2249]: https://github.com/munich-quantum-toolkit/core/pull/2249
[#2246]: https://github.com/munich-quantum-toolkit/core/pull/2246
[#2220]: https://github.com/munich-quantum-toolkit/core/pull/2220
[#2232]: https://github.com/munich-quantum-toolkit/core/pull/2232
[#2224]: https://github.com/munich-quantum-toolkit/core/pull/2224
[#2209]: https://github.com/munich-quantum-toolkit/core/pull/2209
Expand Down
20 changes: 18 additions & 2 deletions mlir/include/mlir/Compiler/Programs.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,6 @@ class QCProgram final : public Program {
*/
class QCOProgram final : public Program {
public:
explicit QCOProgram(Storage storage) : Program(std::move(storage)) {}

/// Parse QCO MLIR assembly.
[[nodiscard]] static std::optional<QCOProgram>
fromMLIRString(std::string_view source);
Expand All @@ -236,6 +234,17 @@ class QCOProgram final : public Program {
[[nodiscard]] static std::optional<QCOProgram>
fromMLIRFile(const std::filesystem::path& path);

/**
* @brief Take ownership of an MLIR module that contains a QCO program.
*
* @details The context must own every dialect referenced by the module and
* must remain the module's context. The factory verifies the module, requires
* at least one operation from the QCO dialect, and verifies QCO linearity.
*/
[[nodiscard]] static std::optional<QCOProgram>
fromModule(std::shared_ptr<MLIRContext> context,
OwningOpRef<ModuleOp> moduleOp);

/// Create an independent QCO program copy.
[[nodiscard]] QCOProgram copy() const;

Expand Down Expand Up @@ -285,6 +294,13 @@ class QCOProgram final : public Program {

/// Consume this program and convert it to `jeff` MLIR.
[[nodiscard]] std::optional<JeffProgram> intoJeff() &&;

private:
friend class QCProgram;
friend class JeffProgram;

explicit QCOProgram(Storage storage) : Program(std::move(storage)) {}
[[nodiscard]] bool hasValidLinearity() const;
};

/**
Expand Down
9 changes: 6 additions & 3 deletions mlir/include/mlir/Dialect/QCO/QCOUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@ inline bool checkDeadGate(Operation* op) {
if (isa<QubitType>(type)) {
return true;
}
const auto tensorType = dyn_cast<RankedTensorType>(type);
return tensorType && tensorType.getRank() == 1 &&
isa<QubitType>(tensorType.getElementType());
const auto shapedType = dyn_cast<ShapedType>(type);
return isa<RankedTensorType, VectorType>(type) && shapedType.getRank() == 1 &&
isa<QubitType>(shapedType.getElementType());
}

/// Verify that every linear QCO value under @p root has exactly one use.
[[nodiscard]] LogicalResult verifyLinearity(Operation* root);

/// Maximum number of modifier targets supported by @ref
/// composeBodyMatrix.
inline constexpr size_t kMaxModifierTargetQubits = 10;
Expand Down
136 changes: 98 additions & 38 deletions mlir/lib/Compiler/Programs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h"
#include "mlir/Dialect/QC/Translation/TranslateQCToOpenQASM3.h"
#include "mlir/Dialect/QCO/IR/QCODialect.h"
#include "mlir/Dialect/QCO/QCOUtils.h"
#include "mlir/Dialect/QCO/Transforms/Passes.h"
#include "mlir/Dialect/QIR/Utils/QIRUtils.h"
#include "mlir/Dialect/QTensor/IR/QTensorDialect.h"
Expand Down Expand Up @@ -153,18 +154,13 @@ parseMLIRFile(MLIRContext* context, const std::filesystem::path& path) {

template <class ProgramType, class Parse>
[[nodiscard]] static std::optional<ProgramType>
parseTypedProgram(const StringRef dialect, Parse&& parse) {
parseTypedProgram(Parse&& parse) {
auto context = createCompilerContext();
auto mod = std::forward<Parse>(parse)(context.get());
if (failed(mod)) {
return std::nullopt;
}
if (!moduleUsesDialect(**mod, dialect)) {
(**mod)->emitError() << "expected a module using the '" << dialect
<< "' dialect";
return std::nullopt;
}
return ProgramType({.context = std::move(context), .mod = std::move(*mod)});
return ProgramType::fromModule(std::move(context), std::move(*mod));
}

[[nodiscard]] static LogicalResult
Expand All @@ -186,6 +182,17 @@ runPasses(ModuleOp mod,
return success();
}

[[nodiscard]] static LogicalResult runQCOTransformPasses(
ModuleOp mod, const llvm::function_ref<void(OpPassManager&)> populatePasses,
const StringRef failureMessage, const bool enableTiming = false,
const bool enableStatistics = false) {
if (failed(qco::verifyLinearity(mod))) {
return failure();
}
return runPasses(mod, populatePasses, failureMessage, enableTiming,
enableStatistics);
}

//===----------------------------------------------------------------------===//
// Program
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -252,16 +259,15 @@ bool OpenQASMProgram::write(const std::filesystem::path& path) const {

std::optional<QCProgram>
QCProgram::fromMLIRString(const std::string_view source) {
return parseTypedProgram<QCProgram>("qc", [source](MLIRContext* context) {
return parseTypedProgram<QCProgram>([source](MLIRContext* context) {
return parseMLIRString(context, source);
});
}

std::optional<QCProgram>
QCProgram::fromMLIRFile(const std::filesystem::path& path) {
return parseTypedProgram<QCProgram>("qc", [&path](MLIRContext* context) {
return parseMLIRFile(context, path);
});
return parseTypedProgram<QCProgram>(
[&path](MLIRContext* context) { return parseMLIRFile(context, path); });
}

std::optional<QCProgram>
Expand Down Expand Up @@ -296,31 +302,32 @@ QCProgram::fromQASMFile(const std::filesystem::path& path) {
std::optional<QCProgram>
QCProgram::fromModule(std::shared_ptr<MLIRContext> context,
OwningOpRef<ModuleOp> moduleOp) {
if (!moduleOp) {
if (context) {
emitError(UnknownLoc::get(context.get()),
Storage storage{.context = std::move(context), .mod = std::move(moduleOp)};
if (!storage.mod) {
if (storage.context) {
emitError(UnknownLoc::get(storage.context.get()),
"cannot construct a QC program from a null module");
}
return std::nullopt;
}
if (!context) {
moduleOp->emitError(
if (!storage.context) {
storage.mod->emitError(
"cannot construct a QC program without its owning context");
return std::nullopt;
}
if (moduleOp->getContext() != context.get()) {
moduleOp->emitError(
if (storage.mod->getContext() != storage.context.get()) {
storage.mod->emitError(
"cannot construct a QC program with a different MLIR context");
return std::nullopt;
}
if (failed(verify(*moduleOp))) {
if (failed(verify(*storage.mod))) {
return std::nullopt;
}
if (!moduleUsesDialect(*moduleOp, "qc")) {
moduleOp->emitError("expected a module using the 'qc' dialect");
if (!moduleUsesDialect(*storage.mod, "qc")) {
storage.mod->emitError("expected a module using the 'qc' dialect");
return std::nullopt;
}
return QCProgram({.context = std::move(context), .mod = std::move(moduleOp)});
return QCProgram(std::move(storage));
}

QCProgram QCProgram::copy() const { return QCProgram(cloneStorage()); }
Expand Down Expand Up @@ -352,6 +359,9 @@ std::optional<QCOProgram> QCProgram::intoQCO() && {
"failed to convert QC to QCO"))) {
return std::nullopt;
}
if (failed(qco::verifyLinearity(mod()))) {
return std::nullopt;
}
return QCOProgram(std::move(*this).releaseStorage());
}

Expand Down Expand Up @@ -409,38 +419,82 @@ size_t QCProgram::numTwoQubitGates() const {

std::optional<QCOProgram>
QCOProgram::fromMLIRString(const std::string_view source) {
return parseTypedProgram<QCOProgram>("qco", [source](MLIRContext* context) {
return parseTypedProgram<QCOProgram>([source](MLIRContext* context) {
return parseMLIRString(context, source);
});
}

std::optional<QCOProgram>
QCOProgram::fromMLIRFile(const std::filesystem::path& path) {
return parseTypedProgram<QCOProgram>("qco", [&path](MLIRContext* context) {
return parseMLIRFile(context, path);
});
return parseTypedProgram<QCOProgram>(
[&path](MLIRContext* context) { return parseMLIRFile(context, path); });
}

std::optional<QCOProgram>
QCOProgram::fromModule(std::shared_ptr<MLIRContext> context,
OwningOpRef<ModuleOp> moduleOp) {
Storage storage{.context = std::move(context), .mod = std::move(moduleOp)};
if (!storage.mod) {
if (storage.context) {
emitError(UnknownLoc::get(storage.context.get()),
"cannot construct a QCO program from a null module");
}
return std::nullopt;
}
if (!storage.context) {
storage.mod->emitError(
"cannot construct a QCO program without its owning context");
return std::nullopt;
}
if (storage.mod->getContext() != storage.context.get()) {
storage.mod->emitError(
"cannot construct a QCO program with a different MLIR context");
return std::nullopt;
}
if (failed(verify(*storage.mod))) {
return std::nullopt;
}
if (!moduleUsesDialect(*storage.mod, "qco")) {
storage.mod->emitError("expected a module using the 'qco' dialect");
return std::nullopt;
}
if (failed(qco::verifyLinearity(*storage.mod))) {
return std::nullopt;
}
return QCOProgram(std::move(storage));
}

QCOProgram QCOProgram::copy() const { return QCOProgram(cloneStorage()); }

bool QCOProgram::hasValidLinearity() const {
return succeeded(qco::verifyLinearity(mod()));
}

bool QCOProgram::cleanup() {
return succeeded(runPasses(mod(), populateQCOCleanupPipeline,
"failed to run the QCO cleanup pipeline"));
return succeeded(
runQCOTransformPasses(mod(), populateQCOCleanupPipeline,
"failed to run the QCO cleanup pipeline"));
}

bool QCOProgram::normalizeGlobalPhases() {
if (!hasValidLinearity()) {
return false;
}
return succeeded(mqt::normalizeGlobalPhases(mod()));
}

bool QCOProgram::runPassPipeline(const std::string_view pipeline,
const bool enableTiming,
const bool enableStatistics) {
if (!hasValidLinearity()) {
return false;
}
return succeeded(
::runPassPipeline(mod(), pipeline, enableTiming, enableStatistics));
}

bool QCOProgram::mergeSingleQubitRotationGates() {
return succeeded(runPasses(
return succeeded(runQCOTransformPasses(
mod(),
[](OpPassManager& pm) {
pm.addPass(qco::createMergeSingleQubitRotationGates());
Expand All @@ -451,7 +505,7 @@ bool QCOProgram::mergeSingleQubitRotationGates() {
bool QCOProgram::fuseSingleQubitUnitaryRuns(const std::string_view basis) {
qco::FuseSingleQubitUnitaryRunsOptions options;
options.basis = basis;
return succeeded(runPasses(
return succeeded(runQCOTransformPasses(
mod(),
[&options](OpPassManager& pm) {
pm.addPass(qco::createFuseSingleQubitUnitaryRuns(options));
Expand All @@ -462,7 +516,7 @@ bool QCOProgram::fuseSingleQubitUnitaryRuns(const std::string_view basis) {
bool QCOProgram::unrollQuantumLoops(const int64_t factor) {
qco::QuantumLoopUnrollOptions options;
options.unrollFactor = factor;
return succeeded(runPasses(
return succeeded(runQCOTransformPasses(
mod(),
[&options](OpPassManager& pm) {
pm.addNestedPass<func::FuncOp>(qco::createQuantumLoopUnroll(options));
Expand All @@ -471,26 +525,26 @@ bool QCOProgram::unrollQuantumLoops(const int64_t factor) {
}

bool QCOProgram::liftHadamards() {
return succeeded(runPasses(
return succeeded(runQCOTransformPasses(
mod(),
[](OpPassManager& pm) { pm.addPass(qco::createHadamardLifting()); },
"failed to lift Hadamard gates"));
}

bool QCOProgram::reuseQubits() {
return succeeded(runPasses(
return succeeded(runQCOTransformPasses(
mod(), [](OpPassManager& pm) { pm.addPass(qco::createReuseQubits()); },
"failed to reuse qubits"));
}

bool QCOProgram::runQubitReusePipeline() {
return succeeded(runPasses(
return succeeded(runQCOTransformPasses(
mod(), [](OpPassManager& pm) { populateQubitReusePipeline(pm); },
"failed to run the qubit reuse pipeline"));
}

bool QCOProgram::decomposeMultiControlled(const uint64_t minQubits) {
return succeeded(runPasses(
return succeeded(runQCOTransformPasses(
mod(),
[minQubits](OpPassManager& pm) {
populateDecomposeMultiControlledPipeline(pm, minQubits);
Expand All @@ -501,6 +555,9 @@ bool QCOProgram::decomposeMultiControlled(const uint64_t minQubits) {
bool QCOProgram::compileForTarget(const CompilerTarget& target,
const bool enableTiming,
const bool enableStatistics) {
if (!hasValidLinearity()) {
return false;
}
return succeeded(runPasses(
mod(),
[&target](OpPassManager& pm) {
Expand All @@ -511,7 +568,7 @@ bool QCOProgram::compileForTarget(const CompilerTarget& target,
}

std::optional<QCProgram> QCOProgram::intoQC() && {
if (failed(runPasses(
if (failed(runQCOTransformPasses(
mod(), [](OpPassManager& pm) { pm.addPass(createQCOToQC()); },
"failed to convert QCO to QC"))) {
return std::nullopt;
Expand All @@ -520,7 +577,7 @@ std::optional<QCProgram> QCOProgram::intoQC() && {
}

std::optional<JeffProgram> QCOProgram::intoJeff() && {
if (failed(runPasses(
if (failed(runQCOTransformPasses(
mod(),
[](OpPassManager& pm) {
pm.addPass(mqt::createUnrollModifiers());
Expand Down Expand Up @@ -599,6 +656,9 @@ std::optional<QCOProgram> JeffProgram::intoQCO() && {
"failed to convert jeff to QCO"))) {
return std::nullopt;
}
if (failed(qco::verifyLinearity(mod()))) {
return std::nullopt;
}
return QCOProgram(std::move(*this).releaseStorage());
}

Expand Down Expand Up @@ -744,7 +804,7 @@ runDefaultPipeline(CompilerInput&& program, const ProgramFormat output,
}
},
std::move(program));
if (!qco) {
if (!qco || failed(qco::verifyLinearity(qco->module()))) {
return std::nullopt;
}
if (output == ProgramFormat::QCO) {
Expand Down
Loading
Loading