diff --git a/CHANGELOG.md b/CHANGELOG.md index dad0d3abf3..48dd3f2b3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,8 @@ This project adheres to [Semantic Versioning], with the exception that minor rel - ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637], [#1676], [#1706]) ([**@denialhaag**], [**@burgholzer**]) - ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748]) ([**@MatthiasReumann**], [**@burgholzer**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects - ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], [#1728], [#1730], [#1749]) - ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) + ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1603], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], [#1728], [#1730], [#1749]) + ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**], [**@J4MMlE**]) ### Changed @@ -434,6 +434,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1623]: https://github.com/munich-quantum-toolkit/core/pull/1623 [#1620]: https://github.com/munich-quantum-toolkit/core/pull/1620 [#1605]: https://github.com/munich-quantum-toolkit/core/pull/1605 +[#1603]: https://github.com/munich-quantum-toolkit/core/pull/1603 [#1602]: https://github.com/munich-quantum-toolkit/core/pull/1602 [#1600]: https://github.com/munich-quantum-toolkit/core/pull/1600 [#1596]: https://github.com/munich-quantum-toolkit/core/pull/1596 diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index ab9532cfb4..cefde2ac57 100644 --- a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -938,6 +938,27 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { */ QCProgramBuilder& inv(const function_ref& body); + /** + * @brief Apply a power operation. + * + * @param exponent The exponent to raise the operation to + * @param body Function that builds the body containing the operation to + * exponentiate + * @return Reference to this builder for method chaining + * + * @par Example: + * ```c++ + * builder.pow(2.0, [&] { builder.s(q0); }); + * ``` + * ```mlir + * qc.pow(2.000000e+00) { + * qc.s %q0 : !qc.qubit + * } + * ``` + */ + QCProgramBuilder& pow(const std::variant& exponent, + 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 8e76c9c3ba..80fdf678b7 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -1008,4 +1008,53 @@ def InvOp : QCOp<"inv", let hasVerifier = 1; } +def PowOp : QCOp<"pow", + traits = [UnitaryOpInterface, + SingleBlockImplicitTerminator<"::mlir::qc::YieldOp">, + RecursiveMemoryEffects]> { + let summary = "Apply a gate power modifier"; + let description = [{ + A modifier operation that raises the unitary operation defined in its + body region to a given power (exponent can be integer or fractional). + + - r > 0: apply the gate raised to the r-th power. + - r = 0: identity (no-op). + - r < 0: equivalent to inv @ pow(-r) @ g. + + Example: + ```mlir + qc.pow(2.000000e+00) { + qc.s %q0 : !qc.qubit + } + ``` + }]; + + let arguments = (ins F64:$exponent); + let regions = (region SizedRegion<1>:$region); + let assemblyFormat = "`(` $exponent `)` $region attr-dict"; + + let extraClassDeclaration = [{ + [[nodiscard]] UnitaryOpInterface getBodyUnitary(); + size_t getNumQubits() { return getBodyUnitary().getNumQubits(); } + size_t getNumTargets() { return getBodyUnitary().getNumTargets(); } + size_t getNumControls() { return getBodyUnitary().getNumControls(); } + Value getQubit(size_t i) { return getBodyUnitary().getQubit(i); } + Value getTarget(size_t i) { return getBodyUnitary().getTarget(i); } + ValueRange getTargets() { return getBodyUnitary().getTargets(); } + Value getControl(size_t i) { return getBodyUnitary().getControl(i); } + ValueRange getControls() { return getBodyUnitary().getControls(); } + size_t getNumParams() { return getBodyUnitary().getNumParams(); } + Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } + ValueRange getParameters() { return getBodyUnitary().getParameters(); } + double getExponentValue(); + static StringRef getBaseSymbol() { return "pow"; } + }]; + + let builders = [OpBuilder<(ins "const std::variant&":$exponent, + "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 772ea1eba2..2b118224e9 100644 --- a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -1186,6 +1186,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 a5bbfb7f51..8e7f4b175d 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -1209,6 +1209,77 @@ def InvOp let hasVerifier = 1; } +def PowOp + : QCOOp<"pow", + traits = [UnitaryOpInterface, + SingleBlockImplicitTerminator<"::mlir::qco::YieldOp">, + RecursiveMemoryEffects]> { + let summary = "Raise a unitary operation to a power"; + let description = [{ + A modifier operation that raises the unitary operation defined in its body + region to the given floating-point exponent. The operation takes a variadic + number of qubits as inputs and produces corresponding output qubits. + + Example: + ```mlir + %q_out = qco.pow (0.5) (%q = %q_in) { + %q_1 = qco.s %q : !qco.qubit -> !qco.qubit + qco.yield %q_1 + } : {!qco.qubit} -> {!qco.qubit} + ``` + }]; + + let arguments = (ins F64:$exponent, + Arg, + "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 = [{ + UnitaryOpInterface getBodyUnitary(); + size_t getNumQubits() { return getNumTargets(); } + size_t getNumTargets() { return getQubitsIn().size(); } + static size_t getNumControls() { return 0; } + Value getInputQubit(size_t i); + OperandRange getInputQubits() { return getQubitsIn(); } + Value getOutputQubit(size_t i); + ResultRange getOutputQubits() { return getResults(); } + Value getInputTarget(size_t i) { return getInputQubit(i); } + OperandRange getInputTargets() { return getInputQubits(); } + Value getOutputTarget(size_t i) { return getOutputQubit(i); } + ResultRange getOutputTargets() { return getOutputQubits(); } + static Value getInputControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } + static OperandRange getInputControls() { return {nullptr, 0}; } + static Value getOutputControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } + static ResultRange getOutputControls() { return {nullptr, 0}; } + Value getInputForOutput(Value output); + Value getOutputForInput(Value input); + size_t getNumParams() { return getBodyUnitary().getNumParams(); } + Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } + ValueRange getParameters() { return getBodyUnitary().getParameters(); } + double getExponentValue(); + [[nodiscard]] static StringRef getBaseSymbol() { return "pow"; } + [[nodiscard]] std::optional 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 3d976a5a63..e33c5d21c2 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -15,10 +15,36 @@ #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; +} + constexpr auto TOLERANCE = 1e-15; inline Value constantFromScalar(OpBuilder& builder, Location loc, double v) { diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 97d7071f69..4687489ccd 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -700,6 +700,64 @@ 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) { + * qc.s %q0 : !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 + auto qcOp = qc::PowOp::create(rewriter, op.getLoc(), adaptor.getExponent()); + + // Clone body region from QCO to QC + auto& dstRegion = qcOp.getRegion(); + rewriter.cloneRegionBefore(op.getRegion(), dstRegion, dstRegion.end()); + + auto& entryBlock = dstRegion.front(); + const auto numArgs = entryBlock.getNumArguments(); + if (adaptor.getQubitsIn().size() != numArgs) { + return op.emitOpError() << "qco.pow: entry block args (" << numArgs + << ") must match number of target operands (" + << adaptor.getQubitsIn().size() << ")"; + } + + // Remove all block arguments in the cloned region + rewriter.modifyOpInPlace(qcOp, [&] { + // 1. Replace uses (Must be done BEFORE erasing) + for (auto i = 0UL; i < numArgs; ++i) { + entryBlock.getArgument(i).replaceAllUsesWith(adaptor.getQubitsIn()[i]); + } + + // 2. Erase all block arguments + if (numArgs > 0) { + entryBlock.eraseArguments(0, numArgs); + } + }); + + // Replace the output qubits with the same QC references + rewriter.replaceOp(op, adaptor.getQubitsIn()); + + return success(); + } +}; + /** * @brief Converts qco.yield to qc.yield or to scf.yield if the parent is a * scf::IfOp @@ -1002,9 +1060,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) { + * qc.s %q0 : !qc.qubit + * } + * ``` + * is converted to + * ```mlir + * %q0_out = qco.pow (2.000000e+00) (%a0_in = %q0_in) { + * %a0_res = qco.s %a0_in : !qco.qubit -> !qco.qubit + * qco.yield %a0_res + * } : {!qco.qubit} -> {!qco.qubit} + * ``` + */ +struct ConvertQCPowOp final : StatefulOpConversionPattern { + using StatefulOpConversionPattern::StatefulOpConversionPattern; + + LogicalResult + matchAndRewrite(qc::PowOp op, OpAdaptor /*adaptor*/, + ConversionPatternRewriter& rewriter) const override { + auto& state = getState(); + auto* operation = op.getOperation(); + const auto numTargets = op.getNumTargets(); + const auto qcTargets = op.getTargets(); + auto qcoTargets = resolveMappedQubits(state, operation, qcTargets); + + // Create qco.pow with exponent + const double exponent = op.getExponentValue(); + auto qcoOp = + qco::PowOp::create(rewriter, op.getLoc(), qcoTargets, exponent); + + assignMappedQubits(state, operation, qcTargets, qcoOp.getQubitsOut()); + + // Clone body region from QC to QCO + auto& dstRegion = qcoOp.getRegion(); + rewriter.cloneRegionBefore(op.getRegion(), dstRegion, dstRegion.end()); + + // Create block arguments for target qubits and seed the nested frame. + auto& entryBlock = dstRegion.front(); + assert(entryBlock.getNumArguments() == 0 && + "QC pow region unexpectedly has entry block arguments"); + pushModifierFrame(state, qcTargets, + addModifierAliases(qcoOp, numTargets, rewriter)); + + rewriter.eraseOp(op); + return success(); + } +}; + /** * @brief Converts qc.yield to qco.yield * @@ -1626,8 +1678,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 5eb022c03a..1d74fd872b 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -491,6 +491,14 @@ QCProgramBuilder& QCProgramBuilder::inv(const function_ref& body) { return *this; } +QCProgramBuilder& +QCProgramBuilder::pow(const std::variant& exponent, + const function_ref& body) { + checkFinalized(); + PowOp::create(*this, exponent, 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 065fe431be..c95d052245 100644 --- a/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp @@ -52,6 +52,32 @@ 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 innerPow = dyn_cast(invOp.getBodyUnitary().getOperation()); + if (!innerPow) { + return failure(); + } + const double exponent = innerPow.getExponentValue(); + rewriter.replaceOpWithNewOp(invOp, -exponent, [&] { + auto* powBody = rewriter.getInsertionBlock(); + rewriter.inlineBlockBefore(innerPow.getBody(), powBody, powBody->begin()); + rewriter.eraseOp(&powBody->back()); // erase the inlined YieldOp + }); + return success(); + } +}; + /** * @brief Remove inverse modifiers around self-adjoint gates. * @@ -295,6 +321,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..5cb6ac03ba --- /dev/null +++ b/mlir/lib/Dialect/QC/IR/Modifiers/PowOp.cpp @@ -0,0 +1,541 @@ +/* + * 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/QCOps.h" +#include "mlir/Dialect/Utils/Utils.h" + +#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* innerOp = op.getBodyUnitary().getOperation(); + rewriter.inlineBlockBefore(op.getBody(), op, {}); + rewriter.eraseOp(op->getPrevNode()); // erase the now-inlined YieldOp + rewriter.replaceOp(op, innerOp->getResults()); + 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(); + } + if (isa(op->getParentOp())) { + if (op.getNumTargets() == 1) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + } else { + return failure(); + } + } else { + rewriter.eraseOp(op); + } + 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, -exp, [&] { + InvOp::create(rewriter, op.getLoc(), [&] { + rewriter.clone(*op.getBodyUnitary().getOperation()); + }); + }); + 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 innerPow = dyn_cast(op.getBodyUnitary().getOperation()); + if (!innerPow) { + return failure(); + } + rewriter.replaceOpWithNewOp( + op, op.getExponentValue() * innerPow.getExponentValue(), + [&] { rewriter.clone(*innerPow.getBodyUnitary().getOperation()); }); + 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 innerCtrl = dyn_cast(op.getBodyUnitary().getOperation()); + if (!innerCtrl) { + return failure(); + } + rewriter.replaceOpWithNewOp(op, innerCtrl.getControls(), [&] { + PowOp::create(rewriter, op.getLoc(), op.getExponentValue(), [&] { + rewriter.clone(*innerCtrl.getBodyUnitary().getOperation()); + }); + }); + 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* innerOp = op.getBodyUnitary().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 gate) { + if (!utils::isIntegerExponent(r)) { + return failure(); + } + if (utils::isEvenExponent(r)) { + if (insideModifier) { + rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + } else { + rewriter.eraseOp(op); + } + } else { + rewriter.moveOpBefore(gate, op); + rewriter.eraseOp(op); + } + return success(); + }) + // pow(n) { ecr/swap } => erase (n even) | ecr/swap (n odd) + .Case([&](auto gate) { + if (!utils::isIntegerExponent(r)) { + return failure(); + } + if (utils::isEvenExponent(r)) { + if (insideModifier) { + return failure(); + } + rewriter.eraseOp(op); + } else { + rewriter.moveOpBefore(gate, op); + rewriter.eraseOp(op); + } + 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 gate) { + rewriter.replaceOpWithNewOp(op, gate.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(); +} + +UnitaryOpInterface PowOp::getBodyUnitary() { + return cast(*(++getBody()->rbegin())); +} + +void PowOp::build(OpBuilder& odsBuilder, OperationState& odsState, + const std::variant& exponent, + const function_ref& bodyBuilder) { + auto expValue = variantToValue(odsBuilder, odsState.location, exponent); + odsState.addOperands(expValue); + auto* region = odsState.addRegion(); + auto& block = region->emplaceBlock(); + + const OpBuilder::InsertionGuard guard(odsBuilder); + odsBuilder.setInsertionPointToStart(&block); + bodyBuilder(); + YieldOp::create(odsBuilder, odsState.location); +} + +LogicalResult PowOp::verify() { + auto& block = *getBody(); + if (block.getOperations().size() < 2) { + return emitOpError("body region must have at least two operations"); + } + if (!isa(block.back())) { + return emitOpError( + "last operation in body region must be a yield operation"); + } + auto iter = ++block.rbegin(); + if (!isa(*iter)) { + return emitOpError( + "second to last operation in body region must be a unitary operation"); + } + for (auto it = ++iter; it != block.rend(); ++it) { + if (isa(*it)) { + return emitOpError("body region may only contain a single unitary op"); + } + } + 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 a07c52aa0f..3000741d77 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 @@ -874,6 +873,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 d82a64f819..9b72077a3a 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -78,6 +79,40 @@ 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 innerPow = dyn_cast(invOp.getBodyUnitary().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. * @@ -397,8 +432,8 @@ LogicalResult InvOp::verify() { void InvOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } std::optional InvOp::getUnitaryMatrix() { 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..e0cef14c56 --- /dev/null +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/PowOp.cpp @@ -0,0 +1,731 @@ +/* + * 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/Utils/Utils.h" + +#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(); + } + + auto* innerOp = op.getBodyUnitary().getOperation(); + rewriter.inlineBlockBefore(op.getBody(), op, op.getInputQubits()); + rewriter.eraseOp(op->getPrevNode()); // erase the now-inlined YieldOp + rewriter.replaceOp(op, innerOp->getResults()); + 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 innerPow = dyn_cast(op.getBodyUnitary().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 = op.getBodyUnitary(); + 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* innerOp = op.getBodyUnitary().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(); +} + +UnitaryOpInterface PowOp::getBodyUnitary() { + // In principle, the body region should only contain exactly two operations, + // the actual unitary operation and a yield operation. However, the region may + // also contain constants and arithmetic operations, e.g., created as part of + // canonicalization. Thus, the only safe way to access the unitary operation + // is to get the second operation from the back of the region. + return cast(*(++getBody()->rbegin())); +} + +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() { + auto& block = *getBody(); + if (block.getOperations().size() < 2) { + return emitOpError("body region must have at least two operations"); + } + 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; + } + auto iter = ++block.rbegin(); + if (!isa(*iter)) { + return emitOpError( + "second to last operation in body region must be a unitary operation"); + } + for (auto it = ++iter; it != block.rend(); ++it) { + if (isa(*it)) { + return emitOpError("body region may only contain a single unitary op"); + } + } + + auto bodyUnitary = getBodyUnitary(); + if (bodyUnitary.getNumQubits() != numTargets) { + return emitOpError("body unitary must operate on exactly ") + << numTargets << " target qubits, but found " + << bodyUnitary.getNumQubits(); + } + const auto numQubits = bodyUnitary.getNumQubits(); + for (size_t i = 0; i < numQubits; i++) { + if (bodyUnitary.getInputQubit(i) != block.getArgument(i)) { + return emitOpError("body unitary must use target alias block argument ") + << i << " (and not the original target operand)"; + } + } + + // Also require yield to forward the unitary's outputs in-order. + for (size_t i = 0; i < numTargets; ++i) { + if (block.back().getOperand(i) != bodyUnitary.getOutputQubit(i)) { + return emitOpError("yield operand ") + << i << " must be the body unitary output qubit " << i; + } + } + + return success(); +} + +void PowOp::getCanonicalizationPatterns(RewritePatternSet& results, + MLIRContext* context) { + results.add(context); +} + +std::optional PowOp::getUnitaryMatrix() { + auto&& bodyUnitary = getBodyUnitary(); + 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) { + const auto dim = targetMatrix->cols(); + return Eigen::MatrixXcd::Identity(dim, dim); + } + + // General case: eigendecomposition U = V D V† => U^p = V D^p V† + const Eigen::ComplexEigenSolver solver(*targetMatrix); + if (solver.info() != Eigen::Success) { + return std::nullopt; + } + + const auto& eigenvalues = solver.eigenvalues(); + const auto& v = solver.eigenvectors(); + + // Compute D^p: raise each eigenvalue to the power p + Eigen::VectorXcd powEigenvalues(eigenvalues.size()); + for (Eigen::Index i = 0; i < eigenvalues.size(); ++i) { + powEigenvalues[i] = std::pow(eigenvalues[i], p); + } + + return v * powEigenvalues.asDiagonal() * v.inverse(); +} diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index 4bd2b24615..6b798da2a4 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -144,6 +144,14 @@ 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)})); +/// @} + /// \name QCOToQC/Modifiers/InvOp.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 3f0df25542..c0f97352aa 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -143,6 +143,14 @@ 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)})); +/// @} + /// \name QCToQCO/Modifiers/InvOp.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 97e4627363..7dc3dfe9be 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -132,6 +132,31 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(fourControlledRxx)})); /// @} +/// \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{"NestedPow", MQT_NAMED_BUILDER(nestedPow), + MQT_NAMED_BUILDER(powSingleExponent)}, + QCTestCase{"PowRxx", MQT_NAMED_BUILDER(powRxx), + MQT_NAMED_BUILDER(powRxx)}, + QCTestCase{"NegPowRx", MQT_NAMED_BUILDER(negPowRx), + MQT_NAMED_BUILDER(powRxNeg)}, + QCTestCase{"NegPowH", MQT_NAMED_BUILDER(negPowH), + MQT_NAMED_BUILDER(negPowH)}, + 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)})); +/// @} + /// \name QC/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( @@ -208,6 +233,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)})); /// @} @@ -258,7 +285,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 @@ -284,7 +315,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 @@ -306,7 +341,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 @@ -332,6 +371,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)})); /// @} @@ -357,7 +398,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 @@ -401,7 +444,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 @@ -424,7 +469,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 @@ -592,7 +639,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 @@ -617,7 +671,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 @@ -642,7 +702,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 @@ -665,32 +729,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 @@ -712,7 +784,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 @@ -737,7 +812,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 @@ -804,7 +883,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 @@ -830,7 +915,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 @@ -856,7 +943,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 @@ -878,7 +967,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 @@ -900,7 +991,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 413f29336d..276ad59179 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -250,6 +250,31 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(singleControlledRxx)})); /// @} +/// \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{"PowRxx", MQT_NAMED_BUILDER(powRxx), + MQT_NAMED_BUILDER(powRxx)}, + QCOTestCase{"NegPowRx", MQT_NAMED_BUILDER(negPowRx), + MQT_NAMED_BUILDER(powRxNeg)}, + QCOTestCase{"NegPowH", MQT_NAMED_BUILDER(negPowH), + MQT_NAMED_BUILDER(negPowH)}, + 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)})); +/// @} + /// \name QCO/Operations/StandardGates/BarrierOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( @@ -269,7 +294,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 @@ -326,7 +353,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 @@ -346,7 +377,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 @@ -370,7 +405,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 @@ -396,7 +435,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 @@ -422,7 +463,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 @@ -472,7 +515,9 @@ INSTANTIATE_TEST_SUITE_P( QCOTestCase{"CanonicalizeRToRx", MQT_NAMED_BUILDER(canonicalizeRToRx), MQT_NAMED_BUILDER(rx)}, QCOTestCase{"CanonicalizeRToRy", MQT_NAMED_BUILDER(canonicalizeRToRy), - MQT_NAMED_BUILDER(ry)})); + MQT_NAMED_BUILDER(ry)}, + QCOTestCase{"PowRScaled", MQT_NAMED_BUILDER(powRScaled), + MQT_NAMED_BUILDER(powRScaledRef)})); /// @} /// \name QCO/Operations/StandardGates/RxOp.cpp @@ -497,7 +542,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 @@ -708,36 +755,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 @@ -767,7 +827,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 @@ -793,7 +857,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 @@ -822,7 +890,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 @@ -847,36 +919,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 @@ -959,7 +1039,13 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledX), MQT_NAMED_BUILDER(multipleControlledX)}, QCOTestCase{"TwoX", MQT_NAMED_BUILDER(twoX), - 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 @@ -988,7 +1074,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(multipleControlledXxMinusYY)}, QCOTestCase{"TwoXXMinusYYOppositePhase", MQT_NAMED_BUILDER(twoXxMinusYYOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowXxMinusYYScaled", MQT_NAMED_BUILDER(powXxMinusYYScaled), + MQT_NAMED_BUILDER(powXxMinusYYScaledRef)})); /// @} /// \name QCO/Operations/StandardGates/XxPlusYyOp.cpp @@ -1017,7 +1105,9 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(multipleControlledXxPlusYY)}, QCOTestCase{"TwoXXPlusYYOppositePhase", MQT_NAMED_BUILDER(twoXxPlusYYOppositePhase), - MQT_NAMED_BUILDER(emptyQCO)})); + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"PowXxPlusYYScaled", MQT_NAMED_BUILDER(powXxPlusYYScaled), + MQT_NAMED_BUILDER(powXxPlusYYScaledRef)})); /// @} /// \name QCO/Operations/StandardGates/YOp.cpp @@ -1041,7 +1131,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 @@ -1065,7 +1157,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 24103da18c..d9f942dd86 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir_matrix.cpp @@ -86,6 +86,62 @@ 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 = RXX(2 * 0.123) = RXX(0.246) + const auto definition = dd::opToTwoQubitGateMatrix(qc::OpType::RXX, {0.246}); + Eigen::Matrix4cd eigenDefinition; + eigenDefinition << definition[0][0], definition[0][1], definition[0][2], + definition[0][3], definition[1][0], definition[1][1], definition[1][2], + definition[1][3], definition[2][0], definition[2][1], definition[2][2], + definition[2][3], definition[3][0], definition[3][1], definition[3][2], + definition[3][3]; + + ASSERT_TRUE(matrix->isApprox(eigenDefinition)); +} + +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 = SX + const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::SX); + Eigen::Matrix2cd eigenDefinition; + eigenDefinition << definition[0], definition[1], definition[2], definition[3]; + + ASSERT_TRUE(matrix->isApprox(eigenDefinition)); +} + +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 = SXdg + const auto definition = dd::opToSingleQubitGateMatrix(qc::OpType::SXdg); + Eigen::Matrix2cd eigenDefinition; + eigenDefinition << definition[0], definition[1], definition[2], definition[3]; + + ASSERT_TRUE(matrix->isApprox(eigenDefinition)); +} +/// @} + /// \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 373452252a..928ad659d6 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -211,6 +211,18 @@ void inverseMultipleControlledGlobalPhase(QCProgramBuilder& b) { b.inv([&]() { b.mcgphase(-0.123, {q[0], q[1], q[2]}); }); } +void powGphaseScaled(QCProgramBuilder& b) { + b.pow(3.0, [&] { b.gphase(0.123); }); +} + +void powGphaseScaledRef(QCProgramBuilder& b) { b.gphase(3.0 * 0.123); } + +void negPowGphase(QCProgramBuilder& b) { + b.pow(-3.0, [&] { 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]); @@ -246,6 +258,11 @@ void inverseMultipleControlledIdentity(QCProgramBuilder& b) { b.inv([&]() { b.mcid({q[2], q[1]}, q[0]); }); } +void powId(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.id(q[0]); }); +} + void x(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.x(q[0]); @@ -290,6 +307,32 @@ void inverseMultipleControlledX(QCProgramBuilder& b) { b.inv([&]() { b.mcx({q[0], q[1]}, q[2]); }); } +void powHalfX(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, [&] { b.x(q[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, [&] { b.x(q[0]); }); +} + +void powThirdX(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, [&] { b.x(q[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]); @@ -325,6 +368,17 @@ void inverseMultipleControlledY(QCProgramBuilder& b) { b.inv([&]() { b.mcy({q[0], q[1]}, q[2]); }); } +void powHalfY(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, [&] { b.y(q[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]); @@ -360,6 +414,26 @@ void inverseMultipleControlledZ(QCProgramBuilder& b) { b.inv([&]() { b.mcz({q[0], q[1]}, q[2]); }); } +void powHalfZ(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, [&] { b.z(q[0]); }); +} + +void powThreeHalvesZ(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.5, [&] { b.z(q[0]); }); +} + +void powThirdZ(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, [&] { b.z(q[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]); @@ -400,6 +474,16 @@ void hWithoutRegister(QCProgramBuilder& b) { b.h(q); } +void powEvenH(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.h(q[0]); }); +} + +void powOddH(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(3.0, [&] { b.h(q[0]); }); +} + void s(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.s(q[0]); @@ -435,6 +519,31 @@ void inverseMultipleControlledS(QCProgramBuilder& b) { b.inv([&]() { b.mcs({q[0], q[1]}, q[2]); }); } +void powTwoS(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.s(q[0]); }); +} + +void powFourS(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(4.0, [&] { b.s(q[0]); }); +} + +void powHalfS(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, [&] { b.s(q[0]); }); +} + +void powThirdS(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, [&] { b.s(q[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]); @@ -470,6 +579,26 @@ void inverseMultipleControlledSdg(QCProgramBuilder& b) { b.inv([&]() { b.mcsdg({q[0], q[1]}, q[2]); }); } +void powTwoSdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.sdg(q[0]); }); +} + +void powHalfSdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.5, [&] { b.sdg(q[0]); }); +} + +void powThirdSdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, [&] { b.sdg(q[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]); @@ -505,6 +634,21 @@ void inverseMultipleControlledT(QCProgramBuilder& b) { b.inv([&]() { b.mct({q[0], q[1]}, q[2]); }); } +void powTwoT(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.t(q[0]); }); +} + +void powThirdT(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, [&] { b.t(q[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]); @@ -540,6 +684,21 @@ void inverseMultipleControlledTdg(QCProgramBuilder& b) { b.inv([&]() { b.mctdg({q[0], q[1]}, q[2]); }); } +void powTwoTdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.tdg(q[0]); }); +} + +void powThirdTdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0 / 3.0, [&] { b.tdg(q[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]); @@ -575,6 +734,27 @@ void inverseMultipleControlledSx(QCProgramBuilder& b) { b.inv([&]() { b.mcsx({q[0], q[1]}, q[2]); }); } +void powTwoSx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.sx(q[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, [&] { b.sx(q[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]); @@ -610,6 +790,27 @@ void inverseMultipleControlledSxdg(QCProgramBuilder& b) { b.inv([&]() { b.mcsxdg({q[0], q[1]}, q[2]); }); } +void powTwoSxdg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.sxdg(q[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, [&] { b.sxdg(q[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]); @@ -645,6 +846,16 @@ void inverseMultipleControlledRx(QCProgramBuilder& b) { b.inv([&]() { b.mcrx(-0.123, {q[0], q[1]}, q[2]); }); } +void powRxScaled(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.rx(0.123, q[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]); @@ -785,6 +996,16 @@ void inverseMultipleControlledR(QCProgramBuilder& b) { b.inv([&]() { b.mcr(-0.123, 0.456, {q[0], q[1]}, q[2]); }); } +void powRScaled(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(3.0, [&] { b.r(0.123, 0.456, q[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]); @@ -892,6 +1113,16 @@ void inverseMultipleControlledSwap(QCProgramBuilder& b) { b.inv([&]() { b.mcswap({q[0], q[1]}, q[2], q[3]); }); } +void powEvenSwap(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(2.0, [&] { b.swap(q[0], q[1]); }); +} + +void powOddSwap(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(3.0, [&] { b.swap(q[0], q[1]); }); +} + void iswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.iswap(q[0], q[1]); @@ -927,6 +1158,16 @@ void inverseMultipleControlledIswap(QCProgramBuilder& b) { b.inv([&]() { b.mciswap({q[0], q[1]}, q[2], q[3]); }); } +void powHalfIswap(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(0.5, [&] { b.iswap(q[0], q[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]); @@ -997,6 +1238,16 @@ void inverseMultipleControlledEcr(QCProgramBuilder& b) { b.inv([&]() { b.mcecr({q[0], q[1]}, q[2], q[3]); }); } +void powEvenEcr(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(2.0, [&] { b.ecr(q[0], q[1]); }); +} + +void powOddEcr(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(3.0, [&] { b.ecr(q[0], q[1]); }); +} + void rxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.rxx(0.123, q[0], q[1]); @@ -1181,6 +1432,16 @@ void inverseMultipleControlledXxPlusYY(QCProgramBuilder& b) { b.inv([&]() { b.mcxx_plus_yy(-0.123, 0.456, {q[0], q[1]}, q[2], q[3]); }); } +void powXxPlusYYScaled(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(3.0, [&] { b.xx_plus_yy(0.123, 0.456, q[0], q[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]); @@ -1216,6 +1477,16 @@ void inverseMultipleControlledXxMinusYY(QCProgramBuilder& b) { b.inv([&]() { b.mcxx_minus_yy(-0.123, 0.456, {q[0], q[1]}, q[2], q[3]); }); } +void powXxMinusYYScaled(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(3.0, [&] { b.xx_minus_yy(0.123, 0.456, q[0], q[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]); @@ -1241,6 +1512,11 @@ void inverseBarrier(QCProgramBuilder& b) { b.inv([&]() { b.barrier(q[0]); }); } +void powBarrier(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.barrier(q[0]); }); +} + void trivialCtrl(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.ctrl({}, [&]() { b.rxx(0.123, q[0], q[1]); }); @@ -1289,6 +1565,76 @@ void invCtrlSandwich(QCProgramBuilder& b) { }); } +void pow1Inline(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(1.0, [&] { b.rx(0.123, q[0]); }); +} + +void pow0Erase(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(0.0, [&] { b.rx(0.123, q[0]); }); +} + +void nestedPow(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(3.0, [&] { b.pow(2.0, [&] { b.rx(0.123, q[0]); }); }); +} + +void powSingleExponent(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(6.0, [&] { b.rx(0.123, q[0]); }); +} + +void powRxx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(2.0, [&] { b.rxx(0.123, q[0], q[1]); }); +} + +void negPowRx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(-2.0, [&] { b.rx(0.123, q[0]); }); +} + +void powRxNeg(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(2.0, [&] { b.rx(-0.123, q[0]); }); +} + +void negPowH(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.pow(-0.5, [&] { b.h(q[0]); }); +} + +void invPowRx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.inv([&] { b.pow(2.0, [&] { b.rx(0.123, q[0]); }); }); +} + +void powCtrlRx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(2.0, [&] { b.ctrl(q[0], [&] { b.rx(0.123, q[1]); }); }); +} + +void ctrlPowRx(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.ctrl(q[0], [&] { b.pow(2.0, [&] { b.rx(0.123, q[1]); }); }); +} + +void negPowInvIswap(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.pow(-2.0, [&] { b.inv([&] { b.iswap(q[0], q[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], [&] { b.pow(1.0 / 3.0, [&] { b.sx(q[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 e6569f7648..70642d4aba 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -130,6 +130,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. @@ -154,6 +166,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. @@ -180,6 +195,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. @@ -203,6 +233,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. @@ -226,6 +262,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. @@ -252,6 +301,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. @@ -275,6 +330,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. @@ -298,6 +370,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. @@ -321,6 +406,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. @@ -344,6 +438,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. @@ -367,6 +470,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. @@ -391,6 +506,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. @@ -414,6 +542,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. @@ -506,6 +640,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. @@ -576,6 +716,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. @@ -600,6 +746,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. @@ -646,6 +799,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. @@ -768,6 +927,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. @@ -792,6 +957,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. @@ -809,6 +980,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. @@ -837,6 +1011,60 @@ void tripleNestedInv(QCProgramBuilder& b); /// Creates a circuit with inverse modifiers interleaved by a control modifier. void invCtrlSandwich(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 (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); + // --- 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 0ad96fbb10..a5f634ddd8 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]); @@ -301,6 +327,41 @@ void twoX(QCOProgramBuilder& b) { q[0] = b.x(q[0]); } +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 y(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.y(q[0]); @@ -354,6 +415,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]); @@ -407,6 +482,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]); @@ -465,6 +569,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]); @@ -524,6 +644,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]); @@ -584,6 +741,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]); @@ -643,6 +829,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]); @@ -703,6 +910,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]); @@ -763,6 +991,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]); @@ -823,6 +1078,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]); @@ -883,6 +1165,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]); @@ -1100,6 +1395,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]); @@ -1306,6 +1614,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]); @@ -1357,6 +1681,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]); @@ -1477,6 +1814,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]); @@ -1827,6 +2180,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]); @@ -1885,6 +2251,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]); @@ -1920,6 +2299,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]}); @@ -2046,6 +2433,136 @@ void invCtrlSandwich(QCOProgramBuilder& b) { }); } +// --- PowOp --------------------------------------------------------------- // + +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 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}; + }); +} + +// --- IfOp ---------------------------------------------------------------- // + 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 b4197c5a7f..790c113015 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. @@ -170,6 +185,21 @@ void inverseMultipleControlledX(QCOProgramBuilder& b); /// Creates a circuit with two X gates in a row. void twoX(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. @@ -196,6 +226,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. @@ -222,6 +258,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. @@ -251,6 +300,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. @@ -280,6 +335,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. @@ -309,6 +381,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. @@ -338,6 +423,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. @@ -367,6 +461,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. @@ -396,6 +499,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. @@ -426,6 +541,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. @@ -455,6 +583,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. @@ -559,6 +693,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); @@ -662,6 +802,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. @@ -686,6 +832,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. @@ -741,6 +894,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. @@ -905,6 +1064,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); @@ -932,6 +1097,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); @@ -952,6 +1123,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); @@ -983,6 +1157,60 @@ void tripleNestedInv(QCOProgramBuilder& b); /// Creates a circuit with inverse modifiers interleaved by a control modifier. void invCtrlSandwich(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 (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); + // --- IfOp ---------------------------------------------------------------- // /// Creates a circuit with a simple if operation with one qubit.