Skip to content
Open
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: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ with the exception that minor releases may include breaking changes.

### Added

- ✨ Add a `decompose-multi-controlled` pass for decomposing multi-controlled X
and Z gates into one- and two-qubit gates ([#1810]) ([**@simon1hofmann**])
- ✨ Add a `fuse-single-qubit-unitary-runs` pass
for fusing compile-time single-qubit unitary runs via Euler resynthesis
([#1672]) ([**@simon1hofmann**], [**@burgholzer**])
Expand Down Expand Up @@ -598,6 +600,7 @@ changelogs._

<!-- PR links -->

[#1810]: https://github.com/munich-quantum-toolkit/core/pull/1810
[#1808]: https://github.com/munich-quantum-toolkit/core/pull/1808
[#1807]: https://github.com/munich-quantum-toolkit/core/pull/1807
[#1806]: https://github.com/munich-quantum-toolkit/core/pull/1806
Expand Down
8 changes: 8 additions & 0 deletions mlir/include/mlir/Compiler/CompilerPipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <mlir/Pass/PassManager.h>
#include <mlir/Support/LogicalResult.h>

#include <cstdint>
#include <string>

namespace mlir {
Expand Down Expand Up @@ -49,6 +50,13 @@ struct QuantumCompilerConfig {

/// Enable Hadamard lifting
bool enableHadamardLifting = false;

/// Decompose multi-controlled X/Z gates into elementary one- and two-qubit
/// gates.
bool enableDecomposeMultiControlled = false;

/// Minimum control count for @ref enableDecomposeMultiControlled (default 2).
std::uint64_t decomposeMultiControlledMinControls = 2;
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#pragma once

#include <mlir/IR/Builders.h>
#include <mlir/IR/Location.h>
#include <mlir/IR/Value.h>
#include <mlir/Support/LLVM.h>

namespace mlir::qco::decomposition {

/**
* @brief Emits a decomposition of a multi-controlled X gate.
*
* @details Emits a sequence of one- and two-qubit gates that, taken together,
* implement the multi-controlled X gate that flips @p target whenever all @p
* controls are in the @f$|1\rangle@f$ state. The decomposition follows Huang
* and Palsberg (PLDI 2024), composing Iten et al. (Phys. Rev. A 93, 032318,
* 2016) dirty-ancilla subcircuits for large control counts. The emitted gates
* are `h`, `t`, `tdg`, `p`, and `cx`.
*
* @note Adapted from ``synth_mcx_noaux_hp24`` and ``synth_mcx_n_dirty_i15`` in
* the IBM Qiskit framework.
* (C) Copyright IBM 2025
*
* This code is licensed under the Apache License, Version 2.0. You may
* obtain a copy of this license in the LICENSE.txt file in the root
* directory of this source tree or at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Any modifications or derivative works of this code must retain this
* copyright notice, and modified files need to carry a notice
* indicating that they have been altered from the originals.
*
* @param builder Builder positioned at the desired insertion point.
* @param loc Location attached to the emitted operations.
* @param controls Current SSA values of the control qubits.
* @param target Current SSA value of the target qubit.
* @return The updated SSA values, ordered as `[controls..., target]`.
*
* @pre @p controls must contain at least two qubits.
*/
[[nodiscard]] SmallVector<Value> synthesizeMcx(OpBuilder& builder, Location loc,
ValueRange controls,
Value target);

/**
* @brief Emits a decomposition of a multi-controlled Z gate.
*
* @details Emits the same gate set as @ref synthesizeMcx (`h`, `t`, `tdg`, `p`,
* and `cx`). For @f$k \ge 2@f$ controls, emits the HP24 MCX core directly,
* since @f$\mathrm{MCZ} =
* H_{\text{target}}\,(H_{\text{target}}\,\mathrm{CORE}\,
* H_{\text{target}})\,H_{\text{target}} = \mathrm{CORE}@f$ and the MCX
* Hadamard bookends cancel under this conjugation.
*
* @param builder Builder positioned at the desired insertion point.
* @param loc Location attached to the emitted operations.
* @param controls Current SSA values of the control qubits.
* @param target Current SSA value of the target qubit.
* @return The updated SSA values, ordered as `[controls..., target]`.
*
* @pre @p controls must contain at least two qubits.
*/
[[nodiscard]] SmallVector<Value> synthesizeMcz(OpBuilder& builder, Location loc,
ValueRange controls,
Value target);

} // namespace mlir::qco::decomposition
42 changes: 42 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,46 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> {
}];
}

//===----------------------------------------------------------------------===//
// Decomposition Passes
//===----------------------------------------------------------------------===//

def DecomposeMultiControlled
: Pass<"decompose-multi-controlled", "mlir::ModuleOp"> {
let dependentDialects = ["mlir::qco::QCODialect",
"::mlir::arith::ArithDialect"];
let summary =
"Decompose multi-controlled gates into one- and two-qubit gates";
let description = [{
Decomposes multi-controlled gates (`qco.ctrl`) into a sequence of one- and
two-qubit gates that can subsequently be handled by one- and two-qubit gate
synthesis methods.

The pass matches `qco.ctrl` operations with at least `min-controls` control
qubits whose body is a single `qco.x` or `qco.z` on one target
(multi-controlled Pauli-X or Pauli-Z). Other multi-controlled gates are left
unchanged. Support for further base gates will be added gradually.

Multi-controlled X gates are decomposed using the Huang-Palsberg (PLDI 2024)
no-ancilla synthesis algorithm with a linear number of CX gates in the
number of controls, composing Iten et al. (Phys. Rev. A 93, 032318, 2016)
dirty-ancilla subcircuits for large control counts. Multi-controlled Z gates
use the same HP24 core (algebraically equivalent to
@f$H_{\text{target}}\,\mathrm{MCX}\,H_{\text{target}}@f$, without redundant
Hadamard bookends for @f$k \ge 2@f$). The pass emits `h`, `t`, `tdg`, `p`,
and `cx` gates.

By default (`min-controls=2`), gates with at least two controls (e.g. `CCX`,
`CCZ`, and larger `MCX`/`MCZ`) are decomposed, leaving single-control gates
(e.g. `CX`, `CZ`) and bare one-qubit gates intact. Increase `min-controls`
to keep smaller multi-controlled gates intact (e.g., when `CCX` is a native
gate of the target).
}];
let options = [Option<
"minControls", "min-controls", "uint64_t", "2",
"Minimum number of controls for which a controlled gate is "
"decomposed. Controlled gates with fewer controls are left "
"unchanged. Must be at least 2.">];
}

#endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD
12 changes: 12 additions & 0 deletions mlir/lib/Compiler/CompilerPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module,
"enabled and the record pointer to be non-null.\n";
return failure();
}
if (config_.enableDecomposeMultiControlled &&
config_.decomposeMultiControlledMinControls < 2) {
llvm::errs()
<< "decomposeMultiControlledMinControls must be at least 2 when "
"enableDecomposeMultiControlled is enabled.\n";
return failure();
}

auto runStage = [&](auto&& populatePasses) -> LogicalResult {
PassManager pm(module.getContext());
Expand Down Expand Up @@ -147,6 +154,11 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module,
}
// Stage 5: Optimization passes
if (failed(runStage([&](PassManager& pm) {
if (config_.enableDecomposeMultiControlled) {
qco::DecomposeMultiControlledOptions options;
options.minControls = config_.decomposeMultiControlledMinControls;
pm.addPass(qco::createDecomposeMultiControlled(options));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (!config_.disableMergeSingleQubitRotationGates) {
pm.addPass(qco::createMergeSingleQubitRotationGates());
}
Expand Down
2 changes: 2 additions & 0 deletions mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ add_mlir_library(
DEPENDS
MLIRQCOTransformsIncGen)

mqt_mlir_target_use_project_options(MLIRQCOTransforms)

# collect header files
file(GLOB_RECURSE PASSES_HEADERS_SOURCE
${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QCO/Transforms/*.h)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#include "mlir/Dialect/QCO/IR/QCOInterfaces.h"
#include "mlir/Dialect/QCO/IR/QCOOps.h"
#include "mlir/Dialect/QCO/Transforms/Decomposition/MultiControlled.h"
#include "mlir/Dialect/QCO/Transforms/Passes.h"
#include "mlir/Dialect/Utils/Utils.h"

#include <mlir/Dialect/Arith/IR/Arith.h> // IWYU pragma: keep (Passes.h.inc)
#include <mlir/IR/MLIRContext.h>
#include <mlir/IR/PatternMatch.h>
#include <mlir/Support/LLVM.h>
#include <mlir/Support/LogicalResult.h>
#include <mlir/Transforms/GreedyPatternRewriteDriver.h>

#include <cstdint>
#include <utility>

namespace mlir::qco {

#define GEN_PASS_DEF_DECOMPOSEMULTICONTROLLED
#include "mlir/Dialect/QCO/Transforms/Passes.h.inc"

namespace {

/**
* @brief Decomposes a multi-controlled Pauli-X or Pauli-Z gate into elementary
* one- and two-qubit gates.
*
* @details Matches `qco.ctrl` with a single `qco.x` or `qco.z` body when the
* control count is at least `minControls_` (and at least two, as enforced by
* the pass). Single-control `CX`/`CZ` and other gates are left unchanged.
*/
struct DecomposeMultiControlledPauliPattern final : OpRewritePattern<CtrlOp> {
explicit DecomposeMultiControlledPauliPattern(MLIRContext* context,
uint64_t minControls)
: OpRewritePattern<CtrlOp>(context), minControls_(minControls) {}

LogicalResult matchAndRewrite(CtrlOp op,
PatternRewriter& rewriter) const override {
if (op.getNumControls() < minControls_ || op.getNumTargets() != 1) {
return failure();
}
auto inner = utils::getSoleBodyUnitary<UnitaryOpInterface>(*op.getBody());
if (!inner) {
return failure();
}

rewriter.setInsertionPoint(op);
const auto controls = op.getControlsIn();
const auto target = op.getInputTarget(0);
const auto loc = op.getLoc();

if (isa<XOp>(inner.getOperation())) {
rewriter.replaceOp(
op, decomposition::synthesizeMcx(rewriter, loc, controls, target));
return success();
}
if (isa<ZOp>(inner.getOperation())) {
rewriter.replaceOp(
op, decomposition::synthesizeMcz(rewriter, loc, controls, target));
return success();
}
return failure();
}

private:
uint64_t minControls_;
};

/**
* @brief Pass that decomposes multi-controlled X and Z gates into elementary
* gates.
*/
struct DecomposeMultiControlled final
: impl::DecomposeMultiControlledBase<DecomposeMultiControlled> {
using DecomposeMultiControlledBase::DecomposeMultiControlledBase;

protected:
void runOnOperation() override {
if (minControls < 2) {
getOperation().emitError()
<< "decompose-multi-controlled requires min-controls >= 2";
signalPassFailure();
return;
}

RewritePatternSet patterns(&getContext());
patterns.add<DecomposeMultiControlledPauliPattern>(&getContext(),
minControls);

if (failed(applyPatternsGreedily(getOperation(), std::move(patterns)))) {
signalPassFailure();
}
}
};

} // namespace

} // namespace mlir::qco
Loading
Loading