Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
95f900b
✨ Add pow modifier (QC, QCO, conversion, builders)
J4MMlE Mar 3, 2026
27dae23
✅ Add pow modifier tests and canonical ordering
J4MMlE Apr 1, 2026
c553825
✨ Add known gate specializations
J4MMlE Apr 7, 2026
b315b8b
♻️ Refactor MoveCtrlOutside and improve test gate
J4MMlE Apr 7, 2026
5920184
♻️ Refactor gate-specific canonicalization
J4MMlE Apr 7, 2026
e1e1b71
✅ Add known gates and conversion tests
J4MMlE Apr 11, 2026
5451cff
🐛 use inlineBlockBefore for PowOp body rewriting
J4MMlE Apr 16, 2026
b264388
♻️ align PowOp UnitaryInterface accessors with InvOp
J4MMlE Apr 16, 2026
15441ba
🐛 restrict NegPowToInvPow to integer exponents
J4MMlE Apr 16, 2026
2921f3e
✏️ name test case correctly
J4MMlE Apr 16, 2026
a34df67
✅ add fractional power matrix test
J4MMlE Apr 17, 2026
36c1ea9
♻️ simplify inv(pow(p){U}) to pow(-p){U} by negating exponent
J4MMlE Apr 23, 2026
f6083bc
🐛 guard identity-producing PowOp folds inside modifiers
J4MMlE Apr 23, 2026
5bb0823
♻️ minor refactors, add overflow safe number check
pre-commit-ci[bot] Apr 23, 2026
c333360
Merge branch 'main' into pow-modifier
denialhaag Apr 29, 2026
40a42f7
Update to new include style
denialhaag Apr 29, 2026
2bbeeb4
Argument: F64Attr -> F64
J4MMlE May 26, 2026
16fc46b
consistent and better docstrings
J4MMlE May 26, 2026
ca05454
MovePowOutside -> InvPowToNegPow
J4MMlE May 27, 2026
c2e42f6
use TOLERANCE
J4MMlE May 28, 2026
6ef5eed
inline templates
J4MMlE May 28, 2026
32868a2
add test for NegPowToInvPow
J4MMlE May 28, 2026
3c355d4
make exponent std::variant
J4MMlE May 29, 2026
bb16c83
Merge remote-tracking branch 'upstream/main' into pow-modifier
J4MMlE May 29, 2026
3b54b8d
🎨 pre-commit fixes
pre-commit-ci[bot] May 29, 2026
13df503
fix failing test
J4MMlE May 29, 2026
bb1ec8a
fix linter errors
J4MMlE Jun 1, 2026
46cfe0c
🎨 pre-commit fixes
pre-commit-ci[bot] Jun 1, 2026
a74a7d1
drop llvm and mlir scope resolution
J4MMlE Jun 2, 2026
eadac62
update CHANGELOG.md
J4MMlE Jun 2, 2026
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: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
- ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637], [#1676], [#1706]) ([**@denialhaag**], [**@burgholzer**])
- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748]) ([**@MatthiasReumann**], [**@burgholzer**])
- ✨ Add initial infrastructure for new QC and QCO MLIR dialects
([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], [#1728], [#1730], [#1749])
([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**])
([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1603], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], [#1728], [#1730], [#1749])
([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**], [**@J4MMlE**])

### Changed

Expand Down Expand Up @@ -434,6 +434,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
[#1623]: https://github.com/munich-quantum-toolkit/core/pull/1623
[#1620]: https://github.com/munich-quantum-toolkit/core/pull/1620
[#1605]: https://github.com/munich-quantum-toolkit/core/pull/1605
[#1603]: https://github.com/munich-quantum-toolkit/core/pull/1603
[#1602]: https://github.com/munich-quantum-toolkit/core/pull/1602
[#1600]: https://github.com/munich-quantum-toolkit/core/pull/1600
[#1596]: https://github.com/munich-quantum-toolkit/core/pull/1596
Expand Down
21 changes: 21 additions & 0 deletions mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,27 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder {
*/
QCProgramBuilder& inv(const function_ref<void()>& body);

/**
* @brief Apply a power operation.
*
* @param exponent The exponent to raise the operation to
* @param body Function that builds the body containing the operation to
* exponentiate
* @return Reference to this builder for method chaining
*
* @par Example:
* ```c++
* builder.pow(2.0, [&] { builder.s(q0); });
* ```
* ```mlir
* qc.pow(2.000000e+00) {
* qc.s %q0 : !qc.qubit
* }
* ```
*/
QCProgramBuilder& pow(const std::variant<double, Value>& exponent,
const function_ref<void()>& body);

//===--------------------------------------------------------------------===//
// Deallocation
//===--------------------------------------------------------------------===//
Expand Down
49 changes: 49 additions & 0 deletions mlir/include/mlir/Dialect/QC/IR/QCOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1008,4 +1008,53 @@ def InvOp : QCOp<"inv",
let hasVerifier = 1;
}

def PowOp : QCOp<"pow",
traits = [UnitaryOpInterface,
SingleBlockImplicitTerminator<"::mlir::qc::YieldOp">,
RecursiveMemoryEffects]> {
let summary = "Apply a gate power modifier";
let description = [{
A modifier operation that raises the unitary operation defined in its
body region to a given power (exponent can be integer or fractional).

- r > 0: apply the gate raised to the r-th power.
- r = 0: identity (no-op).
- r < 0: equivalent to inv @ pow(-r) @ g.

Example:
```mlir
qc.pow(2.000000e+00) {
qc.s %q0 : !qc.qubit
}
```
}];
Comment thread
coderabbitai[bot] marked this conversation as resolved.

let arguments = (ins F64:$exponent);
let regions = (region SizedRegion<1>:$region);
let assemblyFormat = "`(` $exponent `)` $region attr-dict";

let extraClassDeclaration = [{
[[nodiscard]] UnitaryOpInterface getBodyUnitary();
size_t getNumQubits() { return getBodyUnitary().getNumQubits(); }
size_t getNumTargets() { return getBodyUnitary().getNumTargets(); }
size_t getNumControls() { return getBodyUnitary().getNumControls(); }
Value getQubit(size_t i) { return getBodyUnitary().getQubit(i); }
Value getTarget(size_t i) { return getBodyUnitary().getTarget(i); }
ValueRange getTargets() { return getBodyUnitary().getTargets(); }
Value getControl(size_t i) { return getBodyUnitary().getControl(i); }
ValueRange getControls() { return getBodyUnitary().getControls(); }
size_t getNumParams() { return getBodyUnitary().getNumParams(); }
Comment thread
J4MMlE marked this conversation as resolved.
Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); }
ValueRange getParameters() { return getBodyUnitary().getParameters(); }
double getExponentValue();
static StringRef getBaseSymbol() { return "pow"; }
}];

let builders = [OpBuilder<(ins "const std::variant<double, Value>&":$exponent,
"const llvm::function_ref<void()>&":$bodyBuilder)>];

let hasCanonicalizer = 1;
let hasVerifier = 1;
}

#endif // MLIR_DIALECT_QC_IR_QCOPS_TD
27 changes: 27 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,33 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder {
ValueRange inv(ValueRange qubits,
function_ref<SmallVector<Value>(ValueRange)> body);

/**
* @brief Apply a power operation.
*
* @param qubits Input qubits
* @param exponent The exponent to raise the operation to
* @param body Function that builds the body containing the operation to
* exponentiate
* @return Output qubits
Comment thread
J4MMlE marked this conversation as resolved.
*
* @par Example:
* ```c++
* qubits_out = builder.pow(q0_in, 2.0,
* [&](ValueRange qubits) -> SmallVector<Value> {
* return {builder.s(qubits[0])};
* }
* );
* ```
* ```mlir
* %q_out = qco.pow (2.000000e+00) (%q = %q_in) {
* %q_res = qco.s %q : !qco.qubit -> !qco.qubit
* qco.yield %q_res
* } : {!qco.qubit} -> {!qco.qubit}
* ```
*/
ValueRange pow(ValueRange qubits, const std::variant<double, Value>& exponent,
function_ref<SmallVector<Value>(ValueRange)> body);

//===--------------------------------------------------------------------===//
// Deallocation
//===--------------------------------------------------------------------===//
Expand Down
71 changes: 71 additions & 0 deletions mlir/include/mlir/Dialect/QCO/IR/QCOOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,77 @@ def InvOp
let hasVerifier = 1;
}

def PowOp
: QCOOp<"pow",
traits = [UnitaryOpInterface,
SingleBlockImplicitTerminator<"::mlir::qco::YieldOp">,
RecursiveMemoryEffects]> {
let summary = "Raise a unitary operation to a power";
let description = [{
A modifier operation that raises the unitary operation defined in its body
region to the given floating-point exponent. The operation takes a variadic
number of qubits as inputs and produces corresponding output qubits.

Example:
```mlir
%q_out = qco.pow (0.5) (%q = %q_in) {
%q_1 = qco.s %q : !qco.qubit -> !qco.qubit
qco.yield %q_1
} : {!qco.qubit} -> {!qco.qubit}
```
}];

let arguments = (ins F64:$exponent,
Arg<Variadic<QubitType>,
"the qubits involved in the operation", [MemRead]>:$qubits_in);
let results = (outs Variadic<QubitType>:$qubits_out);
let regions = (region SizedRegion<1>:$region);
let assemblyFormat = [{
`(` $exponent `)` custom<TargetAliasing>($region, $qubits_in)
attr-dict `:`
`{` type($qubits_in) `}`
`->`
`{` type($qubits_out) `}`
}];

let extraClassDeclaration = [{
UnitaryOpInterface getBodyUnitary();
size_t getNumQubits() { return getNumTargets(); }
size_t getNumTargets() { return getQubitsIn().size(); }
static size_t getNumControls() { return 0; }
Value getInputQubit(size_t i);
OperandRange getInputQubits() { return getQubitsIn(); }
Value getOutputQubit(size_t i);
ResultRange getOutputQubits() { return getResults(); }
Value getInputTarget(size_t i) { return getInputQubit(i); }
OperandRange getInputTargets() { return getInputQubits(); }
Value getOutputTarget(size_t i) { return getOutputQubit(i); }
ResultRange getOutputTargets() { return getOutputQubits(); }
static Value getInputControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); }
static OperandRange getInputControls() { return {nullptr, 0}; }
static Value getOutputControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); }
static ResultRange getOutputControls() { return {nullptr, 0}; }
Value getInputForOutput(Value output);
Value getOutputForInput(Value input);
size_t getNumParams() { return getBodyUnitary().getNumParams(); }
Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); }
ValueRange getParameters() { return getBodyUnitary().getParameters(); }
double getExponentValue();
[[nodiscard]] static StringRef getBaseSymbol() { return "pow"; }
[[nodiscard]] std::optional<Eigen::MatrixXcd> getUnitaryMatrix();
}];

let builders = [OpBuilder<(ins "ValueRange":$qubits,
"const std::variant<double, Value>&":$exponent)>,
OpBuilder<(ins "ValueRange":$qubits,
"const std::variant<double, Value>&":$exponent,
"llvm::function_ref<llvm::SmallVector<Value>(ValueRange)"
">":$bodyBuilder)>];

let hasCanonicalizer = 1;
let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// SCF operations
//===----------------------------------------------------------------------===//
Expand Down
26 changes: 26 additions & 0 deletions mlir/include/mlir/Dialect/Utils/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,36 @@
#include <mlir/IR/Location.h>
#include <mlir/IR/Value.h>

#include <cmath>
#include <numbers>
#include <variant>

namespace mlir::utils {

/// Check if a floating-point value is an integer.
[[nodiscard]] inline bool isIntegerExponent(double r) {
return r == std::floor(r) && std::isfinite(r);
}

/// Check if a floating-point value is an even integer.
/// Uses fmod to avoid UB from narrowing to int64_t for large values.
[[nodiscard]] inline bool isEvenExponent(double r) {
return std::fmod(std::fabs(r), 2.0) == 0.0;
}

/// Normalize an angle to (-π, π].
[[nodiscard]] inline double normalizeAngle(double theta) {
const double twoPi = 2.0 * std::numbers::pi;
theta = std::fmod(theta, twoPi);
if (theta > std::numbers::pi) {
theta -= twoPi;
}
if (theta <= -std::numbers::pi) {
theta += twoPi;
}
return theta;
}

constexpr auto TOLERANCE = 1e-15;

inline Value constantFromScalar(OpBuilder& builder, Location loc, double v) {
Expand Down
65 changes: 62 additions & 3 deletions mlir/lib/Conversion/QCOToQC/QCOToQC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,64 @@ struct ConvertQCOInvOp final : OpConversionPattern<qco::InvOp> {
}
};

/**
* @brief Converts qco.pow to qc.pow
*
* @par Example:
* ```mlir
* %q0_out = qco.pow (2.000000e+00) (%a_in = %q0_in) {
* %a_res = qco.s %a_in : !qco.qubit -> !qco.qubit
* qco.yield %a_res
* } : {!qco.qubit} -> {!qco.qubit}
* ```
* is converted to
* ```mlir
* qc.pow(2.000000e+00) {
* qc.s %q0 : !qc.qubit
* }
* ```
*/
struct ConvertQCOPowOp final : OpConversionPattern<qco::PowOp> {
using OpConversionPattern::OpConversionPattern;

LogicalResult
matchAndRewrite(qco::PowOp op, OpAdaptor adaptor,
ConversionPatternRewriter& rewriter) const override {
// Create qc.pow operation with exponent
auto qcOp = qc::PowOp::create(rewriter, op.getLoc(), adaptor.getExponent());

// Clone body region from QCO to QC
auto& dstRegion = qcOp.getRegion();
rewriter.cloneRegionBefore(op.getRegion(), dstRegion, dstRegion.end());

auto& entryBlock = dstRegion.front();
const auto numArgs = entryBlock.getNumArguments();
if (adaptor.getQubitsIn().size() != numArgs) {
return op.emitOpError() << "qco.pow: entry block args (" << numArgs
<< ") must match number of target operands ("
<< adaptor.getQubitsIn().size() << ")";
}

// Remove all block arguments in the cloned region
rewriter.modifyOpInPlace(qcOp, [&] {
// 1. Replace uses (Must be done BEFORE erasing)
for (auto i = 0UL; i < numArgs; ++i) {
entryBlock.getArgument(i).replaceAllUsesWith(adaptor.getQubitsIn()[i]);
}

// 2. Erase all block arguments
if (numArgs > 0) {
entryBlock.eraseArguments(0, numArgs);
}
});

// Replace the output qubits with the same QC references
rewriter.replaceOp(op, adaptor.getQubitsIn());

Comment on lines +729 to +756
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Extract shared helper for cloned-region arg remapping.

The block-argument remap/erase sequence here duplicates logic already present in ConvertQCOInvOp (and similar flow in ConvertQCOCtrlOp). Consider extracting a small helper to reduce divergence risk when modifier lowering evolves.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mlir/lib/Conversion/QCOToQC/QCOToQC.cpp` around lines 721 - 748, Extract the
duplicated block-argument remap/erase logic into a shared helper (e.g.,
remapAndEraseBlockArguments) and call it from this lowering and the existing
ConvertQCOInvOp/ConvertQCOCtrlOp implementations: the helper should take the
cloned entry Block, the adaptor operand list (adaptor.getQubitsIn() /
ArrayRef<Value>), and the rewriter/op being modified (so it can call
modifyOpInPlace on qcOp), perform the replaceAllUsesWith loop over
entryBlock.getArgument(i) -> newArgs[i], then eraseArguments(0, numArgs) if
numArgs>0; replace the inline lambda in this file with a call to that helper to
avoid duplicated logic and divergence.

return success();
}
};

/**
* @brief Converts qco.yield to qc.yield or to scf.yield if the parent is a
* scf::IfOp
Expand Down Expand Up @@ -1002,9 +1060,10 @@ struct QCOToQC final : impl::QCOToQCBase<QCOToQC> {
#undef MQT_ADD_QCO_TO_QC_GATE

patterns.add<ConvertQCOBarrierOp, ConvertQCOCtrlOp, ConvertQCOInvOp,
ConvertQCOYieldOp, ConvertQCOIfOp, ConvertQCOSCFWhileOp,
ConvertQCOSCFConditionOp, ConvertQCOSCFYieldOp,
ConvertQCOSCFForOp>(typeConverter, context);
ConvertQCOPowOp, ConvertQCOYieldOp, ConvertQCOIfOp,
ConvertQCOSCFWhileOp, ConvertQCOSCFConditionOp,
ConvertQCOSCFYieldOp, ConvertQCOSCFForOp>(typeConverter,
context);

// Register operation conversion patterns that need state tracking
patterns.add<ConvertQTensorExtractOp, ConvertQTensorAllocOp,
Expand Down
56 changes: 54 additions & 2 deletions mlir/lib/Conversion/QCToQCO/QCToQCO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,58 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern<qc::InvOp> {
}
};

/**
* @brief Converts qc.pow to qco.pow
*
* @par Example:
* ```mlir
* qc.pow(2.000000e+00) {
* qc.s %q0 : !qc.qubit
* }
* ```
* is converted to
* ```mlir
* %q0_out = qco.pow (2.000000e+00) (%a0_in = %q0_in) {
* %a0_res = qco.s %a0_in : !qco.qubit -> !qco.qubit
* qco.yield %a0_res
* } : {!qco.qubit} -> {!qco.qubit}
* ```
*/
struct ConvertQCPowOp final : StatefulOpConversionPattern<qc::PowOp> {
using StatefulOpConversionPattern::StatefulOpConversionPattern;

LogicalResult
matchAndRewrite(qc::PowOp op, OpAdaptor /*adaptor*/,
ConversionPatternRewriter& rewriter) const override {
auto& state = getState();
auto* operation = op.getOperation();
const auto numTargets = op.getNumTargets();
const auto qcTargets = op.getTargets();
auto qcoTargets = resolveMappedQubits(state, operation, qcTargets);

// Create qco.pow with exponent
const double exponent = op.getExponentValue();
auto qcoOp =
qco::PowOp::create(rewriter, op.getLoc(), qcoTargets, exponent);

assignMappedQubits(state, operation, qcTargets, qcoOp.getQubitsOut());

// Clone body region from QC to QCO
auto& dstRegion = qcoOp.getRegion();
rewriter.cloneRegionBefore(op.getRegion(), dstRegion, dstRegion.end());

// Create block arguments for target qubits and seed the nested frame.
auto& entryBlock = dstRegion.front();
assert(entryBlock.getNumArguments() == 0 &&
"QC pow region unexpectedly has entry block arguments");
pushModifierFrame(state, qcTargets,
addModifierAliases(qcoOp, numTargets, rewriter));

rewriter.eraseOp(op);
return success();
}
};

/**
* @brief Converts qc.yield to qco.yield
*
Expand Down Expand Up @@ -1626,8 +1678,8 @@ struct QCToQCO final : impl::QCToQCOBase<QCToQCO> {
ConvertMemRefLoadOp, ConvertMemRefDeallocOp, ConvertQCAllocOp,
ConvertQCDeallocOp, ConvertQCStaticOp, ConvertQCMeasureOp,
ConvertQCResetOp, ConvertQCBarrierOp, ConvertQCCtrlOp,
ConvertQCInvOp, ConvertQCYieldOp>(typeConverter, context,
&state);
ConvertQCInvOp, ConvertQCPowOp, ConvertQCYieldOp>(
typeConverter, context, &state);

// Not part of the central gate table.
patterns.add<ConvertQCGateToQCO<qc::GPhaseOp, qco::GPhaseOp, 0, 1>>(
Expand Down
Loading
Loading