diff --git a/CHANGELOG.md b/CHANGELOG.md index 614fe3c174..3a326bb021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,13 +39,13 @@ with the exception that minor releases may include breaking changes. ([#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], - [#1710], [#1717], [#1728], [#1730], [#1749], [#1751], [#1762], [#1765], - [#1774], [#1780], [#1781], [#1782], [#1787], [#1802]) + [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1603], [#1620], + [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], + [#1700], [#1710], [#1717], [#1728], [#1730], [#1749], [#1751], [#1762], + [#1765], [#1774], [#1780], [#1781], [#1782], [#1787], [#1802]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], - [**@simon1hofmann**]) + [**@simon1hofmann**], [**@J4MMlE**]) ### Changed @@ -643,6 +643,7 @@ changelogs._ [#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 diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index ed94af35bc..862144f867 100644 --- a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -944,6 +944,30 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { QCProgramBuilder& inv(ValueRange qubits, const function_ref& body); + /** + * @brief Apply a power operation. + * + * @param exponent The exponent to raise the operation to + * @param qubits The qubits the body operates on (aliased into the body via + * block arguments) + * @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, q0, [&](ValueRange qubits) { builder.s(qubits[0]); }); + * ``` + * ```mlir + * qc.pow(2.000000e+00) (%a0 = %q0) { + * qc.s %a0 : !qc.qubit + * } : !qc.qubit + * ``` + */ + QCProgramBuilder& pow(const std::variant& exponent, + ValueRange qubits, + const function_ref& body); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 88df7b5586..9834e55e11 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -1027,4 +1027,64 @@ 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. + + The body region may contain an arbitrary amount of unitary and classical operations. + Non-unitary operations, such as `AllocOp` and `MeasureOp`, are not allowed. + + Example: + ```mlir + qc.pow(2.000000e+00) (%a0 = %q0) { + qc.s %a0 : !qc.qubit + } : !qc.qubit + ``` + }]; + + let arguments = (ins F64:$exponent, + Arg, + "the qubits involved in the operation", [MemRead, MemWrite]>:$qubits); + let regions = (region SizedRegion<1>:$region); + let assemblyFormat = [{ + `(` $exponent `)` + custom($region, $qubits) + attr-dict ( `:` type($qubits)^ )? + }]; + + let extraClassDeclaration = [{ + size_t getNumBodyUnitaries(); + [[nodiscard]] UnitaryOpInterface getBodyUnitary(size_t i); + size_t getNumQubits() { return getNumTargets(); } + size_t getNumTargets() { return getQubits().size(); } + size_t getNumControls() { return 0; } + Value getQubit(size_t i) { return getQubits()[i]; } + Value getTarget(size_t i) { return getQubits()[i]; } + OperandRange getTargets() { return getQubits(); } + Value getControl(size_t i) { llvm::reportFatalUsageError("PowOp does not have controls"); } + OperandRange getControls() { return {nullptr, 0}; } + size_t getNumParams() { return 0; } + Value getParameter(size_t i) { llvm::reportFatalUsageError("PowOp does not have parameters"); } + OperandRange getParameters() { return {nullptr, 0}; } + double getExponentValue(); + static StringRef getBaseSymbol() { return "pow"; } + }]; + + let builders = [OpBuilder<(ins "const std::variant&":$exponent, + "ValueRange":$qubits, + "const llvm::function_ref&":$bodyBuilder)>]; + + let hasCanonicalizer = 1; + let hasVerifier = 1; +} + #endif // MLIR_DIALECT_QC_IR_QCOPS_TD diff --git a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h index 12d7ef73d1..9953135908 100644 --- a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -1201,6 +1201,33 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { ValueRange inv(ValueRange qubits, function_ref(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 + * + * @par Example: + * ```c++ + * qubits_out = builder.pow(q0_in, 2.0, + * [&](ValueRange qubits) -> SmallVector { + * 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& exponent, + function_ref(ValueRange)> body); + //===--------------------------------------------------------------------===// // Deallocation //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index f6283bf943..74c8e529b5 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -1217,6 +1217,79 @@ def InvOp : QCOOp<"inv", traits = [UnitaryOpInterface, 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, + "the qubits involved in the operation", [MemRead]>:$qubits_in); + let results = (outs Variadic:$qubits_out); + let regions = (region SizedRegion<1>:$region); + let assemblyFormat = [{ + `(` $exponent `)` custom($region, $qubits_in) + attr-dict `:` + `{` type($qubits_in) `}` + `->` + `{` type($qubits_out) `}` + }]; + + let extraClassDeclaration = [{ + size_t getNumBodyUnitaries(); + [[nodiscard]] UnitaryOpInterface getBodyUnitary(size_t i); + 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 0; } + Value getParameter(size_t i) { llvm::reportFatalUsageError("PowOp does not have parameters"); } + OperandRange getParameters() { return {nullptr, 0}; } + double getExponentValue(); + [[nodiscard]] static StringRef getBaseSymbol() { return "pow"; } + [[nodiscard]] bool hasCompileTimeKnownUnitaryMatrix(); + [[nodiscard]] std::optional getUnitaryMatrix(); + }]; + + let builders = [OpBuilder<(ins "ValueRange":$qubits, + "const std::variant&":$exponent)>, + OpBuilder<(ins "ValueRange":$qubits, + "const std::variant&":$exponent, + "llvm::function_ref(ValueRange)" + ">":$bodyBuilder)>]; + + let hasCanonicalizer = 1; + let hasVerifier = 1; +} + //===----------------------------------------------------------------------===// // SCF operations //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index 4135ca1769..fc5dbb0a9c 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -20,12 +20,38 @@ #include #include +#include #include #include +#include #include 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; +} + /// Default absolute tolerance for MLIR dialect numerics (angle wrapping, /// phase-zero checks). constexpr auto TOLERANCE = 1e-15; diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 768e2d2379..6ad51ebf2a 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -696,6 +696,45 @@ struct ConvertQCOInvOp final : OpConversionPattern { } }; +/** + * @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) (%a0 = %q0) { + * qc.s %a0 : !qc.qubit + * } : !qc.qubit + * ``` + */ +struct ConvertQCOPowOp final : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(qco::PowOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Create qc.pow operation with exponent and qubit operands + auto qcOp = qc::PowOp::create(rewriter, op.getLoc(), adaptor.getExponent(), + adaptor.getQubitsIn()); + + if (failed(moveRegion(op.getRegion(), qcOp.getRegion(), rewriter, + getTypeConverter()))) { + return failure(); + } + + // Replace the output qubits with the same QC references + rewriter.replaceOp(op, adaptor.getQubitsIn()); + + return success(); + } +}; + /** * @brief Converts qco.yield to qc.yield or to scf.yield if the parent is a * scf::IfOp @@ -996,9 +1035,10 @@ struct QCOToQC final : impl::QCOToQCBase { #undef MQT_ADD_QCO_TO_QC_GATE patterns.add(typeConverter, context); + ConvertQCOPowOp, ConvertQCOYieldOp, ConvertQCOIfOp, + ConvertQCOSCFWhileOp, ConvertQCOSCFConditionOp, + ConvertQCOSCFYieldOp, ConvertQCOSCFForOp>(typeConverter, + context); // Register operation conversion patterns that need state tracking patterns.add { } }; +/** + * @brief Converts qc.pow to qco.pow + * + * @par Example: + * ```mlir + * qc.pow(2.000000e+00) (%a0 = %q0) { + * qc.s %a0 : !qc.qubit + * } : !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 { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::PowOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + auto* operation = op.getOperation(); + 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()); + + auto qcArgs = op.getRegion().front().getArguments(); + + // Inline region and convert the block signature to QCO types. + if (failed(moveRegion(op.getRegion(), qcoOp.getRegion(), rewriter, + getTypeConverter()))) { + return failure(); + } + + pushModifierFrame(state, qcArgs, qcoOp.getRegion().front().getArguments()); + + rewriter.eraseOp(op); + return success(); + } +}; + /** * @brief Converts qc.yield to qco.yield * @@ -1608,8 +1658,8 @@ struct QCToQCO final : impl::QCToQCOBase { 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>( diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index 03d7acb815..66c8ae05a5 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -484,6 +484,15 @@ QCProgramBuilder::inv(ValueRange qubits, return *this; } +QCProgramBuilder& +QCProgramBuilder::pow(const std::variant& exponent, + ValueRange qubits, + const function_ref& body) { + checkFinalized(); + PowOp::create(*this, exponent, qubits, body); + return *this; +} + //===----------------------------------------------------------------------===// // SCF operations //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp index b1caa45344..81719f6372 100644 --- a/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp @@ -77,6 +77,49 @@ struct MoveCtrlOutside final : OpRewritePattern { } }; +/** + * @brief Eliminate inv by negating the pow exponent, i.e., + * `inv(pow(p){U}) => pow(-p){U}`. + * + * @details This is always valid for unitaries: `(U^p)† = U^{-p}`. + * Downstream patterns (e.g., `NegPowToInvPow`) can then rewrite + * `pow(-p){U} => pow(p){inv(U)}` when the exponent is an integer. + */ +struct InvPowToNegPow final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(InvOp invOp, + PatternRewriter& rewriter) const override { + auto inner = + utils::getSoleBodyUnitary(*invOp.getBody()); + if (!inner) { + return failure(); + } + auto innerPow = dyn_cast(inner.getOperation()); + if (!innerPow) { + return failure(); + } + const double exponent = innerPow.getExponentValue(); + // The inner pow's operands alias the inv's block args; translate them back + // to the outer qubits the inv aliases so the new pow is valid in the inv's + // parent scope. + SmallVector qubits; + for (Value v : innerPow.getQubits()) { + auto arg = cast(v); + assert(arg.getOwner() == invOp.getBody() && "inner qubit not an inv arg"); + qubits.push_back(invOp.getQubits()[arg.getArgNumber()]); + } + rewriter.replaceOpWithNewOp( + invOp, -exponent, qubits, [&](ValueRange powArgs) { + auto* powBody = rewriter.getInsertionBlock(); + // Inner pow body args now match the new pow's args positionally. + rewriter.inlineBlockBefore(innerPow.getBody(), powBody, + powBody->begin(), powArgs); + rewriter.eraseOp(&powBody->back()); // erase the inlined YieldOp + }); + return success(); + } +}; + /** * @brief Remove inverse modifiers around self-adjoint gates. * @@ -338,6 +381,6 @@ LogicalResult InvOp::verify() { void InvOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/QC/IR/Modifiers/PowOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/PowOp.cpp new file mode 100644 index 0000000000..e6d9539e92 --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Modifiers/PowOp.cpp @@ -0,0 +1,624 @@ +/* + * 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/QC/IR/QCDialect.h" +#include "mlir/Dialect/QC/IR/QCInterfaces.h" +#include "mlir/Dialect/QC/IR/QCOps.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qc; +using namespace mlir::utils; +using llvm::make_early_inc_range; +using llvm::reportFatalUsageError; + +/** + * @brief If the computed P-gate angle corresponds to a named gate, emit it + * directly. + * + * @details Uses these equivalences: + * + * `Z = P(π)`, `S = P(π/2)`, `Sdg = P(-π/2)`, `T = P(π/4)`, `Tdg = P(-π/4)` + * + * Since `P` is diagonal, raising to a power just multiplies the angle: + * + * ``` + * Z^r = P(π)^r = P(r·π) + * S^r = P(π/2)^r = P(r·π/2) + * Sdg^r = P(-π/2)^r = P(-r·π/2) + * T^r = P(π/4)^r = P(r·π/4) + * Tdg^r = P(-π/4)^r = P(-r·π/4) + * ``` + * + * The caller computes `angle = r * base_angle` and passes the raw + * (unnormalized) value here; normalization to (-π, π] is performed internally. + * + * Matched angles and their replacements: + * + * | Angle | Replacement | + * |----------------|-------------| + * | `angle ≈ 0` | identity (op replaced with qubit pass-through) | + * | `angle ≈ +/-π` | `Z` | + * | `angle ≈ π/2` | `S` | + * | `angle ≈ -π/2` | `Sdg` | + * | `angle ≈ π/4` | `T` | + * | `angle ≈ -π/4` | `Tdg` | + * + * @param angle Raw phase angle (`r * base_angle`), in radians. + * @param op The `PowOp` being rewritten. + * @param rewriter The pattern rewriter. + * @return `success()` if replaced, `failure()` if a general `P` gate should be + * used. + */ +static LogicalResult tryReplaceWithNamedPhaseGate(double angle, PowOp op, + PatternRewriter& rewriter, + bool insideModifier) { + const double norm = normalizeAngle(angle); + const double pi = std::numbers::pi; + + if (std::abs(norm) < TOLERANCE) { + if (insideModifier) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + } else { + rewriter.eraseOp(op); + } + return success(); + } + if (std::abs(std::abs(norm) - pi) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + } + if (std::abs(norm - (pi / 2.0)) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + } + if (std::abs(norm + (pi / 2.0)) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + } + if (std::abs(norm - (pi / 4.0)) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + } + if (std::abs(norm + (pi / 4.0)) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + } + return failure(); +} + +/// Materialize exponent * param as arith ops +static Value scaleByExponent(Value param, PowOp op, PatternRewriter& rewriter) { + return arith::MulFOp::create(rewriter, op.getLoc(), op.getExponent(), param); +} + +namespace { + +/// pow(1.0) { g } => g +struct InlinePow1 final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + if (std::abs(op.getExponentValue() - 1.0) > TOLERANCE) { + return failure(); + } + auto inner = utils::getSoleBodyUnitary(*op.getBody()); + if (!inner) { + return failure(); + } + utils::inlineModifierBody(op, *op.getBody(), op.getQubits(), rewriter); + return success(); + } +}; + +/// pow(0.0) { g } => identity (no-op) +struct ErasePow0 final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + if (std::abs(op.getExponentValue()) > TOLERANCE) { + return failure(); + } + // Top-level pow(0) is the identity and can simply be removed. Inside a + // modifier the body must stay non-empty, so emit one IdOp per qubit + // (operands) before erasing. + if (isa(op->getParentOp())) { + rewriter.setInsertionPoint(op); + for (auto qubit : op.getQubits()) { + IdOp::create(rewriter, op.getLoc(), qubit); + } + } + rewriter.eraseOp(op); + return success(); + } +}; + +/// pow(p) where p < 0 => pow(-p) { inv(q) { g } } +struct NegPowToInvPow final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + auto inner = utils::getSoleBodyUnitary(*op.getBody()); + if (!inner) { + return failure(); + } + const double exp = op.getExponentValue(); + // U^{-r} = (U^{-1})^r only when r is an integer: for fractional r, + // eigenvalue -1 yields (-1)^{-r} ≠ (-1)^r (conjugated phase factors). + if (exp >= 0.0 || !utils::isIntegerExponent(-exp)) { + return failure(); + } + auto qubits = llvm::to_vector(op.getQubits()); + rewriter.replaceOpWithNewOp( + op, -exp, qubits, [&](ValueRange powArgs) { + InvOp::create(rewriter, op.getLoc(), powArgs, [&](ValueRange) { + auto* invBody = rewriter.getInsertionBlock(); + // Inline the old pow body, remapping its block args to the new + // inv body's block args. + rewriter.inlineBlockBefore(op.getBody(), invBody, invBody->begin(), + invBody->getArguments()); + rewriter.eraseOp(&invBody->back()); // erase the inlined YieldOp + }); + }); + return success(); + } +}; + +/// pow(a) { pow(b) { g } } => pow(a*b) { g } +struct MergeNestedPow final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + auto inner = utils::getSoleBodyUnitary(*op.getBody()); + if (!inner) { + return failure(); + } + auto innerPow = dyn_cast(inner.getOperation()); + if (!innerPow) { + return failure(); + } + // The inner pow's operands alias the outer pow's block args, possibly in a + // different order / subset. Translate them back to the outer pow's operands + // so the merged pow's footprint matches the inner pow positionally. + SmallVector qubits; + for (Value v : innerPow.getQubits()) { + auto arg = cast(v); + assert(arg.getOwner() == op.getBody() && "inner qubit not an outer arg"); + qubits.push_back(op.getQubits()[arg.getArgNumber()]); + } + rewriter.replaceOpWithNewOp( + op, op.getExponentValue() * innerPow.getExponentValue(), qubits, + [&](ValueRange powArgs) { + auto* newBody = rewriter.getInsertionBlock(); + // Inner pow body args now match the new pow's args positionally. + rewriter.inlineBlockBefore(innerPow.getBody(), newBody, + newBody->begin(), powArgs); + rewriter.eraseOp(&newBody->back()); // erase the inlined YieldOp + }); + return success(); + } +}; + +/// pow(p) { ctrl(c, t) { g } } => ctrl(c, t) { pow(p) { g } } +struct MoveCtrlOutside final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + auto inner = utils::getSoleBodyUnitary(*op.getBody()); + if (!inner) { + return failure(); + } + auto innerCtrl = dyn_cast(inner.getOperation()); + if (!innerCtrl) { + return failure(); + } + // The inner ctrl's operands alias the outer pow's block args; translate + // them back to the outer pow's operands so the hoisted ctrl is valid in + // the pow's parent scope. + auto translate = [&](Value v) -> Value { + auto arg = cast(v); + assert(arg.getOwner() == op.getBody() && "ctrl qubit not an outer arg"); + return op.getQubits()[arg.getArgNumber()]; + }; + SmallVector controls; + SmallVector targets; + for (Value c : innerCtrl.getControls()) { + controls.push_back(translate(c)); + } + for (Value t : innerCtrl.getTargets()) { + targets.push_back(translate(t)); + } + rewriter.replaceOpWithNewOp( + op, controls, targets, [&](ValueRange ctrlArgs) { + // ctrlArgs are the new ctrl's target block args; the pow wraps the + // body gate acting on those targets. + PowOp::create( + rewriter, op.getLoc(), op.getExponentValue(), ctrlArgs, + [&](ValueRange powArgs) { + auto* powBody = rewriter.getInsertionBlock(); + // Inline the old CtrlOp's body, remapping its (target) block + // args to the new pow's block args. + rewriter.inlineBlockBefore(innerCtrl.getBody(), powBody, + powBody->begin(), powArgs); + rewriter.eraseOp(&powBody->back()); // erase the inlined YieldOp + }); + }); + return success(); + } +}; + +/** + * @brief Fold pow(r) around gates into simpler operations. + * + * @details + * - Rotation gates: multiply angle by exponent, + * e.g., `pow(r) { rx(θ) } => rx(r*θ)` + * - Phase/diagonal gates: named gate if angle matches, else `P` gate, + * e.g., `pow(r) { s } => s/sdg/t/tdg/z` or `p(r*π/2)` + * - Hermitian gates (integer exponent): even => erase, odd => gate + * - Identity/barrier: pass through unchanged + */ +struct FoldPowIntoGate final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + auto inner = utils::getSoleBodyUnitary(*op.getBody()); + if (!inner) { + return failure(); + } + auto* innerOp = inner.getOperation(); + const double r = op.getExponentValue(); + auto loc = op.getLoc(); + const bool insideModifier = isa(op->getParentOp()); + + // Folds for X/Y/SX/SXdg emit an additional GPhase op, which is not + // allowed when nested inside a modifier (single-child constraint). + if (isa(innerOp) && insideModifier) { + return failure(); + } + + // Pre-check: only proceed for gate types we can fold. + // HOp, ECROp, SWAPOp additionally require an integer exponent. + if (isa(innerOp) && !utils::isIntegerExponent(r)) { + return failure(); + } + if (!isa( + innerOp)) { + return failure(); + } + + // Move supporting ops (constants, arithmetic) out of the body so their + // Values are accessible from outside and survive PowOp erasure. + for (auto& bodyOp : make_early_inc_range(*op.getBody())) { + if (&bodyOp != innerOp && !isa(&bodyOp)) { + rewriter.moveOpBefore(&bodyOp, op); + } + } + + return TypeSwitch(innerOp) + // --- Rotation gates: multiply angle by exponent --- + // pow(r) { gphase(θ) } => gphase(r*θ) + .Case([&](auto gate) { + auto newParam = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp(op, newParam); + return success(); + }) + // pow(r) { rx/ry/rz/p(θ) } => rx/ry/rz/p(r*θ) + .Case([&](auto gate) { + auto newParam = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp(op, op.getTarget(0), + newParam); + return success(); + }) + // pow(r) { rxx/ryy/rzx/rzz(θ) } => rxx/ryy/rzx/rzz(r*θ) + .Case([&](auto gate) { + auto newParam = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), op.getTarget(1), newParam); + return success(); + }) + // pow(r) { r(θ, φ) } => r(r*θ, φ) + .Case([&](auto gate) { + auto mul = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp(op, op.getTarget(0), mul, + gate.getPhi()); + return success(); + }) + // pow(r) { xx±yy(θ, β) } => xx±yy(r*θ, β) + .Case([&](auto gate) { + auto mul = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), op.getTarget(1), mul, gate.getBeta()); + return success(); + }) + // --- Pauli gates: decompose to rotation + global phase --- + // pow(r) { z } => named gate if angle matches, else p(r*π) + .Case([&](auto) { + const double angle = r * std::numbers::pi; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * std::numbers::pi)); + return success(); + }) + // pow(r) { x } => gphase(-r*π/2); rx(r*π) + // pow(1/2) x => sx (X^(1/2) = SX exactly) + // pow(-1/2) x => sxdg (X^(-1/2) = SXdg exactly) + .Case([&](auto) { + if (std::abs(r - 0.5) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + } + if (std::abs(r + 0.5) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + } + GPhaseOp::create( + rewriter, loc, + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 2.0))); + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * std::numbers::pi)); + return success(); + }) + // pow(r) { y } => gphase(-r*π/2); ry(r*π) + .Case([&](auto) { + GPhaseOp::create( + rewriter, loc, + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 2.0))); + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * std::numbers::pi)); + return success(); + }) + // --- Phase/diagonal gates: named gate if angle matches, else P gate + // --- pow(r) { s } => named gate if angle matches, else p(r*π/2) + .Case([&](auto) { + const double angle = r * std::numbers::pi / 2.0; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (std::numbers::pi / 2.0))); + return success(); + }) + // pow(r) { sdg } => named gate if angle matches, else p(-r*π/2) + .Case([&](auto) { + const double angle = r * -std::numbers::pi / 2.0; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 2.0))); + return success(); + }) + // pow(r) { t } => named gate if angle matches, else p(r*π/4) + .Case([&](auto) { + const double angle = r * std::numbers::pi / 4.0; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (std::numbers::pi / 4.0))); + return success(); + }) + // pow(r) { tdg } => named gate if angle matches, else p(-r*π/4) + .Case([&](auto) { + const double angle = r * -std::numbers::pi / 4.0; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 4.0))); + return success(); + }) + // --- SX/SXdg gates: decompose to rotation + global phase --- + // pow(r) { sx } => gphase(-r*π/4); rx(r*π/2) + // pow(±2) sx => x + .Case([&](auto) { + if (std::abs(std::abs(r) - 2.0) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + } + GPhaseOp::create( + rewriter, loc, + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 4.0))); + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (std::numbers::pi / 2.0))); + return success(); + }) + // pow(r) { sxdg } => gphase(r*π/4); rx(-r*π/2) + // pow(±2) sxdg => x + .Case([&](auto) { + if (std::abs(std::abs(r) - 2.0) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + } + GPhaseOp::create( + rewriter, loc, + utils::constantFromScalar(rewriter, op.getLoc(), + r * (std::numbers::pi / 4.0))); + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 2.0))); + return success(); + }) + // --- Hermitian gates (integer exponent): even => erase/id, odd => gate + // --- pow(n) { h } => id (n even) | h (n odd) + .Case([&](auto) { + if (!utils::isIntegerExponent(r)) { + return failure(); + } + if (utils::isEvenExponent(r)) { + if (insideModifier) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + } else { + rewriter.eraseOp(op); + } + } else { + // pow(odd) { h } => h. The body gate's operands alias the pow's + // block args; inline the body, remapping them to the outer qubit + // operands, instead of hoisting the gate out (which would leave it + // referencing the erased block args). + utils::inlineModifierBody(op, *op.getBody(), op.getQubits(), + rewriter); + } + return success(); + }) + // pow(n) { ecr/swap } => erase (n even) | ecr/swap (n odd) + .Case([&](auto) { + if (!utils::isIntegerExponent(r)) { + return failure(); + } + if (utils::isEvenExponent(r)) { + if (insideModifier) { + return failure(); + } + rewriter.eraseOp(op); + } else { + // pow(odd) { ecr/swap } => ecr/swap. Inline the body, remapping its + // block args to the outer qubit operands (see HOp case above). + utils::inlineModifierBody(op, *op.getBody(), op.getQubits(), + rewriter); + } + return success(); + }) + // --- iSWAP: decompose to parametric gate --- + // pow(r) { iswap } => xx_plus_yy(-r*π, 0) + .Case([&](auto) { + rewriter.replaceOpWithNewOp( + op, op.getTarget(0), op.getTarget(1), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi)), + utils::constantFromScalar(rewriter, op.getLoc(), 0.0)); + return success(); + }) + // --- Identity and barrier: pass through unchanged --- + // pow(r) { id } => id + .Case([&](auto) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + return success(); + }) + // pow(r) { barrier } => barrier + .Case([&](auto) { + rewriter.replaceOpWithNewOp(op, op.getTargets()); + return success(); + }) + .Default([&](auto) { return failure(); }); + } +}; + +} // namespace + +double PowOp::getExponentValue() { + FloatAttr attr; + if (!matchPattern(getExponent(), m_Constant(&attr))) { + reportFatalUsageError("PowOp exponent must be a constant"); + } + return attr.getValueAsDouble(); +} + +size_t PowOp::getNumBodyUnitaries() { + return utils::getNumBodyUnitaries(*getBody()); +} + +UnitaryOpInterface PowOp::getBodyUnitary(const size_t i) { + return utils::getBodyUnitary(*getBody(), i); +} + +void PowOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const std::variant& exponent, + ValueRange qubits, + const function_ref& bodyBuilder) { + auto expValue = variantToValue(odsBuilder, odsState.location, exponent); + build(odsBuilder, odsState, expValue, qubits); + auto& block = odsState.regions.front()->emplaceBlock(); + + auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < qubits.size(); ++i) { + block.addArgument(qubitType, odsState.location); + } + + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + bodyBuilder(block.getArguments()); + YieldOp::create(odsBuilder, odsState.location); +} + +LogicalResult PowOp::verify() { + FloatAttr attr; + if (!matchPattern(getExponent(), m_Constant(&attr))) { + return emitOpError("exponent must be a constant"); + } + if (llvm::any_of(*getBody(), [](Operation& op) { + return isa(op); + })) { + return emitOpError("body must not contain non-unitary quantum operations " + "or modify a quantum register"); + } + if (getNumBodyUnitaries() < 1) { + return emitOpError( + "body region must contain at least one unitary operation"); + } + return success(); +} + +void PowOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index da20f3bfa4..cd39274b03 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -16,7 +16,6 @@ #include "mlir/Dialect/QTensor/IR/QTensorOps.h" #include "mlir/Dialect/Utils/Utils.h" -#include #include #include #include @@ -879,6 +878,43 @@ QCOProgramBuilder::inv(ValueRange qubits, return targetsOut; } +ValueRange +QCOProgramBuilder::pow(ValueRange qubits, + const std::variant& exponent, + function_ref(ValueRange)> body) { + checkFinalized(); + + auto powOp = PowOp::create(*this, qubits, exponent); + + // Add block arguments for all qubits + auto& block = powOp.getBodyRegion().emplaceBlock(); + const auto qubitType = QubitType::get(getContext()); + for (const auto qubit : qubits) { + const auto arg = block.addArgument(qubitType, getLoc()); + updateQubitTracking(qubit, arg); + } + + // Create the final yield operation + const InsertionGuard guard(*this); + setInsertionPointToStart(&block); + const auto innerTargetsOut = body(block.getArguments()); + YieldOp::create(*this, innerTargetsOut); + + if (innerTargetsOut.size() != qubits.size()) { + llvm::reportFatalUsageError( + "Pow body must return exactly one output qubit per target"); + } + + // Update tracking + const auto& targetsOut = powOp.getQubitsOut(); + for (const auto& [target, targetOut] : + llvm::zip_equal(innerTargetsOut, targetsOut)) { + updateQubitTracking(target, targetOut); + } + + return targetsOut; +} + //===----------------------------------------------------------------------===// // Deallocation //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp index c6d6a36679..7b09cf1766 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -93,6 +94,45 @@ struct MoveCtrlOutside final : OpRewritePattern { } }; +/** + * @brief Eliminate inv by negating the pow exponent, i.e., + * `inv(pow(p){U}) => pow(-p){U}`. + * + * This is always valid for unitaries: `(U^p)† = U^{-p}`. + * Downstream patterns (e.g., `NegPowToInvPow`) can then rewrite + * `pow(-p){U} => pow(p){inv(U)}` when the exponent is an integer. + */ +struct InvPowToNegPow final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(InvOp invOp, + PatternRewriter& rewriter) const override { + auto inner = + utils::getSoleBodyUnitary(*invOp.getBody()); + if (!inner) { + return failure(); + } + auto innerPow = dyn_cast(inner.getOperation()); + if (!innerPow) { + return failure(); + } + + const double exponent = innerPow.getExponentValue(); + + rewriter.replaceOpWithNewOp( + invOp, invOp.getQubitsIn(), -exponent, + [&](ValueRange powArgs) -> llvm::SmallVector { + auto* powBody = rewriter.getInsertionBlock(); + rewriter.inlineBlockBefore(innerPow.getBody(), powBody, + powBody->begin(), powArgs); + auto yieldedValues = llvm::to_vector(powBody->back().getOperands()); + rewriter.eraseOp(&powBody->back()); + return yieldedValues; + }); + return success(); + } +}; + /** * @brief Remove inverse modifiers around self-adjoint gates. * @@ -401,8 +441,8 @@ LogicalResult InvOp::verify() { void InvOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } bool InvOp::hasCompileTimeKnownUnitaryMatrix() { diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/PowOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/PowOp.cpp new file mode 100644 index 0000000000..f2babb1f5b --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/PowOp.cpp @@ -0,0 +1,725 @@ +/* + * 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/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Utils/Matrix.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace mlir; +using namespace mlir::qco; +using namespace mlir::utils; +using llvm::reportFatalUsageError; +using llvm::to_vector; + +/** + * @brief If the computed P-gate angle corresponds to a named gate, emit it + * directly. + * + * @details Uses these equivalences: + * + * `Z = P(π)`, `S = P(π/2)`, `Sdg = P(-π/2)`, `T = P(π/4)`, `Tdg = P(-π/4)` + * + * Since `P` is diagonal, raising to a power just multiplies the angle: + * + * ``` + * Z^r = P(π)^r = P(r·π) + * S^r = P(π/2)^r = P(r·π/2) + * Sdg^r = P(-π/2)^r = P(-r·π/2) + * T^r = P(π/4)^r = P(r·π/4) + * Tdg^r = P(-π/4)^r = P(-r·π/4) + * ``` + * + * The caller computes `angle = r * base_angle` and passes the raw + * (unnormalized) value here; normalization to (-π, π] is performed internally. + * + * Matched angles and their replacements: + * + * | Angle | Replacement | + * |----------------|-------------| + * | `angle ≈ 0` | identity (op replaced with qubit pass-through) | + * | `angle ≈ +/-π` | `Z` | + * | `angle ≈ π/2` | `S` | + * | `angle ≈ -π/2` | `Sdg` | + * | `angle ≈ π/4` | `T` | + * | `angle ≈ -π/4` | `Tdg` | + * + * @param angle Raw phase angle (`r * base_angle`), in radians. + * @param op The `PowOp` being rewritten. + * @param rewriter The pattern rewriter. + * @return `success()` if replaced, `failure()` if a general `P` gate should be + * used. + */ +static LogicalResult tryReplaceWithNamedPhaseGate(double angle, PowOp op, + PatternRewriter& rewriter, + bool insideModifier) { + const double norm = normalizeAngle(angle); + const double pi = std::numbers::pi; + + if (std::abs(norm) < TOLERANCE) { + if (insideModifier) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + } else { + rewriter.replaceOp(op, op.getQubitsIn()); + } + return success(); + } + if (std::abs(std::abs(norm) - pi) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + } + if (std::abs(norm - (pi / 2.0)) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + } + if (std::abs(norm + (pi / 2.0)) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + } + if (std::abs(norm - (pi / 4.0)) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + } + if (std::abs(norm + (pi / 4.0)) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + } + return failure(); +} + +/// Materialize exponent * param as arith ops +static Value scaleByExponent(auto param, PowOp op, PatternRewriter& rewriter) { + return arith::MulFOp::create(rewriter, op.getLoc(), op.getExponent(), param); +} + +namespace { + +/// pow(1.0) { g } => inline g +struct InlinePow1 final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + if (std::abs(op.getExponentValue() - 1.0) > TOLERANCE) { + return failure(); + } + + utils::inlineModifierBody(op, *op.getBody(), op.getInputQubits(), rewriter); + return success(); + } +}; + +/// pow(0.0) { g } => identity (pass-through) +struct ErasePow0 final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + if (std::abs(op.getExponentValue()) > TOLERANCE) { + return failure(); + } + + if (isa(op->getParentOp())) { + if (op.getNumTargets() == 1) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + } else { + return failure(); + } + } else { + rewriter.replaceOp(op, op.getQubitsIn()); + } + return success(); + } +}; + +/// pow(p) where p < 0 => pow(-p) { inv { g } } +struct NegPowToInvPow final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + const double exp = op.getExponentValue(); + // U^{-r} = (U^{-1})^r only when r is an integer: for fractional r, + // eigenvalue -1 yields (-1)^{-r} ≠ (-1)^r (conjugated phase factors). + if (exp >= 0.0 || !utils::isIntegerExponent(-exp)) { + return failure(); + } + + rewriter.replaceOpWithNewOp( + op, op.getQubitsIn(), -exp, + [&](ValueRange powArgs) -> SmallVector { + return InvOp::create(rewriter, op.getLoc(), powArgs, + [&](ValueRange invArgs) -> SmallVector { + auto* invBody = rewriter.getInsertionBlock(); + rewriter.inlineBlockBefore( + op.getBody(), invBody, invBody->begin(), + invArgs); + auto yieldedValues = + to_vector(invBody->back().getOperands()); + rewriter.eraseOp(&invBody->back()); + return yieldedValues; + }) + .getResults(); + }); + + return success(); + } +}; + +/// pow(a) { pow(b) { g } } => pow(a*b) { g } +struct MergeNestedPow final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + auto bodyUnitary = + utils::getSoleBodyUnitary(*op.getBody()); + if (!bodyUnitary) { + return failure(); + } + auto innerPow = dyn_cast(bodyUnitary.getOperation()); + if (!innerPow) { + return failure(); + } + + const double merged = op.getExponentValue() * innerPow.getExponentValue(); + auto mergedConst = arith::ConstantFloatOp::create( + rewriter, op.getLoc(), rewriter.getF64Type(), APFloat(merged)); + + rewriter.moveOpBefore(innerPow, op); + rewriter.modifyOpInPlace(innerPow, [&]() { + innerPow.getExponentMutable().assign(mergedConst.getResult()); + innerPow.getQubitsInMutable().assign(op.getInputQubits()); + }); + rewriter.replaceOp(op, innerPow->getResults()); + return success(); + } +}; + +/// pow(p) { ctrl(q, g) } => ctrl(q, pow(p, g)) +struct MoveCtrlOutside final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + auto bodyUnitary = + utils::getSoleBodyUnitary(*op.getBody()); + if (!bodyUnitary) { + return failure(); + } + auto innerCtrlOp = dyn_cast(bodyUnitary.getOperation()); + if (!innerCtrlOp) { + return failure(); + } + + const auto numControls = innerCtrlOp.getNumControls(); + const auto numTargets = innerCtrlOp.getNumTargets(); + auto inputQubits = op.getInputQubits(); + auto controls = inputQubits.take_front(numControls); + auto targets = inputQubits.take_back(numTargets); + const double exponent = op.getExponentValue(); + + rewriter.replaceOpWithNewOp( + op, controls, targets, + [&](ValueRange ctrlTargetArgs) -> SmallVector { + return PowOp::create(rewriter, op.getLoc(), ctrlTargetArgs, exponent, + [&](ValueRange powArgs) -> SmallVector { + auto* powBody = rewriter.getInsertionBlock(); + rewriter.inlineBlockBefore( + innerCtrlOp.getBody(), powBody, + powBody->begin(), powArgs); + auto yieldedValues = + to_vector(powBody->back().getOperands()); + rewriter.eraseOp(&powBody->back()); + return yieldedValues; + }) + .getResults(); + }); + + return success(); + } +}; + +/** + * @brief Fold pow(r) around gates into simpler operations. + * + * @details + * - Rotation gates: multiply angle by exponent, + * e.g., `pow(r) { rx(θ) } => rx(r*θ)` + * - Phase/diagonal gates: named gate if angle matches, else `P` gate, + * e.g., `pow(r) { s } => s/sdg/t/tdg/z` or `p(r*π/2)` + * - Hermitian gates (integer exponent): even => erase, odd => gate + * - Identity/barrier: pass through unchanged + */ +struct FoldPowIntoGate final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(PowOp op, + PatternRewriter& rewriter) const override { + auto bodyUnitary = + utils::getSoleBodyUnitary(*op.getBody()); + if (!bodyUnitary) { + return failure(); + } + auto* innerOp = bodyUnitary.getOperation(); + const double r = op.getExponentValue(); + auto loc = op.getLoc(); + const bool insideModifier = isa(op->getParentOp()); + + // Folds for X/Y/SX/SXdg emit an additional GPhase op, which is not + // allowed when nested inside a modifier (single-child constraint). + if (isa(innerOp) && insideModifier) { + return failure(); + } + + // Even-parity folds for multi-qubit hermitian gates produce identity. + // Bail out before inlining when inside a modifier, since we cannot + // represent a multi-qubit identity as a single body unitary. + if (insideModifier && isa(innerOp) && + utils::isIntegerExponent(r) && utils::isEvenExponent(r)) { + return failure(); + } + + // Pre-check: only proceed for gate types we can fold. + // HOp, ECROp, SWAPOp additionally require an integer exponent. + if (isa(innerOp) && !utils::isIntegerExponent(r)) { + return failure(); + } + if (!isa( + innerOp)) { + return failure(); + } + + // Inline the body before op so all parameter-defining ops (constants, + // arithmetic) are in scope and survive op replacement. + rewriter.inlineBlockBefore(op.getBody(), op, op.getInputQubits()); + rewriter.eraseOp(op->getPrevNode()); // erase the now-inlined YieldOp + rewriter.setInsertionPoint(op); + + const LogicalResult result = + TypeSwitch(innerOp) + // --- Rotation gates: multiply angle by exponent --- + // pow(r) { gphase(θ) } => gphase(r*θ) + .Case([&](auto gate) { + auto newParam = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp(op, newParam); + return success(); + }) + // pow(r) { rx/ry/rz/p(θ) } => rx/ry/rz/p(r*θ) + .Case([&](auto gate) { + auto newParam = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), newParam); + return success(); + }) + // pow(r) { rxx/ryy/rzx/rzz(θ) } => rxx/ryy/rzx/rzz(r*θ) + .Case([&](auto gate) { + auto newParam = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), op.getInputTarget(1), newParam); + return success(); + }) + // pow(r) { r(θ, φ) } => r(r*θ, φ) + .Case([&](auto gate) { + auto mul = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), mul, + gate.getPhi()); + return success(); + }) + // pow(r) { xx±yy(θ, β) } => xx±yy(r*θ, β) + .Case([&](auto gate) { + auto mul = scaleByExponent(gate.getTheta(), op, rewriter); + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), op.getInputTarget(1), mul, + gate.getBeta()); + return success(); + }) + // --- Pauli gates: decompose to rotation + global phase --- + // pow(r) { x } => gphase(-r*π/2); rx(r*π) + // pow(1/2) x => sx (X^(1/2) = SX exactly) + // pow(-1/2) x => sxdg (X^(-1/2) = SXdg exactly) + .Case([&](auto) { + if (std::abs(r - 0.5) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + } + if (std::abs(r + 0.5) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + } + GPhaseOp::create( + rewriter, loc, + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 2.0))); + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * std::numbers::pi)); + return success(); + }) + // pow(r) { y } => gphase(-r*π/2); ry(r*π) + .Case([&](auto) { + GPhaseOp::create( + rewriter, loc, + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 2.0))); + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * std::numbers::pi)); + return success(); + }) + // pow(r) { z } => named gate if angle matches, else p(r*π) + .Case([&](auto) { + const double angle = r * std::numbers::pi; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * std::numbers::pi)); + return success(); + }) + // --- Phase/diagonal gates: named gate if angle matches, else P + // gate + // --- pow(r) { s } => named gate if angle matches, else p(r*π/2) + .Case([&](auto) { + const double angle = r * std::numbers::pi / 2.0; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (std::numbers::pi / 2.0))); + return success(); + }) + // pow(r) { sdg } => named gate if angle matches, else p(-r*π/2) + .Case([&](auto) { + const double angle = r * -std::numbers::pi / 2.0; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 2.0))); + return success(); + }) + // pow(r) { t } => named gate if angle matches, else p(r*π/4) + .Case([&](auto) { + const double angle = r * std::numbers::pi / 4.0; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (std::numbers::pi / 4.0))); + return success(); + }) + // pow(r) { tdg } => named gate if angle matches, else p(-r*π/4) + .Case([&](auto) { + const double angle = r * -std::numbers::pi / 4.0; + if (succeeded(tryReplaceWithNamedPhaseGate(angle, op, rewriter, + insideModifier))) { + return success(); + } + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 4.0))); + return success(); + }) + // --- SX/SXdg gates: decompose to rotation + global phase --- + // pow(r) { sx } => gphase(-r*π/4); rx(r*π/2) + // pow(±2) sx => x + .Case([&](auto) { + if (std::abs(std::abs(r) - 2.0) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + } + GPhaseOp::create( + rewriter, loc, + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 4.0))); + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (std::numbers::pi / 2.0))); + return success(); + }) + // pow(r) { sxdg } => gphase(r*π/4); rx(-r*π/2) + // pow(±2) sxdg => x + .Case([&](auto) { + if (std::abs(std::abs(r) - 2.0) < TOLERANCE) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + } + GPhaseOp::create( + rewriter, loc, + utils::constantFromScalar(rewriter, op.getLoc(), + r * (std::numbers::pi / 4.0))); + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi / 2.0))); + return success(); + }) + // --- Hermitian gates (integer exponent): even => id, odd => gate + // --- pow(n) { h } => id (n even) | h (n odd) + .Case([&](auto gate) { + if (utils::isEvenExponent(r)) { + if (insideModifier) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + } else { + rewriter.replaceOp(op, op.getQubitsIn()); + } + } else { + rewriter.replaceOp(op, gate->getResults()); + } + return success(); + }) + // pow(n) { ecr/swap } => id (n even) | ecr/swap (n odd) + // Note: even + insideModifier is rejected before inlining. + .Case([&](auto gate) { + if (utils::isEvenExponent(r)) { + rewriter.replaceOp(op, op.getQubitsIn()); + } else { + rewriter.replaceOp(op, gate->getResults()); + } + return success(); + }) + // --- iSWAP: decompose to parametric gate --- + // pow(r) { iswap } => xx_plus_yy(-r*π, 0) + // β=0: axis is aligned with XX, matching the iSWAP interaction + // plane + .Case([&](auto) { + rewriter.replaceOpWithNewOp( + op, op.getInputTarget(0), op.getInputTarget(1), + utils::constantFromScalar(rewriter, op.getLoc(), + r * (-std::numbers::pi)), + utils::constantFromScalar(rewriter, op.getLoc(), 0.0)); + return success(); + }) + // --- Identity and barrier: pass through unchanged --- + // pow(r) { id } => id + .Case([&](auto) { + rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + return success(); + }) + // pow(r) { barrier } => barrier + .Case([&](auto) { + rewriter.replaceOpWithNewOp(op, op.getQubitsIn()); + return success(); + }) + .Default([](auto*) -> LogicalResult { + llvm_unreachable("unhandled gate type after pre-check"); + return failure(); // unreachable — satisfies compiler + }); + if (innerOp->use_empty()) { + rewriter.eraseOp(innerOp); + } + return result; + } +}; + +} // namespace + +double PowOp::getExponentValue() { + FloatAttr attr; + if (!matchPattern(getExponent(), m_Constant(&attr))) { + reportFatalUsageError("PowOp exponent must be a constant"); + } + return attr.getValueAsDouble(); +} + +size_t PowOp::getNumBodyUnitaries() { + return utils::getNumBodyUnitaries(*getBody()); +} + +UnitaryOpInterface PowOp::getBodyUnitary(const size_t i) { + return utils::getBodyUnitary(*getBody(), i); +} + +Value PowOp::getInputQubit(const size_t i) { + if (i >= getNumTargets()) { + reportFatalUsageError("Qubit index out of bounds"); + } + return getQubitsIn()[i]; +} + +Value PowOp::getOutputQubit(const size_t i) { + if (i >= getNumTargets()) { + reportFatalUsageError("Qubit index out of bounds"); + } + return getQubitsOut()[i]; +} + +Value PowOp::getInputForOutput(Value output) { + for (size_t i = 0; i < getNumTargets(); ++i) { + if (output == getQubitsOut()[i]) { + return getQubitsIn()[i]; + } + } + reportFatalUsageError("Given qubit is not an output of the operation"); +} + +Value PowOp::getOutputForInput(Value input) { + for (size_t i = 0; i < getNumTargets(); ++i) { + if (input == getQubitsIn()[i]) { + return getQubitsOut()[i]; + } + } + reportFatalUsageError("Given qubit is not an input of the operation"); +} + +void PowOp::build(OpBuilder& odsBuilder, OperationState& odsState, + ValueRange qubits, + const std::variant& exponent) { + auto expValue = variantToValue(odsBuilder, odsState.location, exponent); + build(odsBuilder, odsState, qubits.getTypes(), expValue, qubits); +} + +void PowOp::build(OpBuilder& odsBuilder, OperationState& odsState, + ValueRange qubits, + const std::variant& exponent, + function_ref(ValueRange)> bodyBuilder) { + build(odsBuilder, odsState, qubits, exponent); + auto& block = odsState.regions.front()->emplaceBlock(); + + const auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < qubits.size(); ++i) { + block.addArgument(qubitType, odsState.location); + } + + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + YieldOp::create(odsBuilder, odsState.location, + bodyBuilder(block.getArguments())); +} + +LogicalResult PowOp::verify() { + FloatAttr attr; + if (!matchPattern(getExponent(), m_Constant(&attr))) { + return emitOpError("exponent must be a constant"); + } + + auto& block = *getBody(); + // The body may contain an arbitrary number of unitary (and classical) ops; + // only non-unitary quantum operations are disallowed. + if (llvm::any_of(block, [](Operation& op) { + return isa(op); + })) { + return emitOpError("body must not contain non-unitary quantum operations " + "or modify a quantum register"); + } + if (getNumBodyUnitaries() < 1) { + return emitOpError( + "body region must contain at least one unitary operation"); + } + const auto numTargets = getNumTargets(); + if (block.getArguments().size() != numTargets) { + return emitOpError( + "number of block arguments must match the number of targets"); + } + const auto qubitType = QubitType::get(getContext()); + for (size_t i = 0; i < numTargets; ++i) { + if (block.getArgument(i).getType() != qubitType) { + return emitOpError("block argument type at index ") + << i << " does not match target type"; + } + } + if (!isa(block.back())) { + return emitOpError( + "last operation in body region must be a yield operation"); + } + if (const auto numYieldOperands = block.back().getNumOperands(); + numYieldOperands != numTargets) { + return emitOpError("yield operation must yield ") + << numTargets << " values, but found " << numYieldOperands; + } + + SmallPtrSet uniqueQubitsIn; + for (const auto& target : getQubitsIn()) { + if (!uniqueQubitsIn.insert(target).second) { + return emitOpError("duplicate qubit found"); + } + } + + return success(); +} + +void PowOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +bool PowOp::hasCompileTimeKnownUnitaryMatrix() { + return all_of(getBody()->getOps(), + [](UnitaryOpInterface op) { + return op.hasCompileTimeKnownUnitaryMatrix(); + }); +} + +std::optional PowOp::getUnitaryMatrix() { + auto bodyUnitary = utils::getSoleBodyUnitary(*getBody()); + if (!bodyUnitary) { + return std::nullopt; + } + auto&& targetMatrix = bodyUnitary.getUnitaryMatrix(); + if (!targetMatrix) { + return std::nullopt; + } + + const double p = getExponentValue(); + + // U^1 = U (no computation needed) + if (std::abs(p - 1.0) < TOLERANCE) { + return targetMatrix; + } + + // U^0 = I + if (std::abs(p) < TOLERANCE) { + return DynamicMatrix::identity(targetMatrix->cols()); + } + + // General case requires eigendecomposition which is not supported yet. + return std::nullopt; +} diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index f25ce73b6f..6840cba4a6 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -144,6 +144,18 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qc::allocDeallocPair)})); /// @} +/// \name QCOToQC/Modifiers/PowOp.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCOPowOpTest, QCOToQCTest, + testing::Values(QCOToQCTestCase{"CtrlPowSx", + MQT_NAMED_BUILDER(qco::ctrlPowSx), + MQT_NAMED_BUILDER(qc::ctrlPowSx)}, + QCOToQCTestCase{"PowTwo", MQT_NAMED_BUILDER(qco::powTwo), + MQT_NAMED_BUILDER(qc::powTwo)})); + +/// @} + /// \name QCOToQC/Modifiers/CtrlOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 9e66a0b655..b505f46114 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -143,6 +143,17 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qco::allocSinkPair)})); /// @} +/// \name QCToQCO/Modifiers/PowOp.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCPowOpTest, QCToQCOTest, + testing::Values(QCToQCOTestCase{"CtrlPowSx", + MQT_NAMED_BUILDER(qc::ctrlPowSx), + MQT_NAMED_BUILDER(qco::ctrlPowSx)}, + QCToQCOTestCase{"PowTwo", MQT_NAMED_BUILDER(qc::powTwo), + MQT_NAMED_BUILDER(qco::powTwo)})); +/// @} + /// \name QCToQCO/Modifiers/CtrlOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 4d0f56912b..187827b670 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -11,6 +11,7 @@ #include "TestCaseUtils.h" #include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" #include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QC/IR/QCOps.h" #include "mlir/Support/IRVerification.h" #include "mlir/Support/Passes.h" #include "qc_programs.h" @@ -135,6 +136,102 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(ctrlTwo)})); /// @} +/// \name QC/Modifiers/PowOp.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCPowOpTest, QCTest, + testing::Values(QCTestCase{"Pow1Inline", MQT_NAMED_BUILDER(pow1Inline), + MQT_NAMED_BUILDER(rx)}, + QCTestCase{"Pow0Erase", MQT_NAMED_BUILDER(pow0Erase), + MQT_NAMED_BUILDER(emptyQC)}, + QCTestCase{"Pow0Two", MQT_NAMED_BUILDER(pow0Two), + MQT_NAMED_BUILDER(emptyQC)}, + QCTestCase{"NestedPow", MQT_NAMED_BUILDER(nestedPow), + MQT_NAMED_BUILDER(powSingleExponent)}, + QCTestCase{"NegPowRx", MQT_NAMED_BUILDER(negPowRx), + MQT_NAMED_BUILDER(powRxNeg)}, + QCTestCase{"InvPowRx", MQT_NAMED_BUILDER(invPowRx), + MQT_NAMED_BUILDER(powRxNeg)}, + QCTestCase{"PowCtrlRx", MQT_NAMED_BUILDER(powCtrlRx), + MQT_NAMED_BUILDER(ctrlPowRx)}, + QCTestCase{"NegPowInvIswap", + MQT_NAMED_BUILDER(negPowInvIswap), + MQT_NAMED_BUILDER(negPowInvIswapRef)}, + QCTestCase{"InvPowHFrac", MQT_NAMED_BUILDER(invPowHFrac), + MQT_NAMED_BUILDER(powHFracNeg)})); +/// @} + +/// pow(rxx) folds the exponent into the rotation angle: pow(2){rxx(θ)} => +/// rxx(2θ). Verify that PowOp is folded away by the cleanup pipeline. +TEST_F(QCTest, PowRxxFold) { + auto program = + QCProgramBuilder::build(context.get(), MQT_NAMED_BUILDER(powRxx).fn); + ASSERT_TRUE(program); + EXPECT_TRUE(verify(*program).succeeded()); + EXPECT_TRUE(runQCCleanupPipeline(program.get()).succeeded()); + EXPECT_TRUE(verify(*program).succeeded()); + + int powCount = 0; + program->walk([&](PowOp) { ++powCount; }); + EXPECT_EQ(powCount, 0) << "PowOp around rxx should be folded away"; +} + +/// Regression: pow(-0.5) { h } cannot fold a negative fractional exponent +/// into H (no angle to scale). Verify that PowOp survives. +TEST_F(QCTest, NegPowHNoFold) { + auto program = + QCProgramBuilder::build(context.get(), MQT_NAMED_BUILDER(negPowH).fn); + ASSERT_TRUE(program); + EXPECT_TRUE(verify(*program).succeeded()); + EXPECT_TRUE(runQCCleanupPipeline(program.get()).succeeded()); + EXPECT_TRUE(verify(*program).succeeded()); + + int powCount = 0; + program->walk([&](PowOp) { ++powCount; }); + EXPECT_EQ(powCount, 1) << "PowOp around h must survive the pipeline"; +} + +/// Regression: pow(sx) must not expand inside a ctrl modifier, because sx +/// lowers to gphase + rx (two ops), which is not allowed in a modifier body. +/// Verify that both CtrlOp and its nested PowOp survive. +TEST_F(QCTest, CtrlPowSxNoExpansion) { + auto program = + QCProgramBuilder::build(context.get(), MQT_NAMED_BUILDER(ctrlPowSx).fn); + ASSERT_TRUE(program); + EXPECT_TRUE(verify(*program).succeeded()); + EXPECT_TRUE(runQCCleanupPipeline(program.get()).succeeded()); + EXPECT_TRUE(verify(*program).succeeded()); + + int ctrlCount = 0; + int powCount = 0; + program->walk([&](CtrlOp) { ++ctrlCount; }); + program->walk([&](PowOp) { ++powCount; }); + EXPECT_EQ(ctrlCount, 1) << "CtrlOp must survive the pipeline"; + EXPECT_EQ(powCount, 1) << "PowOp inside ctrl must not be expanded"; +} + +/// A multi-unitary pow body (pow(2){x; rxx}) is now legal and the optimizer +/// leaves it untouched. Verify the pow and both body unitaries survive the +/// cleanup pipeline. (Round-trip coverage lives in the QC↔QCO suites via +/// powTwo.) +TEST_F(QCTest, PowTwoSurvives) { + auto program = + QCProgramBuilder::build(context.get(), MQT_NAMED_BUILDER(powTwo).fn); + ASSERT_TRUE(program); + EXPECT_TRUE(verify(*program).succeeded()); + EXPECT_TRUE(runQCCleanupPipeline(program.get()).succeeded()); + EXPECT_TRUE(verify(*program).succeeded()); + + int powCount = 0; + size_t bodyUnitaries = 0; + program->walk([&](PowOp op) { + ++powCount; + bodyUnitaries = op.getNumBodyUnitaries(); + }); + EXPECT_EQ(powCount, 1) << "multi-unitary PowOp must survive the pipeline"; + EXPECT_EQ(bodyUnitaries, 2U) << "both body unitaries must be preserved"; +} + /// \name QC/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( @@ -213,6 +310,8 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(barrier)}, QCTestCase{"InverseBarrier", MQT_NAMED_BUILDER(inverseBarrier), + MQT_NAMED_BUILDER(barrier)}, + QCTestCase{"PowBarrier", MQT_NAMED_BUILDER(powBarrier), MQT_NAMED_BUILDER(barrier)})); /// @} @@ -263,7 +362,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(ecr)}, QCTestCase{"InverseMultipleControlledECR", MQT_NAMED_BUILDER(inverseMultipleControlledEcr), - MQT_NAMED_BUILDER(multipleControlledEcr)})); + MQT_NAMED_BUILDER(multipleControlledEcr)}, + QCTestCase{"PowEvenECR", MQT_NAMED_BUILDER(powEvenEcr), + MQT_NAMED_BUILDER(emptyQC)}, + QCTestCase{"PowOddECR", MQT_NAMED_BUILDER(powOddEcr), + MQT_NAMED_BUILDER(ecr)})); /// @} /// \name QC/Operations/StandardGates/GphaseOp.cpp @@ -289,7 +392,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(globalPhase)}, QCTestCase{"InverseMultipleControlledGlobalPhase", MQT_NAMED_BUILDER(inverseMultipleControlledGlobalPhase), - MQT_NAMED_BUILDER(multipleControlledP)})); + MQT_NAMED_BUILDER(multipleControlledP)}, + QCTestCase{"PowGphaseScaled", MQT_NAMED_BUILDER(powGphaseScaled), + MQT_NAMED_BUILDER(powGphaseScaledRef)}, + QCTestCase{"NegPowGphase", MQT_NAMED_BUILDER(negPowGphase), + MQT_NAMED_BUILDER(negPowGphaseRef)})); /// @} /// \name QC/Operations/StandardGates/HOp.cpp @@ -311,7 +418,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(h)}, QCTestCase{"InverseMultipleControlledH", MQT_NAMED_BUILDER(inverseMultipleControlledH), - MQT_NAMED_BUILDER(multipleControlledH)})); + MQT_NAMED_BUILDER(multipleControlledH)}, + QCTestCase{"PowEvenH", MQT_NAMED_BUILDER(powEvenH), + MQT_NAMED_BUILDER(emptyQC)}, + QCTestCase{"PowOddH", MQT_NAMED_BUILDER(powOddH), + MQT_NAMED_BUILDER(h)})); /// @} /// \name QC/Operations/StandardGates/IdOp.cpp @@ -337,6 +448,8 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(identity)}, QCTestCase{"InverseMultipleControlledIdentity", MQT_NAMED_BUILDER(inverseMultipleControlledIdentity), + MQT_NAMED_BUILDER(identity)}, + QCTestCase{"PowId", MQT_NAMED_BUILDER(powId), MQT_NAMED_BUILDER(identity)})); /// @} @@ -362,7 +475,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseIswap)}, QCTestCase{"InverseMultipleControllediSWAP", MQT_NAMED_BUILDER(inverseMultipleControlledIswap), - MQT_NAMED_BUILDER(inverseMultipleControlledIswap)})); + MQT_NAMED_BUILDER(inverseMultipleControlledIswap)}, + QCTestCase{"PowHalfiSWAP", MQT_NAMED_BUILDER(powHalfIswap), + MQT_NAMED_BUILDER(powHalfIswapRef)})); /// @} /// \name QC/Operations/StandardGates/POp.cpp @@ -406,7 +521,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(r)}, QCTestCase{"InverseMultipleControlledR", MQT_NAMED_BUILDER(inverseMultipleControlledR), - MQT_NAMED_BUILDER(multipleControlledR)})); + MQT_NAMED_BUILDER(multipleControlledR)}, + QCTestCase{"PowRScaled", MQT_NAMED_BUILDER(powRScaled), + MQT_NAMED_BUILDER(powRScaledRef)})); /// @} /// \name QC/Operations/StandardGates/RxOp.cpp @@ -429,7 +546,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(rx)}, QCTestCase{"InverseMultipleControlledRX", MQT_NAMED_BUILDER(inverseMultipleControlledRx), - MQT_NAMED_BUILDER(multipleControlledRx)})); + MQT_NAMED_BUILDER(multipleControlledRx)}, + QCTestCase{"PowRxScaled", MQT_NAMED_BUILDER(powRxScaled), + MQT_NAMED_BUILDER(rxScaled)})); /// @} /// \name QC/Operations/StandardGates/RxxOp.cpp @@ -597,7 +716,14 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(sdg)}, QCTestCase{"InverseMultipleControlledS", MQT_NAMED_BUILDER(inverseMultipleControlledS), - MQT_NAMED_BUILDER(multipleControlledSdg)})); + MQT_NAMED_BUILDER(multipleControlledSdg)}, + QCTestCase{"PowTwoS", MQT_NAMED_BUILDER(powTwoS), MQT_NAMED_BUILDER(z)}, + QCTestCase{"PowFourSErase", MQT_NAMED_BUILDER(powFourS), + MQT_NAMED_BUILDER(emptyQC)}, + QCTestCase{"PowHalfSToT", MQT_NAMED_BUILDER(powHalfS), + MQT_NAMED_BUILDER(t_)}, + QCTestCase{"PowThirdSToP", MQT_NAMED_BUILDER(powThirdS), + MQT_NAMED_BUILDER(powThirdSRef)})); /// @} /// \name QC/Operations/StandardGates/SdgOp.cpp @@ -622,7 +748,13 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(s)}, QCTestCase{"InverseMultipleControlledSdg", MQT_NAMED_BUILDER(inverseMultipleControlledSdg), - MQT_NAMED_BUILDER(multipleControlledS)})); + MQT_NAMED_BUILDER(multipleControlledS)}, + QCTestCase{"PowTwoSdg", MQT_NAMED_BUILDER(powTwoSdg), + MQT_NAMED_BUILDER(z)}, + QCTestCase{"PowHalfSdgToTdg", MQT_NAMED_BUILDER(powHalfSdg), + MQT_NAMED_BUILDER(tdg)}, + QCTestCase{"PowThirdSdgToP", MQT_NAMED_BUILDER(powThirdSdg), + MQT_NAMED_BUILDER(powThirdSdgRef)})); /// @} /// \name QC/Operations/StandardGates/SwapOp.cpp @@ -647,7 +779,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(swap)}, QCTestCase{"InverseMultipleControlledSWAP", MQT_NAMED_BUILDER(inverseMultipleControlledSwap), - MQT_NAMED_BUILDER(multipleControlledSwap)})); + MQT_NAMED_BUILDER(multipleControlledSwap)}, + QCTestCase{"PowEvenSWAP", MQT_NAMED_BUILDER(powEvenSwap), + MQT_NAMED_BUILDER(emptyQC)}, + QCTestCase{"PowOddSWAP", MQT_NAMED_BUILDER(powOddSwap), + MQT_NAMED_BUILDER(swap)})); /// @} /// \name QC/Operations/StandardGates/SxOp.cpp @@ -670,32 +806,40 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(sxdg)}, QCTestCase{"InverseMultipleControlledSX", MQT_NAMED_BUILDER(inverseMultipleControlledSx), - MQT_NAMED_BUILDER(multipleControlledSxdg)})); + MQT_NAMED_BUILDER(multipleControlledSxdg)}, + QCTestCase{"PowTwoSX", MQT_NAMED_BUILDER(powTwoSx), + MQT_NAMED_BUILDER(powTwoSxRef)}, + QCTestCase{"PowThirdSxGeneral", MQT_NAMED_BUILDER(powThirdSx), + MQT_NAMED_BUILDER(powThirdSxRef)})); /// @} /// \name QC/Operations/StandardGates/SxdgOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QCSXdgOpTest, QCTest, - testing::Values(QCTestCase{"SXdg", MQT_NAMED_BUILDER(sxdg), - MQT_NAMED_BUILDER(sxdg)}, - QCTestCase{"SingleControlledSXdg", - MQT_NAMED_BUILDER(singleControlledSxdg), - MQT_NAMED_BUILDER(singleControlledSxdg)}, - QCTestCase{"MultipleControlledSXdg", - MQT_NAMED_BUILDER(multipleControlledSxdg), - MQT_NAMED_BUILDER(multipleControlledSxdg)}, - QCTestCase{"NestedControlledSXdg", - MQT_NAMED_BUILDER(nestedControlledSxdg), - MQT_NAMED_BUILDER(multipleControlledSxdg)}, - QCTestCase{"TrivialControlledSXdg", - MQT_NAMED_BUILDER(trivialControlledSxdg), - MQT_NAMED_BUILDER(sxdg)}, - QCTestCase{"InverseSXdg", MQT_NAMED_BUILDER(inverseSxdg), - MQT_NAMED_BUILDER(sx)}, - QCTestCase{"InverseMultipleControlledSXdg", - MQT_NAMED_BUILDER(inverseMultipleControlledSxdg), - MQT_NAMED_BUILDER(multipleControlledSx)})); + testing::Values( + QCTestCase{"SXdg", MQT_NAMED_BUILDER(sxdg), MQT_NAMED_BUILDER(sxdg)}, + QCTestCase{"SingleControlledSXdg", + MQT_NAMED_BUILDER(singleControlledSxdg), + MQT_NAMED_BUILDER(singleControlledSxdg)}, + QCTestCase{"MultipleControlledSXdg", + MQT_NAMED_BUILDER(multipleControlledSxdg), + MQT_NAMED_BUILDER(multipleControlledSxdg)}, + QCTestCase{"NestedControlledSXdg", + MQT_NAMED_BUILDER(nestedControlledSxdg), + MQT_NAMED_BUILDER(multipleControlledSxdg)}, + QCTestCase{"TrivialControlledSXdg", + MQT_NAMED_BUILDER(trivialControlledSxdg), + MQT_NAMED_BUILDER(sxdg)}, + QCTestCase{"InverseSXdg", MQT_NAMED_BUILDER(inverseSxdg), + MQT_NAMED_BUILDER(sx)}, + QCTestCase{"InverseMultipleControlledSXdg", + MQT_NAMED_BUILDER(inverseMultipleControlledSxdg), + MQT_NAMED_BUILDER(multipleControlledSx)}, + QCTestCase{"PowTwoSXdg", MQT_NAMED_BUILDER(powTwoSxdg), + MQT_NAMED_BUILDER(powTwoSxdgRef)}, + QCTestCase{"PowThirdSxdgGeneral", MQT_NAMED_BUILDER(powThirdSxdg), + MQT_NAMED_BUILDER(powThirdSxdgRef)})); /// @} /// \name QC/Operations/StandardGates/TOp.cpp @@ -717,7 +861,10 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(tdg)}, QCTestCase{"InverseMultipleControlledT", MQT_NAMED_BUILDER(inverseMultipleControlledT), - MQT_NAMED_BUILDER(multipleControlledTdg)})); + MQT_NAMED_BUILDER(multipleControlledTdg)}, + QCTestCase{"PowTwoT", MQT_NAMED_BUILDER(powTwoT), MQT_NAMED_BUILDER(s)}, + QCTestCase{"PowThirdTToP", MQT_NAMED_BUILDER(powThirdT), + MQT_NAMED_BUILDER(powThirdTRef)})); /// @} /// \name QC/Operations/StandardGates/TdgOp.cpp @@ -742,7 +889,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(t_)}, QCTestCase{"InverseMultipleControlledTdg", MQT_NAMED_BUILDER(inverseMultipleControlledTdg), - MQT_NAMED_BUILDER(multipleControlledT)})); + MQT_NAMED_BUILDER(multipleControlledT)}, + QCTestCase{"PowTwoTdg", MQT_NAMED_BUILDER(powTwoTdg), + MQT_NAMED_BUILDER(sdg)}, + QCTestCase{"PowThirdTdgToP", MQT_NAMED_BUILDER(powThirdTdg), + MQT_NAMED_BUILDER(powThirdTdgRef)})); /// @} /// \name QC/Operations/StandardGates/U2Op.cpp @@ -809,7 +960,13 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(x)}, QCTestCase{"InverseMultipleControlledX", MQT_NAMED_BUILDER(inverseMultipleControlledX), - MQT_NAMED_BUILDER(multipleControlledX)})); + MQT_NAMED_BUILDER(multipleControlledX)}, + QCTestCase{"PowHalfX", MQT_NAMED_BUILDER(powHalfX), + MQT_NAMED_BUILDER(powHalfXRef)}, + QCTestCase{"PowNegHalfXToSXdg", MQT_NAMED_BUILDER(powNegHalfX), + MQT_NAMED_BUILDER(sxdg)}, + QCTestCase{"PowThirdXGeneral", MQT_NAMED_BUILDER(powThirdX), + MQT_NAMED_BUILDER(powThirdXRef)})); /// @} /// \name QC/Operations/StandardGates/XxMinusYyOp.cpp @@ -835,7 +992,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(xxMinusYY)}, QCTestCase{"InverseMultipleControlledXXMinusYY", MQT_NAMED_BUILDER(inverseMultipleControlledXxMinusYY), - MQT_NAMED_BUILDER(multipleControlledXxMinusYY)})); + MQT_NAMED_BUILDER(multipleControlledXxMinusYY)}, + QCTestCase{"PowXxMinusYYScaled", MQT_NAMED_BUILDER(powXxMinusYYScaled), + MQT_NAMED_BUILDER(powXxMinusYYScaledRef)})); /// @} /// \name QC/Operations/StandardGates/XxPlusYyOp.cpp @@ -861,7 +1020,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(xxPlusYY)}, QCTestCase{"InverseMultipleControlledXXPlusYY", MQT_NAMED_BUILDER(inverseMultipleControlledXxPlusYY), - MQT_NAMED_BUILDER(multipleControlledXxPlusYY)})); + MQT_NAMED_BUILDER(multipleControlledXxPlusYY)}, + QCTestCase{"PowXxPlusYYScaled", MQT_NAMED_BUILDER(powXxPlusYYScaled), + MQT_NAMED_BUILDER(powXxPlusYYScaledRef)})); /// @} /// \name QC/Operations/StandardGates/YOp.cpp @@ -883,7 +1044,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(y)}, QCTestCase{"InverseMultipleControlledY", MQT_NAMED_BUILDER(inverseMultipleControlledY), - MQT_NAMED_BUILDER(multipleControlledY)})); + MQT_NAMED_BUILDER(multipleControlledY)}, + QCTestCase{"PowHalfY", MQT_NAMED_BUILDER(powHalfY), + MQT_NAMED_BUILDER(powHalfYRef)})); /// @} /// \name QC/Operations/StandardGates/ZOp.cpp @@ -905,7 +1068,13 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(z)}, QCTestCase{"InverseMultipleControlledZ", MQT_NAMED_BUILDER(inverseMultipleControlledZ), - MQT_NAMED_BUILDER(multipleControlledZ)})); + MQT_NAMED_BUILDER(multipleControlledZ)}, + QCTestCase{"PowHalfZ", MQT_NAMED_BUILDER(powHalfZ), + MQT_NAMED_BUILDER(s)}, + QCTestCase{"NormalizeAngleWrapZ", MQT_NAMED_BUILDER(powThreeHalvesZ), + MQT_NAMED_BUILDER(sdg)}, + QCTestCase{"PowThirdZToP", MQT_NAMED_BUILDER(powThirdZ), + MQT_NAMED_BUILDER(powThirdZRef)})); /// @} /// \name QC/QubitManagement/QubitManagement.cpp diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index 26fca1733f..c3cc606269 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -257,6 +257,78 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(ctrlInvTwo)})); /// @} +/// \name QCO/Modifiers/PowOp.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCOPowOpTest, QCOTest, + testing::Values(QCOTestCase{"Pow1Inline", MQT_NAMED_BUILDER(pow1Inline), + MQT_NAMED_BUILDER(rx)}, + QCOTestCase{"Pow0Erase", MQT_NAMED_BUILDER(pow0Erase), + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"NestedPow", MQT_NAMED_BUILDER(nestedPow), + MQT_NAMED_BUILDER(powSingleExponent)}, + QCOTestCase{"NegPowRx", MQT_NAMED_BUILDER(negPowRx), + MQT_NAMED_BUILDER(powRxNeg)}, + QCOTestCase{"InvPowRx", MQT_NAMED_BUILDER(invPowRx), + MQT_NAMED_BUILDER(powRxNeg)}, + QCOTestCase{"PowCtrlRx", MQT_NAMED_BUILDER(powCtrlRx), + MQT_NAMED_BUILDER(ctrlPowRx)}, + QCOTestCase{"NegPowInvIswap", + MQT_NAMED_BUILDER(negPowInvIswap), + MQT_NAMED_BUILDER(negPowInvIswapRef)}, + QCOTestCase{"InvPowHFrac", MQT_NAMED_BUILDER(invPowHFrac), + MQT_NAMED_BUILDER(powHFracNeg)})); +/// @} + +/// pow(rxx) folds the exponent into the rotation angle: pow(2){rxx(θ)} => +/// rxx(2θ). Verify that PowOp is folded away by the cleanup pipeline. +TEST_F(QCOTest, PowRxxFold) { + auto program = + QCOProgramBuilder::build(context.get(), MQT_NAMED_BUILDER(powRxx).fn); + ASSERT_TRUE(program); + EXPECT_TRUE(verify(*program).succeeded()); + EXPECT_TRUE(runQCOCleanupPipeline(program.get()).succeeded()); + EXPECT_TRUE(verify(*program).succeeded()); + + int powCount = 0; + program->walk([&](PowOp) { ++powCount; }); + EXPECT_EQ(powCount, 0) << "PowOp around rxx should be folded away"; +} + +/// Regression: pow(-0.5) { h } cannot fold a negative fractional exponent +/// into H (no angle to scale). Verify that PowOp survives. +TEST_F(QCOTest, NegPowHNoFold) { + auto program = + QCOProgramBuilder::build(context.get(), MQT_NAMED_BUILDER(negPowH).fn); + ASSERT_TRUE(program); + EXPECT_TRUE(verify(*program).succeeded()); + EXPECT_TRUE(runQCOCleanupPipeline(program.get()).succeeded()); + EXPECT_TRUE(verify(*program).succeeded()); + + int powCount = 0; + program->walk([&](PowOp) { ++powCount; }); + EXPECT_EQ(powCount, 1) << "PowOp around h must survive the pipeline"; +} + +/// Regression: pow(sx) must not expand inside a ctrl modifier, because sx +/// lowers to gphase + rx (two ops), which is not allowed in a modifier body. +/// Verify that both CtrlOp and its nested PowOp survive. +TEST_F(QCOTest, CtrlPowSxNoExpansion) { + auto program = + QCOProgramBuilder::build(context.get(), MQT_NAMED_BUILDER(ctrlPowSx).fn); + ASSERT_TRUE(program); + EXPECT_TRUE(verify(*program).succeeded()); + EXPECT_TRUE(runQCOCleanupPipeline(program.get()).succeeded()); + EXPECT_TRUE(verify(*program).succeeded()); + + int ctrlCount = 0; + int powCount = 0; + program->walk([&](CtrlOp) { ++ctrlCount; }); + program->walk([&](PowOp) { ++powCount; }); + EXPECT_EQ(ctrlCount, 1) << "CtrlOp must survive the pipeline"; + EXPECT_EQ(powCount, 1) << "PowOp inside ctrl must not be expanded"; +} + /// \name QCO/Operations/StandardGates/BarrierOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( @@ -276,7 +348,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseBarrier), MQT_NAMED_BUILDER(barrier)}, QCOTestCase{"TwoBarrier", MQT_NAMED_BUILDER(twoBarrier), - MQT_NAMED_BUILDER(barrierTwoQubits)})); + MQT_NAMED_BUILDER(barrierTwoQubits)}, + QCOTestCase{"PowBarrier", MQT_NAMED_BUILDER(powBarrier), + MQT_NAMED_BUILDER(barrier)})); /// @} /// \name QCO/Operations/StandardGates/DcxOp.cpp @@ -333,7 +407,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledEcr), MQT_NAMED_BUILDER(multipleControlledEcr)}, QCOTestCase{"TwoECR", MQT_NAMED_BUILDER(twoEcr), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowEvenECR", MQT_NAMED_BUILDER(powEvenEcr), + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowOddECR", MQT_NAMED_BUILDER(powOddEcr), + MQT_NAMED_BUILDER(ecr)})); /// @} /// \name QCO/Operations/StandardGates/GphaseOp.cpp @@ -353,7 +431,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(globalPhase)}, QCOTestCase{"InverseMultipleControlledGlobalPhase", MQT_NAMED_BUILDER(inverseMultipleControlledGlobalPhase), - MQT_NAMED_BUILDER(multipleControlledGlobalPhase)})); + MQT_NAMED_BUILDER(multipleControlledGlobalPhase)}, + QCOTestCase{"PowGphaseScaled", MQT_NAMED_BUILDER(powGphaseScaled), + MQT_NAMED_BUILDER(powGphaseScaledRef)}, + QCOTestCase{"NegPowGphase", MQT_NAMED_BUILDER(negPowGphase), + MQT_NAMED_BUILDER(negPowGphaseRef)})); /// @} /// \name QCO/Operations/StandardGates/HOp.cpp @@ -377,7 +459,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledH), MQT_NAMED_BUILDER(multipleControlledH)}, QCOTestCase{"TwoH", MQT_NAMED_BUILDER(twoH), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowEvenH", MQT_NAMED_BUILDER(powEvenH), + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowOddH", MQT_NAMED_BUILDER(powOddH), + MQT_NAMED_BUILDER(h)})); /// @} /// \name QCO/Operations/StandardGates/IdOp.cpp @@ -403,7 +489,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"InverseMultipleControlledIdentity", MQT_NAMED_BUILDER(inverseMultipleControlledIdentity), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowId", MQT_NAMED_BUILDER(powId), + MQT_NAMED_BUILDER(identity)})); /// @} /// \name QCO/Operations/StandardGates/IswapOp.cpp @@ -429,7 +517,9 @@ INSTANTIATE_TEST_SUITE_P( QCOTestCase{ "InverseMultipleControllediSWAP", MQT_NAMED_BUILDER(inverseMultipleControlledIswap), - MQT_NAMED_BUILDER(inverseMultipleControlledIswap)})); + MQT_NAMED_BUILDER(inverseMultipleControlledIswap)}, + QCOTestCase{"PowHalfiSWAP", MQT_NAMED_BUILDER(powHalfIswap), + MQT_NAMED_BUILDER(powHalfIswapRef)})); /// @} /// \name QCO/Operations/StandardGates/POp.cpp @@ -480,7 +570,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(rx)}, QCOTestCase{"CanonicalizeRToRy", MQT_NAMED_BUILDER(canonicalizeRToRy), MQT_NAMED_BUILDER(ry)}, - QCOTestCase{"TwoR", MQT_NAMED_BUILDER(twoR), MQT_NAMED_BUILDER(r)})); + QCOTestCase{"TwoR", MQT_NAMED_BUILDER(twoR), MQT_NAMED_BUILDER(r)}, + QCOTestCase{"PowRScaled", MQT_NAMED_BUILDER(powRScaled), + MQT_NAMED_BUILDER(powRScaledRef)})); /// @} /// \name QCO/Operations/StandardGates/RxOp.cpp @@ -505,7 +597,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledRx), MQT_NAMED_BUILDER(multipleControlledRx)}, QCOTestCase{"TwoRXOppositePhase", MQT_NAMED_BUILDER(twoRxOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowRxScaled", MQT_NAMED_BUILDER(powRxScaled), + MQT_NAMED_BUILDER(rxScaled)})); /// @} /// \name QCO/Operations/StandardGates/RxxOp.cpp @@ -716,36 +810,49 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(multipleControlledSdg)}, QCOTestCase{"SThenSdg", MQT_NAMED_BUILDER(sThenSdg), MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"TwoS", MQT_NAMED_BUILDER(twoS), MQT_NAMED_BUILDER(z)})); + QCOTestCase{"TwoS", MQT_NAMED_BUILDER(twoS), MQT_NAMED_BUILDER(z)}, + QCOTestCase{"PowTwoS", MQT_NAMED_BUILDER(powTwoS), + MQT_NAMED_BUILDER(z)}, + QCOTestCase{"PowFourSErase", MQT_NAMED_BUILDER(powFourS), + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowHalfSToT", MQT_NAMED_BUILDER(powHalfS), + MQT_NAMED_BUILDER(t_)}, + QCOTestCase{"PowThirdSToP", MQT_NAMED_BUILDER(powThirdS), + MQT_NAMED_BUILDER(powThirdSRef)})); /// @} /// \name QCO/Operations/StandardGates/SdgOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QCOSdgOpTest, QCOTest, - testing::Values(QCOTestCase{"Sdg", MQT_NAMED_BUILDER(sdg), - MQT_NAMED_BUILDER(sdg)}, - QCOTestCase{"SingleControlledSdg", - MQT_NAMED_BUILDER(singleControlledSdg), - MQT_NAMED_BUILDER(singleControlledSdg)}, - QCOTestCase{"MultipleControlledSdg", - MQT_NAMED_BUILDER(multipleControlledSdg), - MQT_NAMED_BUILDER(multipleControlledSdg)}, - QCOTestCase{"NestedControlledSdg", - MQT_NAMED_BUILDER(nestedControlledSdg), - MQT_NAMED_BUILDER(multipleControlledSdg)}, - QCOTestCase{"TrivialControlledSdg", - MQT_NAMED_BUILDER(trivialControlledSdg), - MQT_NAMED_BUILDER(sdg)}, - QCOTestCase{"InverseSdg", MQT_NAMED_BUILDER(inverseSdg), - MQT_NAMED_BUILDER(s)}, - QCOTestCase{"InverseMultipleControlledSdg", - MQT_NAMED_BUILDER(inverseMultipleControlledSdg), - MQT_NAMED_BUILDER(multipleControlledS)}, - QCOTestCase{"SdgThenS", MQT_NAMED_BUILDER(sdgThenS), - MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"TwoSdg", MQT_NAMED_BUILDER(twoSdg), - MQT_NAMED_BUILDER(z)})); + testing::Values( + QCOTestCase{"Sdg", MQT_NAMED_BUILDER(sdg), MQT_NAMED_BUILDER(sdg)}, + QCOTestCase{"SingleControlledSdg", + MQT_NAMED_BUILDER(singleControlledSdg), + MQT_NAMED_BUILDER(singleControlledSdg)}, + QCOTestCase{"MultipleControlledSdg", + MQT_NAMED_BUILDER(multipleControlledSdg), + MQT_NAMED_BUILDER(multipleControlledSdg)}, + QCOTestCase{"NestedControlledSdg", + MQT_NAMED_BUILDER(nestedControlledSdg), + MQT_NAMED_BUILDER(multipleControlledSdg)}, + QCOTestCase{"TrivialControlledSdg", + MQT_NAMED_BUILDER(trivialControlledSdg), + MQT_NAMED_BUILDER(sdg)}, + QCOTestCase{"InverseSdg", MQT_NAMED_BUILDER(inverseSdg), + MQT_NAMED_BUILDER(s)}, + QCOTestCase{"InverseMultipleControlledSdg", + MQT_NAMED_BUILDER(inverseMultipleControlledSdg), + MQT_NAMED_BUILDER(multipleControlledS)}, + QCOTestCase{"SdgThenS", MQT_NAMED_BUILDER(sdgThenS), + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"TwoSdg", MQT_NAMED_BUILDER(twoSdg), MQT_NAMED_BUILDER(z)}, + QCOTestCase{"PowTwoSdg", MQT_NAMED_BUILDER(powTwoSdg), + MQT_NAMED_BUILDER(z)}, + QCOTestCase{"PowHalfSdgToTdg", MQT_NAMED_BUILDER(powHalfSdg), + MQT_NAMED_BUILDER(tdg)}, + QCOTestCase{"PowThirdSdgToP", MQT_NAMED_BUILDER(powThirdSdg), + MQT_NAMED_BUILDER(powThirdSdgRef)})); /// @} /// \name QCO/Operations/StandardGates/SwapOp.cpp @@ -775,7 +882,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"TwoSWAPSwappedTargets", MQT_NAMED_BUILDER(twoSwapSwappedTargets), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowEvenSWAP", MQT_NAMED_BUILDER(powEvenSwap), + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowOddSWAP", MQT_NAMED_BUILDER(powOddSwap), + MQT_NAMED_BUILDER(swap)})); /// @} /// \name QCO/Operations/StandardGates/SxOp.cpp @@ -801,7 +912,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(multipleControlledSxdg)}, QCOTestCase{"SXThenSXdg", MQT_NAMED_BUILDER(sxThenSxdg), MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"TwoSX", MQT_NAMED_BUILDER(twoSx), MQT_NAMED_BUILDER(x)})); + QCOTestCase{"TwoSX", MQT_NAMED_BUILDER(twoSx), MQT_NAMED_BUILDER(x)}, + QCOTestCase{"PowTwoSX", MQT_NAMED_BUILDER(powTwoSx), + MQT_NAMED_BUILDER(powTwoSxRef)}, + QCOTestCase{"PowThirdSxGeneral", MQT_NAMED_BUILDER(powThirdSx), + MQT_NAMED_BUILDER(powThirdSxRef)})); /// @} /// \name QCO/Operations/StandardGates/SxdgOp.cpp @@ -830,7 +945,11 @@ INSTANTIATE_TEST_SUITE_P( QCOTestCase{"SXdgThenSX", MQT_NAMED_BUILDER(sxdgThenSx), MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"TwoSXdg", MQT_NAMED_BUILDER(twoSxdg), - MQT_NAMED_BUILDER(x)})); + MQT_NAMED_BUILDER(x)}, + QCOTestCase{"PowTwoSXdg", MQT_NAMED_BUILDER(powTwoSxdg), + MQT_NAMED_BUILDER(powTwoSxdgRef)}, + QCOTestCase{"PowThirdSxdgGeneral", MQT_NAMED_BUILDER(powThirdSxdg), + MQT_NAMED_BUILDER(powThirdSxdgRef)})); /// @} /// \name QCO/Operations/StandardGates/TOp.cpp @@ -855,36 +974,44 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(multipleControlledTdg)}, QCOTestCase{"TThenTdg", MQT_NAMED_BUILDER(tThenTdg), MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"TwoT", MQT_NAMED_BUILDER(twoT), MQT_NAMED_BUILDER(s)})); + QCOTestCase{"TwoT", MQT_NAMED_BUILDER(twoT), MQT_NAMED_BUILDER(s)}, + QCOTestCase{"PowTwoT", MQT_NAMED_BUILDER(powTwoT), + MQT_NAMED_BUILDER(s)}, + QCOTestCase{"PowThirdTToP", MQT_NAMED_BUILDER(powThirdT), + MQT_NAMED_BUILDER(powThirdTRef)})); /// @} /// \name QCO/Operations/StandardGates/TdgOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QCOTdgOpTest, QCOTest, - testing::Values(QCOTestCase{"Tdg", MQT_NAMED_BUILDER(tdg), - MQT_NAMED_BUILDER(tdg)}, - QCOTestCase{"SingleControlledTdg", - MQT_NAMED_BUILDER(singleControlledTdg), - MQT_NAMED_BUILDER(singleControlledTdg)}, - QCOTestCase{"MultipleControlledTdg", - MQT_NAMED_BUILDER(multipleControlledTdg), - MQT_NAMED_BUILDER(multipleControlledTdg)}, - QCOTestCase{"NestedControlledTdg", - MQT_NAMED_BUILDER(nestedControlledTdg), - MQT_NAMED_BUILDER(multipleControlledTdg)}, - QCOTestCase{"TrivialControlledTdg", - MQT_NAMED_BUILDER(trivialControlledTdg), - MQT_NAMED_BUILDER(tdg)}, - QCOTestCase{"InverseTdg", MQT_NAMED_BUILDER(inverseTdg), - MQT_NAMED_BUILDER(t_)}, - QCOTestCase{"InverseMultipleControlledTdg", - MQT_NAMED_BUILDER(inverseMultipleControlledTdg), - MQT_NAMED_BUILDER(multipleControlledT)}, - QCOTestCase{"TdgThenS", MQT_NAMED_BUILDER(tdgThenT), - MQT_NAMED_BUILDER(emptyQCO)}, - QCOTestCase{"TwoTdg", MQT_NAMED_BUILDER(twoTdg), - MQT_NAMED_BUILDER(sdg)})); + testing::Values( + QCOTestCase{"Tdg", MQT_NAMED_BUILDER(tdg), MQT_NAMED_BUILDER(tdg)}, + QCOTestCase{"SingleControlledTdg", + MQT_NAMED_BUILDER(singleControlledTdg), + MQT_NAMED_BUILDER(singleControlledTdg)}, + QCOTestCase{"MultipleControlledTdg", + MQT_NAMED_BUILDER(multipleControlledTdg), + MQT_NAMED_BUILDER(multipleControlledTdg)}, + QCOTestCase{"NestedControlledTdg", + MQT_NAMED_BUILDER(nestedControlledTdg), + MQT_NAMED_BUILDER(multipleControlledTdg)}, + QCOTestCase{"TrivialControlledTdg", + MQT_NAMED_BUILDER(trivialControlledTdg), + MQT_NAMED_BUILDER(tdg)}, + QCOTestCase{"InverseTdg", MQT_NAMED_BUILDER(inverseTdg), + MQT_NAMED_BUILDER(t_)}, + QCOTestCase{"InverseMultipleControlledTdg", + MQT_NAMED_BUILDER(inverseMultipleControlledTdg), + MQT_NAMED_BUILDER(multipleControlledT)}, + QCOTestCase{"TdgThenT", MQT_NAMED_BUILDER(tdgThenT), + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"TwoTdg", MQT_NAMED_BUILDER(twoTdg), + MQT_NAMED_BUILDER(sdg)}, + QCOTestCase{"PowTwoTdg", MQT_NAMED_BUILDER(powTwoTdg), + MQT_NAMED_BUILDER(sdg)}, + QCOTestCase{"PowThirdTdgToP", MQT_NAMED_BUILDER(powThirdTdg), + MQT_NAMED_BUILDER(powThirdTdgRef)})); /// @} /// \name QCO/Operations/StandardGates/U2Op.cpp @@ -971,7 +1098,13 @@ INSTANTIATE_TEST_SUITE_P( QCOTestCase{"ControlledTwoX", MQT_NAMED_BUILDER(controlledTwoX), MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"InverseTwoX", MQT_NAMED_BUILDER(inverseTwoX), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowHalfX", MQT_NAMED_BUILDER(powHalfX), + MQT_NAMED_BUILDER(powHalfXRef)}, + QCOTestCase{"PowNegHalfXToSXdg", MQT_NAMED_BUILDER(powNegHalfX), + MQT_NAMED_BUILDER(sxdg)}, + QCOTestCase{"PowThirdXGeneral", MQT_NAMED_BUILDER(powThirdX), + MQT_NAMED_BUILDER(powThirdXRef)})); /// @} /// \name QCO/Operations/StandardGates/XxMinusYyOp.cpp @@ -1003,7 +1136,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"TwoXXMinusYYSwappedTargets", MQT_NAMED_BUILDER(twoXxMinusYYSwappedTargets), - MQT_NAMED_BUILDER(xxMinusYY)})); + MQT_NAMED_BUILDER(xxMinusYY)}, + QCOTestCase{"PowXxMinusYYScaled", MQT_NAMED_BUILDER(powXxMinusYYScaled), + MQT_NAMED_BUILDER(powXxMinusYYScaledRef)})); /// @} /// \name QCO/Operations/StandardGates/XxPlusYyOp.cpp @@ -1035,7 +1170,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(emptyQCO)}, QCOTestCase{"TwoXXPlusYYSwappedTargets", MQT_NAMED_BUILDER(twoXxPlusYYSwappedTargets), - MQT_NAMED_BUILDER(xxPlusYY)})); + MQT_NAMED_BUILDER(xxPlusYY)}, + QCOTestCase{"PowXxPlusYYScaled", MQT_NAMED_BUILDER(powXxPlusYYScaled), + MQT_NAMED_BUILDER(powXxPlusYYScaledRef)})); /// @} /// \name QCO/Operations/StandardGates/YOp.cpp @@ -1059,7 +1196,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledY), MQT_NAMED_BUILDER(multipleControlledY)}, QCOTestCase{"TwoY", MQT_NAMED_BUILDER(twoY), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowHalfY", MQT_NAMED_BUILDER(powHalfY), + MQT_NAMED_BUILDER(powHalfYRef)})); /// @} /// \name QCO/Operations/StandardGates/ZOp.cpp @@ -1083,7 +1222,13 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledZ), MQT_NAMED_BUILDER(multipleControlledZ)}, QCOTestCase{"TwoZ", MQT_NAMED_BUILDER(twoZ), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowHalfZ", MQT_NAMED_BUILDER(powHalfZ), + MQT_NAMED_BUILDER(s)}, + QCOTestCase{"NormalizeAngleWrapZ", MQT_NAMED_BUILDER(powThreeHalvesZ), + MQT_NAMED_BUILDER(sdg)}, + QCOTestCase{"PowThirdZToP", MQT_NAMED_BUILDER(powThirdZ), + MQT_NAMED_BUILDER(powThirdZRef)})); /// @} /// \name QCO/Operations/MeasureOp.cpp diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp index e196b6c1b8..3a4043a4a2 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp @@ -95,6 +95,46 @@ TEST_F(QCOMatrixTest, CXOpMatrix) { } /// @} +/// \name QCO/Modifiers/PowOp.cpp +/// @{ +TEST_F(QCOMatrixTest, PowRxxOpMatrix) { + auto moduleOp = QCOProgramBuilder::build(context.get(), powRxx); + ASSERT_TRUE(moduleOp); + + // Get the PowOp from the module + auto funcOp = *moduleOp->getBody()->getOps().begin(); + auto powOp = *funcOp.getBody().getOps().begin(); + auto matrix = powOp.getUnitaryMatrix(); + + // RXX(0.123)^2 requires eigendecomposition (not yet supported). + ASSERT_FALSE(matrix.has_value()); +} + +TEST_F(QCOMatrixTest, PowHalfXOpMatrix) { + auto moduleOp = QCOProgramBuilder::build(context.get(), powHalfX); + ASSERT_TRUE(moduleOp); + + auto funcOp = *moduleOp->getBody()->getOps().begin(); + auto powOp = *funcOp.getBody().getOps().begin(); + auto matrix = powOp.getUnitaryMatrix(); + + // X^0.5 requires eigendecomposition (not yet supported). + ASSERT_FALSE(matrix.has_value()); +} + +TEST_F(QCOMatrixTest, PowNegHalfXOpMatrix) { + auto moduleOp = QCOProgramBuilder::build(context.get(), powNegHalfX); + ASSERT_TRUE(moduleOp); + + auto funcOp = *moduleOp->getBody()->getOps().begin(); + auto powOp = *funcOp.getBody().getOps().begin(); + auto matrix = powOp.getUnitaryMatrix(); + + // X^-0.5 requires eigendecomposition (not yet supported). + ASSERT_FALSE(matrix.has_value()); +} +/// @} + /// \name QCO/Modifiers/InvOp.cpp /// @{ TEST_F(QCOMatrixTest, InverseIswapOpMatrix) { diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index 8410afd7a8..5bd5b21fe8 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -223,6 +223,18 @@ void inverseMultipleControlledGlobalPhase(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mcgphase(-0.123, qubits); }); } +void powGphaseScaled(QCProgramBuilder& b) { + b.pow(3.0, {}, [&](ValueRange) { b.gphase(0.123); }); +} + +void powGphaseScaledRef(QCProgramBuilder& b) { b.gphase(3.0 * 0.123); } + +void negPowGphase(QCProgramBuilder& b) { + b.pow(-3.0, {}, [&](ValueRange) { b.gphase(0.123); }); +} + +void negPowGphaseRef(QCProgramBuilder& b) { b.gphase(-3.0 * 0.123); } + void identity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.id(q[0]); @@ -260,6 +272,11 @@ void inverseMultipleControlledIdentity(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mcid({qubits[0], qubits[1]}, qubits[2]); }); } +void powId(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.id(qubits[0]); }); +} + void x(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.x(q[0]); @@ -305,6 +322,32 @@ void inverseMultipleControlledX(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mcx({qubits[0], qubits[1]}, qubits[2]); }); } +void powHalfX(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, q[0], [&](ValueRange qubits) { b.x(qubits[0]); }); +} + +void powHalfXRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.sx(q[0]); +} + +void powNegHalfX(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(-0.5, q[0], [&](ValueRange qubits) { b.x(qubits[0]); }); +} + +void powThirdX(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, q[0], [&](ValueRange qubits) { b.x(qubits[0]); }); +} + +void powThirdXRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.gphase(-1.0 / 3.0 * std::numbers::pi / 2.0); + b.rx(1.0 / 3.0 * std::numbers::pi, q[0]); +} + void y(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.y(q[0]); @@ -342,6 +385,17 @@ void inverseMultipleControlledY(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mcy({qubits[0], qubits[1]}, qubits[2]); }); } +void powHalfY(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, q[0], [&](ValueRange qubits) { b.y(qubits[0]); }); +} + +void powHalfYRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.gphase(-std::numbers::pi / 4.0); + b.ry(std::numbers::pi / 2.0, q[0]); +} + void z(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.z(q[0]); @@ -379,6 +433,26 @@ void inverseMultipleControlledZ(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mcz({qubits[0], qubits[1]}, qubits[2]); }); } +void powHalfZ(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, q[0], [&](ValueRange qubits) { b.z(qubits[0]); }); +} + +void powThreeHalvesZ(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.5, q[0], [&](ValueRange qubits) { b.z(qubits[0]); }); +} + +void powThirdZ(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, q[0], [&](ValueRange qubits) { b.z(qubits[0]); }); +} + +void powThirdZRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.p(1.0 / 3.0 * std::numbers::pi, q[0]); +} + void h(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); @@ -421,6 +495,16 @@ void hWithoutRegister(QCProgramBuilder& b) { b.h(q); } +void powEvenH(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.h(qubits[0]); }); +} + +void powOddH(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(3.0, q[0], [&](ValueRange qubits) { b.h(qubits[0]); }); +} + void s(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.s(q[0]); @@ -458,6 +542,31 @@ void inverseMultipleControlledS(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mcs({qubits[0], qubits[1]}, qubits[2]); }); } +void powTwoS(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.s(qubits[0]); }); +} + +void powFourS(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(4.0, q[0], [&](ValueRange qubits) { b.s(qubits[0]); }); +} + +void powHalfS(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, q[0], [&](ValueRange qubits) { b.s(qubits[0]); }); +} + +void powThirdS(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, q[0], [&](ValueRange qubits) { b.s(qubits[0]); }); +} + +void powThirdSRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.p(1.0 / 3.0 * std::numbers::pi / 2.0, q[0]); +} + void sdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sdg(q[0]); @@ -495,6 +604,26 @@ void inverseMultipleControlledSdg(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mcsdg({qubits[0], qubits[1]}, qubits[2]); }); } +void powTwoSdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.sdg(qubits[0]); }); +} + +void powHalfSdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, q[0], [&](ValueRange qubits) { b.sdg(qubits[0]); }); +} + +void powThirdSdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, q[0], [&](ValueRange qubits) { b.sdg(qubits[0]); }); +} + +void powThirdSdgRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.p(-1.0 / 3.0 * std::numbers::pi / 2.0, q[0]); +} + void t_(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.t(q[0]); @@ -532,6 +661,21 @@ void inverseMultipleControlledT(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mct({qubits[0], qubits[1]}, qubits[2]); }); } +void powTwoT(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.t(qubits[0]); }); +} + +void powThirdT(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, q[0], [&](ValueRange qubits) { b.t(qubits[0]); }); +} + +void powThirdTRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.p(1.0 / 3.0 * std::numbers::pi / 4.0, q[0]); +} + void tdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.tdg(q[0]); @@ -569,6 +713,21 @@ void inverseMultipleControlledTdg(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mctdg({qubits[0], qubits[1]}, qubits[2]); }); } +void powTwoTdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.tdg(qubits[0]); }); +} + +void powThirdTdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, q[0], [&](ValueRange qubits) { b.tdg(qubits[0]); }); +} + +void powThirdTdgRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.p(-1.0 / 3.0 * std::numbers::pi / 4.0, q[0]); +} + void sx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sx(q[0]); @@ -606,6 +765,27 @@ void inverseMultipleControlledSx(QCProgramBuilder& b) { [&](ValueRange qubits) { b.mcsx({qubits[0], qubits[1]}, qubits[2]); }); } +void powTwoSx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.sx(qubits[0]); }); +} + +void powTwoSxRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.x(q[0]); +} + +void powThirdSx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, q[0], [&](ValueRange qubits) { b.sx(qubits[0]); }); +} + +void powThirdSxRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.gphase(-1.0 / 3.0 * std::numbers::pi / 4.0); + b.rx(1.0 / 3.0 * std::numbers::pi / 2.0, q[0]); +} + void sxdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sxdg(q[0]); @@ -644,6 +824,27 @@ void inverseMultipleControlledSxdg(QCProgramBuilder& b) { }); } +void powTwoSxdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.sxdg(qubits[0]); }); +} + +void powTwoSxdgRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.x(q[0]); +} + +void powThirdSxdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, q[0], [&](ValueRange qubits) { b.sxdg(qubits[0]); }); +} + +void powThirdSxdgRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.gphase(1.0 / 3.0 * std::numbers::pi / 4.0); + b.rx(-1.0 / 3.0 * std::numbers::pi / 2.0, q[0]); +} + void rx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.rx(0.123, q[0]); @@ -682,6 +883,16 @@ void inverseMultipleControlledRx(QCProgramBuilder& b) { }); } +void powRxScaled(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.rx(0.123, qubits[0]); }); +} + +void rxScaled(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.rx(0.246, q[0]); +} + void ry(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.ry(0.456, q[0]); @@ -835,6 +1046,16 @@ void inverseMultipleControlledR(QCProgramBuilder& b) { }); } +void powRScaled(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(3.0, q[0], [&](ValueRange qubits) { b.r(0.123, 0.456, qubits[0]); }); +} + +void powRScaledRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.r(3.0 * 0.123, 0.456, q[0]); +} + void u2(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.u2(0.234, 0.567, q[0]); @@ -955,6 +1176,18 @@ void inverseMultipleControlledSwap(QCProgramBuilder& b) { }); } +void powEvenSwap(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(2.0, {q[0], q[1]}, + [&](ValueRange qubits) { b.swap(qubits[0], qubits[1]); }); +} + +void powOddSwap(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(3.0, {q[0], q[1]}, + [&](ValueRange qubits) { b.swap(qubits[0], qubits[1]); }); +} + void iswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.iswap(q[0], q[1]); @@ -995,6 +1228,17 @@ void inverseMultipleControlledIswap(QCProgramBuilder& b) { }); } +void powHalfIswap(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(0.5, {q[0], q[1]}, + [&](ValueRange qubits) { b.iswap(qubits[0], qubits[1]); }); +} + +void powHalfIswapRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.xx_plus_yy(-std::numbers::pi / 2.0, 0.0, q[0], q[1]); +} + void dcx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.dcx(q[0], q[1]); @@ -1073,6 +1317,18 @@ void inverseMultipleControlledEcr(QCProgramBuilder& b) { }); } +void powEvenEcr(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(2.0, {q[0], q[1]}, + [&](ValueRange qubits) { b.ecr(qubits[0], qubits[1]); }); +} + +void powOddEcr(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(3.0, {q[0], q[1]}, + [&](ValueRange qubits) { b.ecr(qubits[0], qubits[1]); }); +} + void rxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rxx(0.123, q[0], q[1]); @@ -1284,6 +1540,18 @@ void inverseMultipleControlledXxPlusYY(QCProgramBuilder& b) { }); } +void powXxPlusYYScaled(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(3.0, {q[0], q[1]}, [&](ValueRange qubits) { + b.xx_plus_yy(0.123, 0.456, qubits[0], qubits[1]); + }); +} + +void powXxPlusYYScaledRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.xx_plus_yy(3.0 * 0.123, 0.456, q[0], q[1]); +} + void xxMinusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.xx_minus_yy(0.123, 0.456, q[0], q[1]); @@ -1326,6 +1594,18 @@ void inverseMultipleControlledXxMinusYY(QCProgramBuilder& b) { }); } +void powXxMinusYYScaled(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(3.0, {q[0], q[1]}, [&](ValueRange qubits) { + b.xx_minus_yy(0.123, 0.456, qubits[0], qubits[1]); + }); +} + +void powXxMinusYYScaledRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.xx_minus_yy(3.0 * 0.123, 0.456, q[0], q[1]); +} + void barrier(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.barrier(q[0]); @@ -1351,6 +1631,11 @@ void inverseBarrier(QCProgramBuilder& b) { b.inv(q[0], [&](ValueRange qubits) { b.barrier(qubits[0]); }); } +void powBarrier(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.barrier(qubits[0]); }); +} + void trivialCtrl(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ctrl({}, {q[0], q[1]}, @@ -1497,6 +1782,119 @@ void invCtrlTwo(QCProgramBuilder& b) { }); } +void pow1Inline(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0, q[0], [&](ValueRange qubits) { b.rx(0.123, qubits[0]); }); +} + +void pow0Erase(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.0, q[0], [&](ValueRange qubits) { b.rx(0.123, qubits[0]); }); +} + +void nestedPow(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(3.0, q[0], [&](ValueRange qubits) { + b.pow(2.0, qubits[0], [&](ValueRange inner) { b.rx(0.123, inner[0]); }); + }); +} + +void powSingleExponent(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(6.0, q[0], [&](ValueRange qubits) { b.rx(0.123, qubits[0]); }); +} + +void powRxx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(2.0, {q[0], q[1]}, + [&](ValueRange qubits) { b.rxx(0.123, qubits[0], qubits[1]); }); +} + +void negPowRx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(-2.0, q[0], [&](ValueRange qubits) { b.rx(0.123, qubits[0]); }); +} + +void powRxNeg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, q[0], [&](ValueRange qubits) { b.rx(-0.123, qubits[0]); }); +} + +void negPowH(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(-0.5, q[0], [&](ValueRange qubits) { b.h(qubits[0]); }); +} + +void invPowHFrac(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.inv(q[0], [&](ValueRange args) { + b.pow(0.5, args[0], [&](ValueRange p) { b.h(p[0]); }); + }); +} + +void powHFracNeg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(-0.5, q[0], [&](ValueRange qubits) { b.h(qubits[0]); }); +} + +void invPowRx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.inv(q[0], [&](ValueRange args) { + b.pow(2.0, args[0], [&](ValueRange p) { b.rx(0.123, p[0]); }); + }); +} + +void powCtrlRx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(2.0, {q[0], q[1]}, [&](ValueRange qubits) { + b.ctrl(qubits[0], qubits[1], + [&](ValueRange args) { b.rx(0.123, args[0]); }); + }); +} + +void ctrlPowRx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.ctrl(q[0], q[1], [&](ValueRange args) { + b.pow(2.0, args[0], [&](ValueRange p) { b.rx(0.123, p[0]); }); + }); +} + +void negPowInvIswap(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(-2.0, {q[0], q[1]}, [&](ValueRange qubits) { + b.inv({qubits[0], qubits[1]}, + [&](ValueRange args) { b.iswap(args[0], args[1]); }); + }); +} + +void negPowInvIswapRef(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.xx_plus_yy(-2.0 * std::numbers::pi, 0.0, q[0], q[1]); +} + +void ctrlPowSx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.ctrl(q[0], q[1], [&](ValueRange args) { + b.pow(1.0 / 3.0, args[0], [&](ValueRange p) { b.sx(p[0]); }); + }); +} + +void powTwo(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(2.0, {q[0], q[1]}, [&](ValueRange qubits) { + b.x(qubits[0]); + b.rxx(0.123, qubits[0], qubits[1]); + }); +} + +void pow0Two(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(0.0, {q[0], q[1]}, [&](ValueRange qubits) { + b.x(qubits[0]); + b.rxx(0.123, qubits[0], qubits[1]); + }); +} + void simpleIf(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); diff --git a/mlir/unittests/programs/qc_programs.h b/mlir/unittests/programs/qc_programs.h index 25d76718cc..d4cbd19698 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -133,6 +133,18 @@ void inverseGlobalPhase(QCProgramBuilder& b); /// phase gate. void inverseMultipleControlledGlobalPhase(QCProgramBuilder& b); +/// Creates a circuit with pow(3.0) wrapping a global-phase gate (scales θ). +void powGphaseScaled(QCProgramBuilder& b); + +/// Creates the reference for powGphaseScaled: gphase(3*0.123). +void powGphaseScaledRef(QCProgramBuilder& b); + +/// Creates a circuit with pow(-3.0) wrapping gphase (negative exponent). +void negPowGphase(QCProgramBuilder& b); + +/// Reference for negPowGphase: gphase(-3.0 * 0.123). +void negPowGphaseRef(QCProgramBuilder& b); + // --- IdOp ----------------------------------------------------------------- // /// Creates a circuit with just an identity gate. @@ -157,6 +169,9 @@ void inverseIdentity(QCProgramBuilder& b); /// gate. void inverseMultipleControlledIdentity(QCProgramBuilder& b); +/// Creates a circuit with pow(2.0) wrapping id (should pass through). +void powId(QCProgramBuilder& b); + // --- XOp ------------------------------------------------------------------ // /// Creates a circuit with just an X gate. @@ -183,6 +198,21 @@ void inverseX(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled X gate. void inverseMultipleControlledX(QCProgramBuilder& b); +/// Creates a circuit with pow(0.5) wrapping an X gate (folds to gphase + RX). +void powHalfX(QCProgramBuilder& b); + +/// Creates the reference for powHalfX: sx (X^(1/2) = SX exactly). +void powHalfXRef(QCProgramBuilder& b); + +/// Creates a circuit with pow(-0.5) wrapping an X gate (r == -0.5 → sxdg). +void powNegHalfX(QCProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an X gate (general: gphase + rx). +void powThirdX(QCProgramBuilder& b); + +/// Creates the reference for powThirdX: gphase(-π/6) + rx(π/3). +void powThirdXRef(QCProgramBuilder& b); + // --- YOp ------------------------------------------------------------------ // /// Creates a circuit with just a Y gate. @@ -206,6 +236,12 @@ void inverseY(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Y gate. void inverseMultipleControlledY(QCProgramBuilder& b); +/// Creates a circuit with pow(0.5) wrapping a Y gate (folds to gphase + RY). +void powHalfY(QCProgramBuilder& b); + +/// Creates the reference for powHalfY: gphase(-π/4) followed by ry(π/2). +void powHalfYRef(QCProgramBuilder& b); + // --- ZOp ------------------------------------------------------------------ // /// Creates a circuit with just a Z gate. @@ -229,6 +265,19 @@ void inverseZ(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Z gate. void inverseMultipleControlledZ(QCProgramBuilder& b); +/// Creates a circuit with pow(0.5) wrapping a Z gate (folds to P(π/2) = S). +void powHalfZ(QCProgramBuilder& b); + +/// Creates a circuit with pow(1.5) wrapping a Z gate. +/// Exercises normalizeAngle theta -= twoPi (1.5π normalises to -π/2 → sdg). +void powThreeHalvesZ(QCProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping a Z gate (falls through to P gate). +void powThirdZ(QCProgramBuilder& b); + +/// Creates the reference for powThirdZ: p(π/3). +void powThirdZRef(QCProgramBuilder& b); + // --- HOp ------------------------------------------------------------------ // /// Creates a circuit with just an H gate. @@ -255,6 +304,12 @@ void inverseMultipleControlledH(QCProgramBuilder& b); /// Creates a circuit with just an H gate and no qubit register. void hWithoutRegister(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an H gate (even hermitian → erase). +void powEvenH(QCProgramBuilder& b); + +/// Creates a circuit with pow(3) wrapping an H gate (odd hermitian → H). +void powOddH(QCProgramBuilder& b); + // --- SOp ------------------------------------------------------------------ // /// Creates a circuit with just an S gate. @@ -278,6 +333,23 @@ void inverseS(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled S gate. void inverseMultipleControlledS(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an S gate (folds to P(π) = Z). +void powTwoS(QCProgramBuilder& b); + +/// Creates a circuit with pow(4.0) wrapping an S gate. +/// Exercises tryReplaceWithNamedPhaseGate erase path (angle=2π → identity). +void powFourS(QCProgramBuilder& b); + +/// Creates a circuit with pow(0.5) wrapping an S gate. +/// Exercises tryReplaceWithNamedPhaseGate TOp path (angle=π/4 → t). +void powHalfS(QCProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an S gate (default: p(π/6)). +void powThirdS(QCProgramBuilder& b); + +/// Creates the reference for powThirdS: p(π/6). +void powThirdSRef(QCProgramBuilder& b); + // --- SdgOp ---------------------------------------------------------------- // /// Creates a circuit with just an Sdg gate. @@ -301,6 +373,19 @@ void inverseSdg(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Sdg gate. void inverseMultipleControlledSdg(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an Sdg gate (folds to P(-π) = Z). +void powTwoSdg(QCProgramBuilder& b); + +/// Creates a circuit with pow(0.5) wrapping an Sdg gate. +/// Exercises tryReplaceWithNamedPhaseGate TdgOp path (angle=-π/4 → tdg). +void powHalfSdg(QCProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an Sdg gate (default: p(-π/6)). +void powThirdSdg(QCProgramBuilder& b); + +/// Creates the reference for powThirdSdg: p(-π/6). +void powThirdSdgRef(QCProgramBuilder& b); + // --- TOp ------------------------------------------------------------------ // /// Creates a circuit with just a T gate. @@ -324,6 +409,15 @@ void inverseT(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled T gate. void inverseMultipleControlledT(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping a T gate (folds to P(π/2) = S). +void powTwoT(QCProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping a T gate (default: p(π/12)). +void powThirdT(QCProgramBuilder& b); + +/// Creates the reference for powThirdT: p(π/12). +void powThirdTRef(QCProgramBuilder& b); + // --- TdgOp ---------------------------------------------------------------- // /// Creates a circuit with just a Tdg gate. @@ -347,6 +441,15 @@ void inverseTdg(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled Tdg gate. void inverseMultipleControlledTdg(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping a Tdg gate (folds to P(-π/2) = Sdg). +void powTwoTdg(QCProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping a Tdg gate (default: p(-π/12)). +void powThirdTdg(QCProgramBuilder& b); + +/// Creates the reference for powThirdTdg: p(-π/12). +void powThirdTdgRef(QCProgramBuilder& b); + // --- SXOp ----------------------------------------------------------------- // /// Creates a circuit with just an SX gate. @@ -370,6 +473,18 @@ void inverseSx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled SX gate. void inverseMultipleControlledSx(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an SX gate (folds to X: SX^2 = X). +void powTwoSx(QCProgramBuilder& b); + +/// Creates the reference for powTwoSx: x (SX^2 = X exactly). +void powTwoSxRef(QCProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an SX gate (default: gphase+rx). +void powThirdSx(QCProgramBuilder& b); + +/// Creates the reference for powThirdSx: gphase(-π/12) + rx(π/6). +void powThirdSxRef(QCProgramBuilder& b); + // --- SXdgOp --------------------------------------------------------------- // /// Creates a circuit with just an SXdg gate. @@ -394,6 +509,19 @@ void inverseSxdg(QCProgramBuilder& b); /// gate. void inverseMultipleControlledSxdg(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an SXdg gate (folds to X: SXdg^2 = +/// X). +void powTwoSxdg(QCProgramBuilder& b); + +/// Creates the reference for powTwoSxdg: x (SXdg^2 = X exactly). +void powTwoSxdgRef(QCProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an SXdg gate (default: gphase+rx). +void powThirdSxdg(QCProgramBuilder& b); + +/// Creates the reference for powThirdSxdg: gphase(π/12) + rx(-π/6). +void powThirdSxdgRef(QCProgramBuilder& b); + // --- RXOp ----------------------------------------------------------------- // /// Creates a circuit with just an RX gate. @@ -417,6 +545,12 @@ void inverseRx(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled RX gate. void inverseMultipleControlledRx(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping rx(0.123) (folds to rx(0.246)). +void powRxScaled(QCProgramBuilder& b); + +/// Creates the reference for powRxScaled: rx(0.246) directly. +void rxScaled(QCProgramBuilder& b); + // --- RYOp ----------------------------------------------------------------- // /// Creates a circuit with just an RY gate. @@ -509,6 +643,12 @@ void inverseR(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled R gate. void inverseMultipleControlledR(QCProgramBuilder& b); +/// Creates a circuit with pow(3.0) wrapping an R gate (scales θ, preserves φ). +void powRScaled(QCProgramBuilder& b); + +/// Creates the reference for powRScaled: r(3*0.123, 0.456). +void powRScaledRef(QCProgramBuilder& b); + // --- U2Op ----------------------------------------------------------------- // /// Creates a circuit with just a U2 gate. @@ -579,6 +719,12 @@ void inverseSwap(QCProgramBuilder& b); /// gate. void inverseMultipleControlledSwap(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping a SWAP gate (even hermitian → erase). +void powEvenSwap(QCProgramBuilder& b); + +/// Creates a circuit with pow(3) wrapping a SWAP gate (odd hermitian → SWAP). +void powOddSwap(QCProgramBuilder& b); + // --- iSWAPOp -------------------------------------------------------------- // /// Creates a circuit with just an iSWAP gate. @@ -603,6 +749,13 @@ void inverseIswap(QCProgramBuilder& b); /// gate. void inverseMultipleControlledIswap(QCProgramBuilder& b); +/// Creates a circuit with pow(0.5) wrapping an iSWAP gate (folds to +/// xx_plus_yy(-π/2, 0)). +void powHalfIswap(QCProgramBuilder& b); + +/// Creates the reference for powHalfIswap: xx_plus_yy(-π/2, 0) directly. +void powHalfIswapRef(QCProgramBuilder& b); + // --- DCXOp ---------------------------------------------------------------- // /// Creates a circuit with just a DCX gate. @@ -649,6 +802,12 @@ void inverseEcr(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled ECR gate. void inverseMultipleControlledEcr(QCProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an ECR gate (even hermitian → erase). +void powEvenEcr(QCProgramBuilder& b); + +/// Creates a circuit with pow(3) wrapping an ECR gate (odd hermitian → ECR). +void powOddEcr(QCProgramBuilder& b); + // --- RXXOp ---------------------------------------------------------------- // /// Creates a circuit with just an RXX gate. @@ -771,6 +930,12 @@ void inverseXxPlusYY(QCProgramBuilder& b); /// gate. void inverseMultipleControlledXxPlusYY(QCProgramBuilder& b); +/// Creates a circuit with pow(3.0) wrapping an XX+YY gate (scales θ). +void powXxPlusYYScaled(QCProgramBuilder& b); + +/// Creates the reference for powXxPlusYYScaled: xx_plus_yy(3*0.123, 0.456). +void powXxPlusYYScaledRef(QCProgramBuilder& b); + // --- XXMinusYYOp ---------------------------------------------------------- // /// Creates a circuit with just an XXMinusYY gate. @@ -795,6 +960,12 @@ void inverseXxMinusYY(QCProgramBuilder& b); /// gate. void inverseMultipleControlledXxMinusYY(QCProgramBuilder& b); +/// Creates a circuit with pow(3.0) wrapping an XX-YY gate (scales θ). +void powXxMinusYYScaled(QCProgramBuilder& b); + +/// Creates the reference for powXxMinusYYScaled: xx_minus_yy(3*0.123, 0.456). +void powXxMinusYYScaledRef(QCProgramBuilder& b); + // --- BarrierOp ------------------------------------------------------------ // /// Creates a circuit with a barrier. @@ -812,6 +983,9 @@ void singleControlledBarrier(QCProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a barrier. void inverseBarrier(QCProgramBuilder& b); +/// Creates a circuit with pow(2.0) wrapping barrier (should pass through). +void powBarrier(QCProgramBuilder& b); + // --- CtrlOp --------------------------------------------------------------- // /// Creates a circuit with a trivial ctrl modifier. @@ -867,6 +1041,76 @@ void invTwo(QCProgramBuilder& b); /// applied to two gates. void invCtrlTwo(QCProgramBuilder& b); +// --- PowOp ---------------------------------------------------------------- // + +/// Creates a circuit with pow(1.0) modifier (should inline to just the gate). +void pow1Inline(QCProgramBuilder& b); + +/// Creates a circuit with pow(0.0) modifier (should erase to identity). +void pow0Erase(QCProgramBuilder& b); + +/// Creates a circuit with nested pow modifiers (should merge exponents). +void nestedPow(QCProgramBuilder& b); + +/// Creates a circuit with pow(6.0) as the merged reference for nestedPow. +void powSingleExponent(QCProgramBuilder& b); + +/// Creates a circuit with pow(2.0) wrapping a two-qubit RXX gate. +void powRxx(QCProgramBuilder& b); + +/// Creates a circuit with pow(-2.0) wrapping an RX gate (negative exponent). +void negPowRx(QCProgramBuilder& b); + +/// Creates a circuit with pow(2.0) wrapping RX(-0.123) (reference for +/// negPowRx and invPowRx — inv folds into angle negation). +void powRxNeg(QCProgramBuilder& b); + +/// Creates a circuit with pow(-0.5) wrapping H (negative non-integer exponent). +/// Expected to remain unchanged: fractional exponent on a unitary with +/// eigenvalue -1 cannot safely apply NegPowToInvPow. +void negPowH(QCProgramBuilder& b); + +/// Creates a circuit with inv wrapping pow(0.5) wrapping H. +/// MovePowOutside emits pow(-0.5){H} (not wrapping in inv). +void invPowHFrac(QCProgramBuilder& b); + +/// Creates a circuit with pow(-0.5) wrapping H (reference for invPowHFrac). +void powHFracNeg(QCProgramBuilder& b); + +/// Creates a circuit with inv wrapping pow (should reorder to pow wrapping +/// inv). +void invPowRx(QCProgramBuilder& b); + +/// Creates a circuit with pow wrapping ctrl wrapping RX (should move ctrl +/// outside). +void powCtrlRx(QCProgramBuilder& b); + +/// Creates a circuit with ctrl wrapping pow wrapping RX (reference for +/// powCtrlRx). +void ctrlPowRx(QCProgramBuilder& b); + +/// Creates a circuit with pow(-2) wrapping inv wrapping iSWAP. +/// Exercises NegPowToInvPow: inv{iswap} survives InvOp canonicalization, +/// FoldPowIntoGate fails (inner is InvOp), so NegPowToInvPow fires. +void negPowInvIswap(QCProgramBuilder& b); + +/// Reference for negPowInvIswap: xx_plus_yy(-2π, 0) (the fully folded form). +void negPowInvIswapRef(QCProgramBuilder& b); + +/// Creates a circuit with ctrl wrapping pow(1/3) wrapping SX. The fold +/// pow(p){SX} → gphase+rx is suppressed inside ctrl (would emit two ops), +/// so the pow survives canonicalization and reaches ConvertQCPowOp. +void ctrlPowSx(QCProgramBuilder& b); + +/// pow(2) with a two-unitary body (x; rxx) — a multi-unitary pow body (newly +/// legal). The optimizer leaves such bodies untouched; used to check +/// verification and the QC↔QCO round-trip. +void powTwo(QCProgramBuilder& b); + +/// pow(0) with a two-unitary body (x; rxx) — folds to identity (erased at top +/// level). +void pow0Two(QCProgramBuilder& b); + // --- IfOp ----------------------------------------------------------------- // /// Creates a circuit with a simple if operation with one qubit. diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index b384aa20a1..ddf174dd6b 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -191,6 +191,24 @@ void inverseMultipleControlledGlobalPhase(QCOProgramBuilder& b) { }); } +void powGphaseScaled(QCOProgramBuilder& b) { + b.pow({}, 3.0, [&](mlir::ValueRange /*qubits*/) { + b.gphase(0.123); + return llvm::SmallVector{}; + }); +} + +void powGphaseScaledRef(QCOProgramBuilder& b) { b.gphase(3.0 * 0.123); } + +void negPowGphase(QCOProgramBuilder& b) { + b.pow({}, -3.0, [&](mlir::ValueRange /*qubits*/) { + b.gphase(0.123); + return llvm::SmallVector{}; + }); +} + +void negPowGphaseRef(QCOProgramBuilder& b) { b.gphase(-3.0 * 0.123); } + void identity(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.id(q[0]); @@ -239,6 +257,14 @@ void inverseMultipleControlledIdentity(QCOProgramBuilder& b) { }); } +void powId(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.id(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + void x(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.x(q[0]); @@ -328,6 +354,40 @@ void inverseTwoX(QCOProgramBuilder& b) { return SmallVector{q}; }); } +void powHalfX(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 0.5, [&](mlir::ValueRange qubits) { + auto q0 = b.x(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powHalfXRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.sx(q[0]); +} + +void powNegHalfX(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, -0.5, [&](mlir::ValueRange qubits) { + auto q0 = b.x(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdX(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.0 / 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.x(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdXRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.gphase(-1.0 / 3.0 * std::numbers::pi / 2.0); + q[0] = b.rx(1.0 / 3.0 * std::numbers::pi, q[0]); +} void inverseGphaseX(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); @@ -406,6 +466,20 @@ void twoY(QCOProgramBuilder& b) { q[0] = b.y(q[0]); } +void powHalfY(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 0.5, [&](mlir::ValueRange qubits) { + auto q0 = b.y(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powHalfYRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.gphase(-std::numbers::pi / 4.0); + q[0] = b.ry(std::numbers::pi / 2.0, q[0]); +} + void z(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.z(q[0]); @@ -459,6 +533,35 @@ void twoZ(QCOProgramBuilder& b) { q[0] = b.z(q[0]); } +void powHalfZ(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 0.5, [&](mlir::ValueRange qubits) { + auto q0 = b.z(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThreeHalvesZ(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.5, [&](mlir::ValueRange qubits) { + auto q0 = b.z(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdZ(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.0 / 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.z(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdZRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.p(1.0 / 3.0 * std::numbers::pi, q[0]); +} + void h(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); @@ -517,6 +620,22 @@ void hWithoutRegister(QCOProgramBuilder& b) { b.h(q); } +void powEvenH(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.h(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powOddH(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.h(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + void s(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.s(q[0]); @@ -576,6 +695,43 @@ void twoS(QCOProgramBuilder& b) { q[0] = b.s(q[0]); } +void powTwoS(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.s(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powFourS(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 4.0, [&](mlir::ValueRange qubits) { + auto q0 = b.s(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powHalfS(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 0.5, [&](mlir::ValueRange qubits) { + auto q0 = b.s(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdS(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.0 / 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.s(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdSRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.p(1.0 / 3.0 * std::numbers::pi / 2.0, q[0]); +} + void sdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sdg(q[0]); @@ -636,6 +792,35 @@ void twoSdg(QCOProgramBuilder& b) { q[0] = b.sdg(q[0]); } +void powTwoSdg(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.sdg(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powHalfSdg(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 0.5, [&](mlir::ValueRange qubits) { + auto q0 = b.sdg(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdSdg(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.0 / 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.sdg(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdSdgRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.p(-1.0 / 3.0 * std::numbers::pi / 2.0, q[0]); +} + void t_(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.t(q[0]); @@ -695,6 +880,27 @@ void twoT(QCOProgramBuilder& b) { q[0] = b.t(q[0]); } +void powTwoT(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.t(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdT(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.0 / 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.t(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdTRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.p(1.0 / 3.0 * std::numbers::pi / 4.0, q[0]); +} + void tdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.tdg(q[0]); @@ -755,6 +961,27 @@ void twoTdg(QCOProgramBuilder& b) { q[0] = b.tdg(q[0]); } +void powTwoTdg(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.tdg(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdTdg(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.0 / 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.tdg(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdTdgRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.p(-1.0 / 3.0 * std::numbers::pi / 4.0, q[0]); +} + void sx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sx(q[0]); @@ -815,6 +1042,33 @@ void twoSx(QCOProgramBuilder& b) { q[0] = b.sx(q[0]); } +void powTwoSx(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.sx(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powTwoSxRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.x(q[0]); +} + +void powThirdSx(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.0 / 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.sx(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdSxRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.gphase(-1.0 / 3.0 * std::numbers::pi / 4.0); + q[0] = b.rx(1.0 / 3.0 * std::numbers::pi / 2.0, q[0]); +} + void sxdg(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.sxdg(q[0]); @@ -875,6 +1129,33 @@ void twoSxdg(QCOProgramBuilder& b) { q[0] = b.sxdg(q[0]); } +void powTwoSxdg(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.sxdg(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powTwoSxdgRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.x(q[0]); +} + +void powThirdSxdg(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.0 / 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.sxdg(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powThirdSxdgRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.gphase(1.0 / 3.0 * std::numbers::pi / 4.0); + q[0] = b.rx(-1.0 / 3.0 * std::numbers::pi / 2.0, q[0]); +} + void rx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.rx(0.123, q[0]); @@ -935,6 +1216,19 @@ void rxPiOver2(QCOProgramBuilder& b) { b.rx(std::numbers::pi / 2, q[0]); } +void powRxScaled(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.rx(0.123, qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void rxScaled(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.rx(0.246, q[0]); +} + void ry(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.ry(0.456, q[0]); @@ -1152,6 +1446,19 @@ void inverseMultipleControlledR(QCOProgramBuilder& b) { }); } +void powRScaled(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 3.0, [&](mlir::ValueRange qubits) { + auto q0 = b.r(0.123, 0.456, qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powRScaledRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + q[0] = b.r(3.0 * 0.123, 0.456, q[0]); +} + void canonicalizeRToRx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); q[0] = b.r(0.123, 0., q[0]); @@ -1364,6 +1671,22 @@ void twoSwapSwappedTargets(QCOProgramBuilder& b) { std::tie(q[1], q[0]) = b.swap(q[1], q[0]); } +void powEvenSwap(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 2.0, [&](mlir::ValueRange qubits) { + auto res = b.swap(qubits[0], qubits[1]); + return llvm::SmallVector{res.first, res.second}; + }); +} + +void powOddSwap(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 3.0, [&](mlir::ValueRange qubits) { + auto res = b.swap(qubits[0], qubits[1]); + return llvm::SmallVector{res.first, res.second}; + }); +} + void iswap(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.iswap(q[0], q[1]); @@ -1415,6 +1738,19 @@ void inverseMultipleControlledIswap(QCOProgramBuilder& b) { }); } +void powHalfIswap(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 0.5, [&](mlir::ValueRange qubits) { + auto res = b.iswap(qubits[0], qubits[1]); + return llvm::SmallVector{res.first, res.second}; + }); +} + +void powHalfIswapRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.xx_plus_yy(-std::numbers::pi / 2.0, 0.0, q[0], q[1]); +} + void dcx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.dcx(q[0], q[1]); @@ -1535,6 +1871,22 @@ void twoEcr(QCOProgramBuilder& b) { std::tie(q[0], q[1]) = b.ecr(q[0], q[1]); } +void powEvenEcr(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 2.0, [&](mlir::ValueRange qubits) { + auto res = b.ecr(qubits[0], qubits[1]); + return llvm::SmallVector{res.first, res.second}; + }); +} + +void powOddEcr(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 3.0, [&](mlir::ValueRange qubits) { + auto res = b.ecr(qubits[0], qubits[1]); + return llvm::SmallVector{res.first, res.second}; + }); +} + void rxx(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rxx(0.123, q[0], q[1]); @@ -1885,6 +2237,19 @@ void inverseMultipleControlledXxPlusYY(QCOProgramBuilder& b) { }); } +void powXxPlusYYScaled(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 3.0, [&](mlir::ValueRange qubits) { + auto [q0, q1] = b.xx_plus_yy(0.123, 0.456, qubits[0], qubits[1]); + return llvm::SmallVector{q0, q1}; + }); +} + +void powXxPlusYYScaledRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.xx_plus_yy(3.0 * 0.123, 0.456, q[0], q[1]); +} + void twoXxPlusYYOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.xx_plus_yy(0.123, 0.456, q[0], q[1]); @@ -1949,6 +2314,19 @@ void inverseMultipleControlledXxMinusYY(QCOProgramBuilder& b) { }); } +void powXxMinusYYScaled(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 3.0, [&](mlir::ValueRange qubits) { + auto [q0, q1] = b.xx_minus_yy(0.123, 0.456, qubits[0], qubits[1]); + return llvm::SmallVector{q0, q1}; + }); +} + +void powXxMinusYYScaledRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.xx_minus_yy(3.0 * 0.123, 0.456, q[0], q[1]); +} + void twoXxMinusYYOppositePhase(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); std::tie(q[0], q[1]) = b.xx_minus_yy(0.123, 0.456, q[0], q[1]); @@ -1990,6 +2368,14 @@ void inverseBarrier(QCOProgramBuilder& b) { }); } +void powBarrier(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.barrier(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + void twoBarrier(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); auto b1 = b.barrier({q[0], q[1]}); @@ -2190,6 +2576,17 @@ void invTwo(QCOProgramBuilder& b) { }); } +void powTwo(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 2.0, [&](ValueRange qubits) { + auto i0 = qubits[0]; + auto i1 = qubits[1]; + i0 = b.x(i0); + std::tie(i0, i1) = b.rxx(0.123, i0, i1); + return SmallVector{i0, i1}; + }); +} + void invCtrlTwo(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(3); b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { @@ -2205,6 +2602,162 @@ void invCtrlTwo(QCOProgramBuilder& b) { }); } +void pow1Inline(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 1.0, [&](mlir::ValueRange qubits) { + auto q0 = b.rx(0.123, qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void pow0Erase(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 0.0, [&](mlir::ValueRange qubits) { + auto q0 = b.rx(0.123, qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void nestedPow(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 3.0, [&](mlir::ValueRange qubits) { + auto inner = b.pow({qubits[0]}, 2.0, [&](mlir::ValueRange innerQubits) { + auto q0 = b.rx(0.123, innerQubits[0]); + return llvm::SmallVector{q0}; + }); + return llvm::SmallVector{inner}; + }); +} + +void powSingleExponent(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 6.0, [&](mlir::ValueRange qubits) { + auto q0 = b.rx(0.123, qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powRxx(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 2.0, [&](mlir::ValueRange qubits) { + auto [q0, q1] = b.rxx(0.123, qubits[0], qubits[1]); + return llvm::SmallVector{q0, q1}; + }); +} + +void negPowRx(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, -2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.rx(0.123, qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void negPowH(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, -0.5, [&](mlir::ValueRange qubits) { + auto q0 = b.h(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void invPowHFrac(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.inv({q[0]}, [&](mlir::ValueRange invArgs) { + auto inner = b.pow({invArgs[0]}, 0.5, [&](mlir::ValueRange powArgs) { + auto q0 = b.h(powArgs[0]); + return llvm::SmallVector{q0}; + }); + return llvm::SmallVector{inner}; + }); +} + +void powHFracNeg(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, -0.5, [&](mlir::ValueRange qubits) { + auto q0 = b.h(qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void invPowRx(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.inv({q[0]}, [&](mlir::ValueRange invArgs) { + auto inner = b.pow({invArgs[0]}, 2.0, [&](mlir::ValueRange powArgs) { + auto q0 = b.rx(0.123, powArgs[0]); + return llvm::SmallVector{q0}; + }); + return llvm::SmallVector{inner}; + }); +} + +void powRxNeg(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow({q[0]}, 2.0, [&](mlir::ValueRange qubits) { + auto q0 = b.rx(-0.123, qubits[0]); + return llvm::SmallVector{q0}; + }); +} + +void powCtrlRx(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, 2.0, [&](mlir::ValueRange powArgs) { + const auto& [controlsOut, targetsOut] = + b.ctrl({powArgs[0]}, {powArgs[1]}, [&](mlir::ValueRange targets) { + return llvm::SmallVector{b.rx(0.123, targets[0])}; + }); + return llvm::to_vector(llvm::concat(controlsOut, targetsOut)); + }); +} + +void ctrlPowRx(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.ctrl({q[0]}, {q[1]}, [&](mlir::ValueRange targets) { + auto inner = b.pow({targets[0]}, 2.0, [&](mlir::ValueRange powArgs) { + auto q0 = b.rx(0.123, powArgs[0]); + return llvm::SmallVector{q0}; + }); + return llvm::SmallVector{inner}; + }); +} + +void negPowInvIswap(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow({q[0], q[1]}, -2.0, [&](mlir::ValueRange qubits) { + return b.inv({qubits[0], qubits[1]}, [&](mlir::ValueRange invArgs) { + auto [q0, q1] = b.iswap(invArgs[0], invArgs[1]); + return llvm::SmallVector{q0, q1}; + }); + }); +} + +void negPowInvIswapRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.xx_plus_yy(-2.0 * std::numbers::pi, 0.0, q[0], q[1]); +} + +void ctrlPowSx(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.ctrl({q[0]}, {q[1]}, [&](mlir::ValueRange targets) { + auto inner = b.pow({targets[0]}, 1.0 / 3.0, [&](mlir::ValueRange powArgs) { + auto q0 = b.sx(powArgs[0]); + return llvm::SmallVector{q0}; + }); + return llvm::SmallVector{inner}; + }); +} + +void ctrlPowSxRef(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.ctrl({q[0]}, {q[1]}, [&](mlir::ValueRange targets) { + auto inner = b.pow({targets[0]}, 1.0 / 3.0, [&](mlir::ValueRange powArgs) { + auto q0 = b.sx(powArgs[0]); + return llvm::SmallVector{q0}; + }); + return llvm::SmallVector{inner}; + }); +} + void simpleIf(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); auto q0 = b.h(q[0]); diff --git a/mlir/unittests/programs/qco_programs.h b/mlir/unittests/programs/qco_programs.h index b568b4b02f..beda000526 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -117,6 +117,18 @@ void inverseGlobalPhase(QCOProgramBuilder& b); /// phase gate. void inverseMultipleControlledGlobalPhase(QCOProgramBuilder& b); +/// Creates a circuit with pow(3.0) wrapping a global-phase gate (scales θ). +void powGphaseScaled(QCOProgramBuilder& b); + +/// Creates the reference for powGphaseScaled: gphase(3*0.123). +void powGphaseScaledRef(QCOProgramBuilder& b); + +/// Creates a circuit with pow(-3.0) wrapping gphase (negative exponent). +void negPowGphase(QCOProgramBuilder& b); + +/// Reference for negPowGphase: gphase(-3.0 * 0.123). +void negPowGphaseRef(QCOProgramBuilder& b); + // --- IdOp ----------------------------------------------------------------- // /// Creates a circuit with just an identity gate. @@ -141,6 +153,9 @@ void inverseIdentity(QCOProgramBuilder& b); /// gate. void inverseMultipleControlledIdentity(QCOProgramBuilder& b); +/// Creates a circuit with pow(2.0) wrapping id (should pass through). +void powId(QCOProgramBuilder& b); + // --- XOp ------------------------------------------------------------------ // /// Creates a circuit with just an X gate. @@ -189,6 +204,21 @@ void inverseGphaseBarrier(QCOProgramBuilder& b); /// barriers. void inverseTwoBarriersInInv(QCOProgramBuilder& b); +/// Creates a circuit with pow(0.5) wrapping an X gate (folds to gphase + RX). +void powHalfX(QCOProgramBuilder& b); + +/// Creates the reference for powHalfX: sx (X^(1/2) = SX exactly). +void powHalfXRef(QCOProgramBuilder& b); + +/// Creates a circuit with pow(-0.5) wrapping an X gate (r == -0.5 → sxdg). +void powNegHalfX(QCOProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an X gate (general: gphase + rx). +void powThirdX(QCOProgramBuilder& b); + +/// Creates the reference for powThirdX: gphase(-π/6) + rx(π/3). +void powThirdXRef(QCOProgramBuilder& b); + // --- YOp ------------------------------------------------------------------ // /// Creates a circuit with just a Y gate. @@ -215,6 +245,12 @@ void inverseMultipleControlledY(QCOProgramBuilder& b); /// Creates a circuit with two Y gates in a row. void twoY(QCOProgramBuilder& b); +/// Creates a circuit with pow(0.5) wrapping a Y gate (folds to gphase + RY). +void powHalfY(QCOProgramBuilder& b); + +/// Creates the reference for powHalfY: gphase(-π/4) followed by ry(π/2). +void powHalfYRef(QCOProgramBuilder& b); + // --- ZOp ------------------------------------------------------------------ // /// Creates a circuit with just a Z gate. @@ -241,6 +277,19 @@ void inverseMultipleControlledZ(QCOProgramBuilder& b); /// Creates a circuit with two Z gates in a row. void twoZ(QCOProgramBuilder& b); +/// Creates a circuit with pow(0.5) wrapping a Z gate (folds to P(π/2) = S). +void powHalfZ(QCOProgramBuilder& b); + +/// Creates a circuit with pow(1.5) wrapping a Z gate. +/// Exercises normalizeAngle theta -= twoPi (1.5π normalises to -π/2 → sdg). +void powThreeHalvesZ(QCOProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping a Z gate (falls through to P gate). +void powThirdZ(QCOProgramBuilder& b); + +/// Creates the reference for powThirdZ: p(π/3). +void powThirdZRef(QCOProgramBuilder& b); + // --- HOp ------------------------------------------------------------------ // /// Creates a circuit with just an H gate. @@ -270,6 +319,12 @@ void twoH(QCOProgramBuilder& b); /// Creates a circuit with just an H gate and no qubit register. void hWithoutRegister(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an H gate (even hermitian → erase). +void powEvenH(QCOProgramBuilder& b); + +/// Creates a circuit with pow(3) wrapping an H gate (odd hermitian → H). +void powOddH(QCOProgramBuilder& b); + // --- SOp ------------------------------------------------------------------ // /// Creates a circuit with just an S gate. @@ -299,6 +354,23 @@ void sThenSdg(QCOProgramBuilder& b); /// Creates a circuit with two S gates in a row. void twoS(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an S gate (folds to P(π) = Z). +void powTwoS(QCOProgramBuilder& b); + +/// Creates a circuit with pow(4.0) wrapping an S gate. +/// Exercises tryReplaceWithNamedPhaseGate erase path (angle=2π → identity). +void powFourS(QCOProgramBuilder& b); + +/// Creates a circuit with pow(0.5) wrapping an S gate. +/// Exercises tryReplaceWithNamedPhaseGate TOp path (angle=π/4 → t). +void powHalfS(QCOProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an S gate (default: p(π/6)). +void powThirdS(QCOProgramBuilder& b); + +/// Creates the reference for powThirdS: p(π/6). +void powThirdSRef(QCOProgramBuilder& b); + // --- SdgOp ---------------------------------------------------------------- // /// Creates a circuit with just an Sdg gate. @@ -328,6 +400,19 @@ void sdgThenS(QCOProgramBuilder& b); /// Creates a circuit with two Sdg gates in a row. void twoSdg(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an Sdg gate (folds to P(-π) = Z). +void powTwoSdg(QCOProgramBuilder& b); + +/// Creates a circuit with pow(0.5) wrapping an Sdg gate. +/// Exercises tryReplaceWithNamedPhaseGate TdgOp path (angle=-π/4 → tdg). +void powHalfSdg(QCOProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an Sdg gate (default: p(-π/6)). +void powThirdSdg(QCOProgramBuilder& b); + +/// Creates the reference for powThirdSdg: p(-π/6). +void powThirdSdgRef(QCOProgramBuilder& b); + // --- TOp ------------------------------------------------------------------ // /// Creates a circuit with just a T gate. @@ -357,6 +442,15 @@ void tThenTdg(QCOProgramBuilder& b); /// Creates a circuit with two T gates in a row. void twoT(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping a T gate (folds to P(π/2) = S). +void powTwoT(QCOProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping a T gate (default: p(π/12)). +void powThirdT(QCOProgramBuilder& b); + +/// Creates the reference for powThirdT: p(π/12). +void powThirdTRef(QCOProgramBuilder& b); + // --- TdgOp ---------------------------------------------------------------- // /// Creates a circuit with just a Tdg gate. @@ -386,6 +480,15 @@ void tdgThenT(QCOProgramBuilder& b); /// Creates a circuit with two Tdg gates in a row. void twoTdg(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping a Tdg gate (folds to P(-π/2) = Sdg). +void powTwoTdg(QCOProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping a Tdg gate (default: p(-π/12)). +void powThirdTdg(QCOProgramBuilder& b); + +/// Creates the reference for powThirdTdg: p(-π/12). +void powThirdTdgRef(QCOProgramBuilder& b); + // --- SXOp ----------------------------------------------------------------- // /// Creates a circuit with just an SX gate. @@ -415,6 +518,18 @@ void sxThenSxdg(QCOProgramBuilder& b); /// Creates a circuit with two SX gates in a row. void twoSx(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an SX gate (folds to X: SX^2 = X). +void powTwoSx(QCOProgramBuilder& b); + +/// Creates the reference for powTwoSx: x (SX^2 = X exactly). +void powTwoSxRef(QCOProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an SX gate (default: gphase+rx). +void powThirdSx(QCOProgramBuilder& b); + +/// Creates the reference for powThirdSx: gphase(-π/12) + rx(π/6). +void powThirdSxRef(QCOProgramBuilder& b); + // --- SXdgOp --------------------------------------------------------------- // /// Creates a circuit with just an SXdg gate. @@ -445,6 +560,19 @@ void sxdgThenSx(QCOProgramBuilder& b); /// Creates a circuit with two SXdg gates in a row. void twoSxdg(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an SXdg gate (folds to X: SXdg^2 = +/// X). +void powTwoSxdg(QCOProgramBuilder& b); + +/// Creates the reference for powTwoSxdg: x (SXdg^2 = X exactly). +void powTwoSxdgRef(QCOProgramBuilder& b); + +/// Creates a circuit with pow(1/3) wrapping an SXdg gate (default: gphase+rx). +void powThirdSxdg(QCOProgramBuilder& b); + +/// Creates the reference for powThirdSxdg: gphase(π/12) + rx(-π/6). +void powThirdSxdgRef(QCOProgramBuilder& b); + // --- RXOp ----------------------------------------------------------------- // /// Creates a circuit with just an RX gate. @@ -474,6 +602,12 @@ void twoRxOppositePhase(QCOProgramBuilder& b); /// Creates a circuit with an RX gate with an angle of pi/2. void rxPiOver2(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping rx(0.123) (folds to rx(0.246)). +void powRxScaled(QCOProgramBuilder& b); + +/// Creates the reference for powRxScaled: rx(0.246) directly. +void rxScaled(QCOProgramBuilder& b); + // --- RYOp ----------------------------------------------------------------- // /// Creates a circuit with just an RY gate. @@ -578,6 +712,12 @@ void inverseR(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled R gate. void inverseMultipleControlledR(QCOProgramBuilder& b); +/// Creates a circuit with pow(3.0) wrapping an R gate (scales θ, preserves φ). +void powRScaled(QCOProgramBuilder& b); + +/// Creates the reference for powRScaled: r(3*0.123, 0.456). +void powRScaledRef(QCOProgramBuilder& b); + /// Creates a circuit with an R gate that can be canonicalized to an RX gate. void canonicalizeRToRx(QCOProgramBuilder& b); @@ -684,6 +824,12 @@ void twoSwap(QCOProgramBuilder& b); /// Creates a circuit with two SWAP gates in a row with swapped targets. void twoSwapSwappedTargets(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping a SWAP gate (even hermitian → erase). +void powEvenSwap(QCOProgramBuilder& b); + +/// Creates a circuit with pow(3) wrapping a SWAP gate (odd hermitian → SWAP). +void powOddSwap(QCOProgramBuilder& b); + // --- iSWAPOp -------------------------------------------------------------- // /// Creates a circuit with just an iSWAP gate. @@ -708,6 +854,13 @@ void inverseIswap(QCOProgramBuilder& b); /// gate. void inverseMultipleControlledIswap(QCOProgramBuilder& b); +/// Creates a circuit with pow(0.5) wrapping an iSWAP gate (folds to +/// xx_plus_yy(-π/2, 0)). +void powHalfIswap(QCOProgramBuilder& b); + +/// Creates the reference for powHalfIswap: xx_plus_yy(-π/2, 0) directly. +void powHalfIswapRef(QCOProgramBuilder& b); + // --- DCXOp ---------------------------------------------------------------- // /// Creates a circuit with just a DCX gate. @@ -763,6 +916,12 @@ void inverseMultipleControlledEcr(QCOProgramBuilder& b); /// Creates a circuit with two ECR gates in a row. void twoEcr(QCOProgramBuilder& b); +/// Creates a circuit with pow(2) wrapping an ECR gate (even hermitian → erase). +void powEvenEcr(QCOProgramBuilder& b); + +/// Creates a circuit with pow(3) wrapping an ECR gate (odd hermitian → ECR). +void powOddEcr(QCOProgramBuilder& b); + // --- RXXOp ---------------------------------------------------------------- // /// Creates a circuit with just an RXX gate. @@ -927,6 +1086,12 @@ void inverseXxPlusYY(QCOProgramBuilder& b); /// gate. void inverseMultipleControlledXxPlusYY(QCOProgramBuilder& b); +/// Creates a circuit with pow(3.0) wrapping an XX+YY gate (scales θ). +void powXxPlusYYScaled(QCOProgramBuilder& b); + +/// Creates the reference for powXxPlusYYScaled: xx_plus_yy(3*0.123, 0.456). +void powXxPlusYYScaledRef(QCOProgramBuilder& b); + /// Creates a circuit with two XXPlusYY gates in a row with opposite phases. void twoXxPlusYYOppositePhase(QCOProgramBuilder& b); @@ -957,6 +1122,12 @@ void inverseXxMinusYY(QCOProgramBuilder& b); /// gate. void inverseMultipleControlledXxMinusYY(QCOProgramBuilder& b); +/// Creates a circuit with pow(3.0) wrapping an XX-YY gate (scales θ). +void powXxMinusYYScaled(QCOProgramBuilder& b); + +/// Creates the reference for powXxMinusYYScaled: xx_minus_yy(3*0.123, 0.456). +void powXxMinusYYScaledRef(QCOProgramBuilder& b); + /// Creates a circuit with two XXMinusYY gates in a row with opposite phases. void twoXxMinusYYOppositePhase(QCOProgramBuilder& b); @@ -980,6 +1151,9 @@ void singleControlledBarrier(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a barrier. void inverseBarrier(QCOProgramBuilder& b); +/// Creates a circuit with pow(2.0) wrapping barrier (should pass through). +void powBarrier(QCOProgramBuilder& b); + /// Creates a circuit with two barriers in a row with overlapping qubits. void twoBarrier(QCOProgramBuilder& b); @@ -1034,10 +1208,77 @@ void invCtrlSandwich(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to two gates. void invTwo(QCOProgramBuilder& b); +/// Creates a circuit with a power modifier applied to two gates. +void powTwo(QCOProgramBuilder& b); + /// Creates a circuit with an inverse modifier applied to a control modifier /// applied to two gates. void invCtrlTwo(QCOProgramBuilder& b); +// --- PowOp ---------------------------------------------------------------- // + +/// Creates a circuit with pow(1.0) modifier (should inline to just the gate). +void pow1Inline(QCOProgramBuilder& b); + +/// Creates a circuit with pow(0.0) modifier (should erase to identity). +void pow0Erase(QCOProgramBuilder& b); + +/// Creates a circuit with nested pow modifiers (should merge exponents). +void nestedPow(QCOProgramBuilder& b); + +/// Creates a circuit with pow(6.0) as the merged reference for nestedPow. +void powSingleExponent(QCOProgramBuilder& b); + +/// Creates a circuit with pow(2.0) wrapping a two-qubit RXX gate. +void powRxx(QCOProgramBuilder& b); + +/// Creates a circuit with pow(-2.0) wrapping an RX gate (negative exponent). +void negPowRx(QCOProgramBuilder& b); + +/// Creates a circuit with pow(2.0) wrapping RX(-0.123) (reference for +/// negPowRx and invPowRx — inv folds into angle negation). +void powRxNeg(QCOProgramBuilder& b); + +/// Creates a circuit with pow(-0.5) wrapping H (negative non-integer exponent). +/// Expected to remain unchanged: fractional exponent on a unitary with +/// eigenvalue -1 cannot safely apply NegPowToInvPow. +void negPowH(QCOProgramBuilder& b); + +/// Creates a circuit with inv wrapping pow(0.5) wrapping H. +/// MovePowOutside emits pow(-0.5){H} (not wrapping in inv). +void invPowHFrac(QCOProgramBuilder& b); + +/// Creates a circuit with pow(-0.5) wrapping H (reference for invPowHFrac). +void powHFracNeg(QCOProgramBuilder& b); + +/// Creates a circuit with inv wrapping pow (should reorder to pow wrapping +/// inv). +void invPowRx(QCOProgramBuilder& b); + +/// Creates a circuit with pow wrapping ctrl wrapping RX (should move ctrl +/// outside). +void powCtrlRx(QCOProgramBuilder& b); + +/// Creates a circuit with ctrl wrapping pow wrapping RX (reference for +/// powCtrlRx). +void ctrlPowRx(QCOProgramBuilder& b); + +/// Creates a circuit with pow(-2) wrapping inv wrapping iSWAP. +/// Exercises NegPowToInvPow: inv{iswap} survives InvOp canonicalization, +/// FoldPowIntoGate fails (inner is InvOp), so NegPowToInvPow fires. +void negPowInvIswap(QCOProgramBuilder& b); + +/// Reference for negPowInvIswap: xx_plus_yy(-2π, 0) (the fully folded form). +void negPowInvIswapRef(QCOProgramBuilder& b); + +/// Creates a circuit with ctrl wrapping pow(1/3) wrapping SX. The fold +/// pow(p){SX} → gphase+rx is suppressed inside ctrl (would emit two ops), +/// so the pow survives canonicalization and reaches ConvertQCOPowOp. +void ctrlPowSx(QCOProgramBuilder& b); + +/// Reference for ctrlPowSx: identical circuit (pow is blocked inside ctrl). +void ctrlPowSxRef(QCOProgramBuilder& b); + // --- IfOp ---------------------------------------------------------------- // /// Creates a circuit with a simple if operation with one qubit.