diff --git a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h index ab9532cfb4..ed94af35bc 100644 --- a/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h @@ -406,8 +406,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.c##OP_NAME(q0, q1); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0) { \ - * qc.OP_NAME %q1 : !qc.qubit \ + * qc.ctrl(%q0) targets(%a0 = %q1) { \ + * qc.OP_NAME %a0 : !qc.qubit \ * } : !qc.qubit \ * ``` \ */ \ @@ -424,8 +424,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.mc##OP_NAME({q0, q1}, q2); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0, %q1) { \ - * qc.OP_NAME %q2 : !qc.qubit \ + * qc.ctrl(%q0, %q1) targets(%a0 = %q2) { \ + * qc.OP_NAME %a0 : !qc.qubit \ * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ @@ -478,8 +478,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.c##OP_NAME(PARAM, q0, q1); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0) { \ - * qc.OP_NAME(%PARAM) %q1 : !qc.qubit \ + * qc.ctrl(%q0) targets(%a0 = %q1) { \ + * qc.OP_NAME(%PARAM) %a0 : !qc.qubit \ * } : !qc.qubit \ * ``` \ */ \ @@ -498,8 +498,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.mc##OP_NAME(PARAM, {q0, q1}, q2); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0, %q1) { \ - * qc.OP_NAME(%PARAM) %q2 : !qc.qubit \ + * qc.ctrl(%q0, %q1) targets(%a0 = %q2) { \ + * qc.OP_NAME(%PARAM) %a0 : !qc.qubit \ * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ @@ -549,8 +549,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0) { \ - * qc.OP_NAME(%PARAM1, %PARAM2) %q1 : !qc.qubit \ + * qc.ctrl(%q0) (%a0 = %q1) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %a0 : !qc.qubit \ * } : !qc.qubit \ * ``` \ */ \ @@ -571,8 +571,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0, %q1) { \ - * qc.OP_NAME(%PARAM1, %PARAM2) %q2 : !qc.qubit \ + * qc.ctrl(%q0, %q1) targets(%a0 = %q2) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %a0 : !qc.qubit \ * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ @@ -625,8 +625,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.c##OP_NAME(PARAM1, PARAM2, PARAM3, q0, q1); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0) { \ - * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q1 : !qc.qubit \ + * qc.ctrl(%q0) targets(%a0 = %q1) { \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %a0 : !qc.qubit \ * } : !qc.qubit \ * ``` \ */ \ @@ -649,8 +649,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.mc##OP_NAME(PARAM1, PARAM2, PARAM3, {q0, q1}, q2); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0, %q1) { \ - * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %q2 : !qc.qubit \ + * qc.ctrl(%q0, %q1) targets(%a0 = %q2) { \ + * qc.OP_NAME(%PARAM1, %PARAM2, %PARAM3) %a0 : !qc.qubit \ * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ @@ -695,8 +695,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.c##OP_NAME(q0, q1, q2); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0) { \ - * qc.OP_NAME %q1, %q2 : !qc.qubit, !qc.qubit \ + * qc.ctrl(%q0) targets(%a0 = %q1, %a1 = %q2) { \ + * qc.OP_NAME %a0, %a1 : !qc.qubit, !qc.qubit \ * } : !qc.qubit \ * ``` \ */ \ @@ -714,8 +714,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.mc##OP_NAME({q0, q1}, q2, q3); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0, %q1) { \ - * qc.OP_NAME %q2, %q3 : !qc.qubit, !qc.qubit \ + * qc.ctrl(%q0, %q1) targets(%a0 = %q2, %a1 = %q3) { \ + * qc.OP_NAME %a0, %a1 : !qc.qubit, !qc.qubit \ * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ @@ -764,8 +764,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.c##OP_NAME(PARAM, q0, q1, q2); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0) { \ - * qc.OP_NAME(%PARAM) %q1, %q2 : !qc.qubit, !qc.qubit \ + * qc.ctrl(%q0) targets(%a0 = %q1, %a1 = %q2) { \ + * qc.OP_NAME(%PARAM) %a0, %a1 : !qc.qubit, !qc.qubit \ * } : !qc.qubit \ * ``` \ */ \ @@ -785,8 +785,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.mc##OP_NAME(PARAM, {q0, q1}, q2, q3); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0, %q1) { \ - * qc.OP_NAME(%PARAM) %q2, %q3 : !qc.qubit, !qc.qubit \ + * qc.ctrl(%q0, %q1) targets(%a0 = %q2, %a1 = %q3) { \ + * qc.OP_NAME(%PARAM) %a0, %a1 : !qc.qubit, !qc.qubit \ * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ @@ -839,8 +839,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.c##OP_NAME(PARAM1, PARAM2, q0, q1, q2); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0) { \ - * qc.OP_NAME(%PARAM1, %PARAM2) %q1, %q2 : !qc.qubit, \ + * qc.ctrl(%q0) targets(%a0 = %q1, %a1 = %q2) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %a0, %a1 : !qc.qubit, \ * !qc.qubit \ * } : !qc.qubit \ * ``` \ @@ -863,8 +863,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * builder.mc##OP_NAME(PARAM1, PARAM2, {q0, q1}, q2, q3); \ * ``` \ * ```mlir \ - * qc.ctrl(%q0, %q1) { \ - * qc.OP_NAME(%PARAM1, %PARAM2) %q2, %q3 : !qc.qubit, !qc.qubit \ + * qc.ctrl(%q0, %q1) targets(%a0 = %q2, %a1 = %q3) { \ + * qc.OP_NAME(%PARAM1, %PARAM2) %a0, %a1 : !qc.qubit, !qc.qubit \ * } : !qc.qubit, !qc.qubit \ * ``` \ */ \ @@ -909,15 +909,18 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * * @par Example: * ```c++ - * builder.ctrl(q0, [&] { builder.x(q1); }); + * builder.ctrl(q0, q1, [&](ValueRange targets) { + * builder.x(targets[0]); + * }); * ``` * ```mlir - * qc.ctrl(%q0) { - * qc.x %q1 : !qc.qubit + * qc.ctrl(%q0) targets(%a0 = %q1) { + * qc.x %a0 : !qc.qubit * } : !qc.qubit * ``` */ - QCProgramBuilder& ctrl(ValueRange controls, const function_ref& body); + QCProgramBuilder& ctrl(ValueRange controls, ValueRange targets, + const function_ref& body); /** * @brief Apply an inverse (i.e., adjoint) operation. @@ -928,7 +931,9 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * * @par Example: * ```c++ - * builder.inv([&] { builder.s(q0); }); + * builder.inv(q0, [&](ValueRange qubits) { + * builder.h(qubits[0]); + * }); * ``` * ```mlir * qc.inv { @@ -936,7 +941,8 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder { * } * ``` */ - QCProgramBuilder& inv(const function_ref& body); + QCProgramBuilder& inv(ValueRange qubits, + const function_ref& body); //===--------------------------------------------------------------------===// // Deallocation diff --git a/mlir/include/mlir/Dialect/QC/IR/QCOps.td b/mlir/include/mlir/Dialect/QC/IR/QCOps.td index 8e76c9c3ba..96aac45960 100644 --- a/mlir/include/mlir/Dialect/QC/IR/QCOps.td +++ b/mlir/include/mlir/Dialect/QC/IR/QCOps.td @@ -916,51 +916,58 @@ def YieldOp : QCOp<"yield", traits = [Terminator]> { def CtrlOp : QCOp<"ctrl", - traits = [UnitaryOpInterface, + traits = [UnitaryOpInterface, AttrSizedOperandSegments, SingleBlockImplicitTerminator<"::mlir::qc::YieldOp">, RecursiveMemoryEffects]> { let summary = "Add control qubits to a unitary operation"; let description = [{ - A modifier operation that adds control qubits to the unitary operation - defined in its body region. The controlled operation applies the - underlying unitary only when all control qubits are in the |1⟩ state. + A modifier operation that adds control qubits to the unitary operation defined in its body region. + The controlled operation applies the underlying unitary only when all control qubits are in the $|1\rangle$ state. - Note that control qubits are logically unmodified by this operation in that - their quantum state remains unchanged. However, the `controls` argument - is marked with `MemWrite` to ensure correct dependency tracking in MLIR. + Note that control qubits are logically unmodified by this operation in that their quantum state remains unchanged. + However, the `controls` argument is marked with `MemWrite` to ensure correct dependency tracking in MLIR. + + The body region may contain an arbitrary amount of unitary and classical operations. + Non-unitary operations, such as `AllocOp` and `MeasureOp`, are not allowed. Example: ```mlir - qc.ctrl(%q0) { - qc.x %q1 : !qc.qubit + qc.ctrl(%q0) targets(%a0 = %q1) { + qc.x %a0 : !qc.qubit } : !qc.qubit ``` }]; - let arguments = - (ins Arg, - "the control qubits", [MemRead, MemWrite]>:$controls); + let arguments = (ins Arg, + "the control qubits", [MemRead, MemWrite]>:$controls, + Arg, + "the target qubits", [MemRead, MemWrite]>:$targets); let regions = (region SizedRegion<1>:$region); - let assemblyFormat = - "`(` $controls `)` $region attr-dict `:` type($controls)"; + let assemblyFormat = [{ + `(` $controls `)` + `targets` + custom($region, $targets) + attr-dict `:` + `{` type($controls) `}` ( `,` `{` type($targets)^ `}` )? + }]; let extraClassDeclaration = [{ - [[nodiscard]] UnitaryOpInterface getBodyUnitary(); + size_t getNumBodyUnitaries(); + [[nodiscard]] UnitaryOpInterface getBodyUnitary(size_t i); size_t getNumQubits() { return getNumTargets() + getNumControls(); } - size_t getNumTargets() { return getBodyUnitary().getNumTargets(); } + size_t getNumTargets() { return getTargets().size(); } size_t getNumControls() { return getControls().size(); } Value getQubit(size_t i); - Value getTarget(size_t i) { return getBodyUnitary().getTarget(i); } - ValueRange getTargets() { return getBodyUnitary().getTargets(); } + Value getTarget(size_t i) { return getTargets()[i]; } Value getControl(size_t i); - size_t getNumParams() { return getBodyUnitary().getNumParams(); } - Value getParameter(size_t i) { return getBodyUnitary().getParameter(i); } - ValueRange getParameters() { return getBodyUnitary().getParameters(); } + size_t getNumParams() { return 0; } + Value getParameter(size_t i) { llvm::reportFatalUsageError("CtrlOp does not have parameters"); } + ValueRange getParameters() { llvm::reportFatalUsageError("CtrlOp does not have parameters"); } static StringRef getBaseSymbol() { return "ctrl"; } }]; - let builders = [OpBuilder<(ins "ValueRange":$controls, - "const function_ref&":$bodyBuilder)>]; + let builders = [OpBuilder<(ins "ValueRange":$controls, "ValueRange":$targets, + "const function_ref&":$body)>]; let hasCanonicalizer = 1; let hasVerifier = 1; @@ -972,37 +979,48 @@ def InvOp : QCOp<"inv", RecursiveMemoryEffects]> { let summary = "Invert a unitary operation"; let description = [{ - A modifier operation that inverts the unitary operation defined in its body - region. + A modifier operation that inverts the unitary operation defined in its body region. + + The body region may contain an arbitrary amount of unitary and classical operations. + Non-unitary operations, such as `AllocOp` and `MeasureOp`, are not allowed. Example: ```mlir - qc.inv { - qc.s %q0 : !qc.qubit + qc.inv (%a0 = %q0) { + qc.s %a0 : !qc.qubit } ``` }]; + let arguments = (ins Arg< + Variadic, + "the qubits involved in the operation", [MemRead, MemWrite]>:$qubits); let regions = (region SizedRegion<1>:$region); - let assemblyFormat = "$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(); } + let assemblyFormat = [{ + custom($region, $qubits) + attr-dict `:` + type($qubits) + }]; + + let extraClassDeclaration = [{ + size_t getNumBodyUnitaries(); + [[nodiscard]] UnitaryOpInterface getBodyUnitary(size_t i); + size_t getNumQubits() { return getNumTargets(); } + size_t getNumTargets() { return getQubits().size(); } + size_t getNumControls() { return 0; } + Value getQubit(size_t i) { return getTarget(i); } + Value getTarget(size_t i) { return getQubits()[i]; } + ValueRange getTargets() { return getQubits(); } + Value getControl(size_t i) { llvm::reportFatalUsageError("InvOp does not have controls"); } + ValueRange getControls() { return {nullptr, 0}; } + size_t getNumParams() { return 0; } + Value getParameter(size_t i) { llvm::reportFatalUsageError("InvOp does not have parameters"); } + ValueRange getParameters() { return {nullptr, 0}; } static StringRef getBaseSymbol() { return "inv"; } }]; - let builders = [OpBuilder<(ins "const function_ref&":$bodyBuilder)>]; + let builders = [OpBuilder<(ins "ValueRange":$qubits, + "const function_ref&":$body)>]; let hasCanonicalizer = 1; let hasVerifier = 1; diff --git a/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h b/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h new file mode 100644 index 0000000000..c3cfc1c2b2 --- /dev/null +++ b/mlir/include/mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#include + +#include +#include + +namespace mlir::qc { + +/** + * @brief Translate an OpenQASM3 program to the QC dialect. + * + * @param context MLIRContext to create the module in. + * @param filename Path to the input OpenQASM3 file. + */ +[[nodiscard]] OwningOpRef +translateQASM3ToQC(MLIRContext* context, const std::string& filename); + +/** + * @brief Translate an OpenQASM3 program to the QC dialect. + * + * @param context MLIRContext to create the module in. + * @param input Stream containing the OpenQASM3 program. + */ +[[nodiscard]] OwningOpRef translateQASM3ToQC(MLIRContext* context, + std::istream& input); + +} // namespace mlir::qc diff --git a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td index a5bbfb7f51..8d0025d286 100644 --- a/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td +++ b/mlir/include/mlir/Dialect/QCO/IR/QCOOps.td @@ -1069,12 +1069,14 @@ def CtrlOp RecursiveMemoryEffects]> { let summary = "Add control qubits to a unitary operation"; let description = [{ - A modifier operation that adds control qubits to the unitary operation - defined in its body region. The controlled operation applies the - underlying unitary only when all control qubits are in the |1⟩ state. - The operation takes a variadic number of control and target qubits as - inputs and produces corresponding output qubits. Control qubits are not - modified by the operation and simply pass through to the outputs. + A modifier operation that adds control qubits to the unitary operation defined in its body region. + The controlled operation applies the underlying unitary only when all control qubits are in the $|1\rangle$ state. + + The operation takes a variadic number of control and target qubits as inputs and produces corresponding output qubits. + Control qubits are not modified by the operation and simply pass through to the outputs. + + The body region may contain an arbitrary amount of unitary and classical operations. + Non-unitary operations, such as `AllocOp` and `MeasureOp`, are not allowed. Example: ```mlir @@ -1102,7 +1104,8 @@ def CtrlOp }]; let extraClassDeclaration = [{ - UnitaryOpInterface getBodyUnitary(); + size_t getNumBodyUnitaries(); + [[nodiscard]] UnitaryOpInterface getBodyUnitary(size_t i); size_t getNumQubits() { return getNumControls() + getNumTargets(); } size_t getNumTargets() { return getTargetsIn().size(); } size_t getNumControls() { return getControlsIn().size(); } @@ -1120,9 +1123,9 @@ def CtrlOp ResultRange getOutputControls() { return getControlsOut(); } 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(); } + size_t getNumParams() { return 0; } + Value getParameter(size_t i) { llvm::reportFatalUsageError("CtrlOp does not have parameters"); } + ValueRange getParameters() { return {nullptr, 0}; } [[nodiscard]] static StringRef getBaseSymbol() { return "ctrl"; } [[nodiscard]] std::optional getUnitaryMatrix(); }]; @@ -1146,9 +1149,12 @@ def InvOp RecursiveMemoryEffects]> { let summary = "Invert a unitary operation"; let description = [{ - A modifier operation that inverts the unitary operation defined in its body - region. The operation takes a variadic number of qubits as inputs and - produces corresponding output qubits. + A modifier operation that inverts the unitary operation defined in its body region. + + The operation takes a variadic number of qubits as inputs and produces corresponding output qubits. + + The body region may contain an arbitrary amount of unitary and classical operations. + Non-unitary operations, such as `AllocOp` and `MeasureOp`, are not allowed. Example: ```mlir @@ -1173,7 +1179,8 @@ def InvOp }]; let extraClassDeclaration = [{ - UnitaryOpInterface getBodyUnitary(); + size_t getNumBodyUnitaries(); + [[nodiscard]] UnitaryOpInterface getBodyUnitary(size_t i); size_t getNumQubits() { return getNumTargets(); } size_t getNumTargets() { return getQubitsIn().size(); } static size_t getNumControls() { return 0; } @@ -1184,16 +1191,16 @@ def InvOp ResultRange getOutputQubits() { return getQubitsOut(); } Value getInputTarget(size_t i) { return getInputQubit(i); } Value getOutputTarget(size_t i) { return getOutputQubit(i); } - static Value getInputControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } + static Value getInputControl(size_t i) { llvm::reportFatalUsageError("InvOp does not have controls"); } static OperandRange getInputControls() { return {nullptr, 0}; } - static Value getOutputControl(size_t i) { llvm::reportFatalUsageError("Operation does not have controls"); } + static Value getOutputControl(size_t i) { llvm::reportFatalUsageError("InvOp does not have controls"); } static ResultRange getOutputControls() { return {nullptr, 0}; } ResultRange getOutputTargets() { return getOutputQubits(); } 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(); } + size_t getNumParams() { return 0; } + Value getParameter(size_t i) { llvm::reportFatalUsageError("InvOp does not have parameters"); } + ValueRange getParameters() { return {nullptr, 0}; } [[nodiscard]] static StringRef getBaseSymbol() { return "inv"; } [[nodiscard]] std::optional getUnitaryMatrix(); }]; diff --git a/mlir/include/mlir/Dialect/Utils/Utils.h b/mlir/include/mlir/Dialect/Utils/Utils.h index 3d976a5a63..a885c2da2b 100644 --- a/mlir/include/mlir/Dialect/Utils/Utils.h +++ b/mlir/include/mlir/Dialect/Utils/Utils.h @@ -12,9 +12,11 @@ #include #include +#include #include #include +#include #include namespace mlir::utils { @@ -78,4 +80,120 @@ template return std::nullopt; } +template +[[nodiscard]] +static ParseResult +parseTargetAliasing(OpAsmParser& parser, Region& region, + SmallVectorImpl& operands) { + // 1. Parse the opening parenthesis + if (parser.parseLParen()) { + return failure(); + } + + // Temporary storage for block arguments we are about to create + SmallVector blockArgs; + + // 2. Prepare to parse the list + if (failed(parser.parseOptionalRParen())) { + do { + OpAsmParser::Argument newArg; // The "new" variable name + OpAsmParser::UnresolvedOperand oldOperand; // The "old" input variable + + // Parse "%new" + if (parser.parseArgument(newArg)) { + return failure(); + } + + // Parse "=" + if (parser.parseEqual()) { + return failure(); + } + + // Parse "%old" + if (parser.parseOperand(oldOperand)) { + return failure(); + } + operands.push_back(oldOperand); + + // Hard-code QubitType since targets in qco.ctrl are always qubits. + // This avoids double-binding type($targets_in) in the assembly format + // while keeping the parser simple and the assembly format clean. + newArg.type = QubitType::get(parser.getBuilder().getContext()); + blockArgs.push_back(newArg); + + } while (succeeded(parser.parseOptionalComma())); + + if (parser.parseRParen()) { + return failure(); + } + } + + // 4. Parse the Region + // We explicitly pass the blockArgs we just parsed so they become the entry + // block! + if (parser.parseRegion(region, blockArgs)) { + return failure(); + } + + return success(); +} + +static void printTargetAliasing(OpAsmPrinter& printer, Region& region, + OperandRange targetsIn) { + printer << "("; + if (region.empty()) { + printer << ") "; + printer.printRegion(region, false); + return; + } + Block& entryBlock = region.front(); + + const auto numTargets = targetsIn.size(); + for (unsigned i = 0; i < numTargets; ++i) { + if (i > 0) { + printer << ", "; + } + printer.printOperand(entryBlock.getArgument(i)); + printer << " = "; + printer.printOperand(targetsIn[i]); + } + printer << ") "; + + printer.printRegion(region, false); +} + +/** + * @brief Get the value corresponding to @p qubit from the block arguments @p + * qubits if @p qubit is a block argument, otherwise return @p qubit itself. + */ +static Value getValueFromBlockArgument(Value qubit, ValueRange qubits) { + if (auto blockArg = dyn_cast(qubit)) { + return qubits[blockArg.getArgNumber()]; + } + return qubit; +} + +/** + * @brief Create a mapping between block arguments and qubit values. + * + * @details This helper function is used to resolve block arguments for nested + * modifiers. + */ +static void populateMapping(IRMapping& mapping, Block& block, + ValueRange innerQubits, ValueRange outerQubits, + ValueRange newQubits, ValueRange qubitArgs) { + assert(innerQubits.size() == block.getNumArguments() && + "Size of innerQubits must match number of block arguments"); + for (auto arg : block.getArguments()) { + auto innerQubit = innerQubits[arg.getArgNumber()]; + auto outerQubit = getValueFromBlockArgument(innerQubit, outerQubits); + if (auto it = llvm::find(newQubits, outerQubit); it != newQubits.end()) { + auto index = std::distance(newQubits.begin(), it); + mapping.map(arg, qubitArgs[index]); + } else { + llvm::reportFatalInternalError("Outer qubit not found in new qubits"); + } + } +} + } // namespace mlir::utils diff --git a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp index a486e82c5d..96598b2c82 100644 --- a/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp +++ b/mlir/lib/Conversion/QCOToJeff/QCOToJeff.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -155,19 +156,30 @@ static void handleResult(Operation* op, ConversionPatternRewriter& rewriter, * @brief Target operands: `adaptor.getOperands()` at the matched op, or * `state.targetsIn` while lowering inside `qco.ctrl` / `qco.inv`. * - * @param state Lowering state. - * @param adaptor Operand adaptor for the matched op. + * @param op The operation being converted. + * @param adaptor The operation adaptor of the operation. + * @param state The lowering state. * @tparam NumParams Number of parameters to drop from the end of the operand * list. - * @tparam OpAdaptor Adaptor with `getOperands()`. - * @return ValueRange The target operands. + * @tparam OpType The type of the operation. + * @tparam OpAdaptorType The type of the operation adaptor. + * @return The target operands. */ -template -[[nodiscard]] static ValueRange getEffectiveTargetOperands(LoweringState& state, - OpAdaptor adaptor) { - return state.inModifier() - ? ValueRange(state.targetsIn) - : ValueRange(adaptor.getOperands().drop_back(NumParams)); +template +[[nodiscard]] static SmallVector +getEffectiveTargetOperands(OpType op, OpAdaptorType adaptor, + LoweringState& state) { + if (!state.inModifier()) { + return adaptor.getOperands().drop_back(NumParams); + } + + SmallVector targets; + for (auto targetArg : op->getOperands().drop_back(NumParams)) { + auto target = + state.targetsIn[cast(targetArg).getArgNumber()]; + targets.push_back(target); + } + return targets; } /** @@ -190,10 +202,10 @@ convertJeffGate(QCOOpType op, typename QCOOpType::Adaptor adaptor, std::index_sequence /*targetIndices*/, std::index_sequence /*paramIndices*/) { constexpr std::size_t numParams = sizeof...(ParamIndices); - ValueRange targets = getEffectiveTargetOperands(state, adaptor); + auto targets = getEffectiveTargetOperands(op, adaptor, state); assert(targets.size() >= sizeof...(TargetIndices) && "Not enough operands available for conversion"); - ValueRange params = op.getParameters(); + auto params = op.getParameters(); auto jeffOp = JeffOpType::create( rewriter, op.getLoc(), targets[TargetIndices]..., params[ParamIndices]..., @@ -336,7 +348,7 @@ static LogicalResult moveRegion(Region& source, Region& dest, ConversionPatternRewriter& rewriter, const TypeConverter* typeConverter) { rewriter.inlineRegionBefore(source, dest, dest.end()); - Block* block = &dest.front(); + auto* block = &dest.front(); TypeConverter::SignatureConversion sc(block->getNumArguments()); if (failed( typeConverter->convertSignatureArgs(block->getArgumentTypes(), sc))) { @@ -728,7 +740,7 @@ struct ConvertQCOCustomGateToJeff final } } - ValueRange targets = getEffectiveTargetOperands(state, adaptor); + auto targets = getEffectiveTargetOperands(op, adaptor, state); assert(targets.size() >= NumTargets && "Not enough operands available for conversion"); @@ -764,7 +776,7 @@ struct ConvertQCOPPRGateToJeff final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); - ValueRange targets = getEffectiveTargetOperands<1>(state, adaptor); + auto targets = getEffectiveTargetOperands<1>(op, adaptor, state); assert(targets.size() >= 2 && "Not enough operands available for conversion"); createPPROp(op, rewriter, state, targets, {p0_, p1_}); @@ -798,7 +810,7 @@ struct ConvertQCOU2OpToJeff final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - ValueRange targets = getEffectiveTargetOperands<2>(state, adaptor); + auto targets = getEffectiveTargetOperands<2>(op, adaptor, state); assert(!targets.empty() && "Not enough operands available for conversion"); auto target = targets.front(); @@ -840,11 +852,8 @@ struct ConvertQCOBarrierOpToJeff final matchAndRewrite(BarrierOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { auto& state = getState(); - - ValueRange targets = getEffectiveTargetOperands<0>(state, adaptor); - + auto targets = getEffectiveTargetOperands<0>(op, adaptor, state); createCustomOp(op, rewriter, state, targets, {}, false, "barrier"); - return success(); } }; @@ -871,18 +880,24 @@ struct ConvertQCOCtrlOpToJeff final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(CtrlOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { + if (op.getNumBodyUnitaries() != 1) { + return rewriter.notifyMatchFailure( + op, + "Control modifiers with multiple body unitaries are not supported."); + } + auto& state = getState(); if (state.inCtrlOp) { return rewriter.notifyMatchFailure( - op, "Nested control operations are not supported. Run the " + op, "Nested control modifiers are not supported. Run the " "canonicalization pass before the conversion"); } if (state.inInvOp) { return rewriter.notifyMatchFailure( - op, "Control operations inside inversion operations are not " - "supported. Run the canonicalization pass before the conversion"); + op, "Control modifiers inside inversion modifiers are not supported. " + "Run the canonicalization pass before the conversion"); } // Set modifier information @@ -921,11 +936,17 @@ struct ConvertQCOInvOpToJeff final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(InvOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { + if (op.getNumBodyUnitaries() != 1) { + return rewriter.notifyMatchFailure(op, + "Inversion modifiers with multiple " + "body unitaries are not supported."); + } + auto& state = getState(); if (state.inInvOp) { return rewriter.notifyMatchFailure( - op, "Nested inversion operations are not supported. Run the " + op, "Nested inversion modifiers are not supported. Run the " "canonicalization pass before the conversion"); } @@ -934,6 +955,13 @@ struct ConvertQCOInvOpToJeff final : StatefulOpConversionPattern { state.invOp = op; if (state.targetsIn.empty()) { state.targetsIn = llvm::to_vector(adaptor.getQubitsIn()); + } else { + auto outerQubits = state.targetsIn; + SmallVector innerQubits; + for (auto arg : op.getBody()->getArguments()) { + innerQubits.push_back(outerQubits[arg.getArgNumber()]); + } + state.targetsIn = std::move(innerQubits); } // Inline region diff --git a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp index 97d7071f69..a8ba24090d 100644 --- a/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp +++ b/mlir/lib/Conversion/QCOToQC/QCOToQC.cpp @@ -120,25 +120,21 @@ class StatefulOpConversionPattern : public OpConversionPattern { * @param sourceRegion Source region where the operations are moved from * @param targetRegion Target region where the operations are moved to * @param offset Offset to the arguments that are dropped - * @param numArgs Number of arguments that are dropped * @param replacementValues Values to replace the uses of the arguments * @param rewriter PatternRewriter of the current conversion pass */ static void inlineRegion(Region& sourceRegion, Region& targetRegion, - unsigned int offset, unsigned int numArgs, - ValueRange replacementValues, + unsigned int offset, ValueRange replacementValues, ConversionPatternRewriter& rewriter) { - assert(replacementValues.size() == numArgs && - "replacementValues size must match numArgs"); - rewriter.inlineRegionBefore(sourceRegion, targetRegion, targetRegion.end()); auto& block = targetRegion.front(); - + assert(block.getNumArguments() == offset + replacementValues.size() && + "Number of replacement values must match number of block arguments"); for (auto [arg, replacementVal] : llvm::zip_equal( block.getArguments().drop_front(offset), replacementValues)) { arg.replaceAllUsesWith(replacementVal); } - block.eraseArguments(offset, numArgs); + block.eraseArguments(offset, replacementValues.size()); } #define GEN_PASS_DEF_QCOTOQC @@ -634,8 +630,8 @@ struct ConvertQCOBarrierOp final : OpConversionPattern { * ``` * is converted to * ```mlir - * qc.ctrl(%q0) { - * qc.x %q1 : !qc.qubit + * qc.ctrl(%q0) targets(%a0 = %q1) { + * qc.x %a0 : !qc.qubit * } : !qc.qubit * ``` */ @@ -645,16 +641,19 @@ struct ConvertQCOCtrlOp final : OpConversionPattern { LogicalResult matchAndRewrite(qco::CtrlOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // Get QC controls - auto qcControls = adaptor.getControlsIn(); - // Create qc.ctrl operation - auto qcOp = qc::CtrlOp::create(rewriter, op.getLoc(), qcControls); - - // Inline the region and replace the blockarguments - inlineRegion(op.getRegion(), qcOp.getRegion(), 0, - adaptor.getTargetsIn().size(), adaptor.getTargetsIn(), - rewriter); + auto qcOp = qc::CtrlOp::create( + rewriter, op.getLoc(), adaptor.getControlsIn(), adaptor.getTargetsIn()); + + auto& dstRegion = qcOp.getRegion(); + rewriter.inlineRegionBefore(op.getRegion(), dstRegion, dstRegion.begin()); + auto* block = &dstRegion.front(); + TypeConverter::SignatureConversion sc(block->getNumArguments()); + if (failed(typeConverter->convertSignatureArgs(block->getArgumentTypes(), + sc))) { + return failure(); + } + rewriter.applySignatureConversion(block, sc); // Replace the output qubits with the same QC references rewriter.replaceOp(op, adaptor.getOperands()); @@ -687,11 +686,17 @@ struct ConvertQCOInvOp final : OpConversionPattern { matchAndRewrite(qco::InvOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { // Create qc.inv operation - auto qcOp = qc::InvOp::create(rewriter, op.getLoc()); - - // Inline the region and replace the blockarguments - inlineRegion(op.getRegion(), qcOp.getRegion(), 0, - adaptor.getOperands().size(), adaptor.getQubitsIn(), rewriter); + auto qcOp = qc::InvOp::create(rewriter, op.getLoc(), adaptor.getQubitsIn()); + + auto& dstRegion = qcOp.getRegion(); + rewriter.inlineRegionBefore(op.getRegion(), dstRegion, dstRegion.begin()); + auto* block = &dstRegion.front(); + TypeConverter::SignatureConversion sc(block->getNumArguments()); + if (failed(typeConverter->convertSignatureArgs(block->getArgumentTypes(), + sc))) { + return failure(); + } + rewriter.applySignatureConversion(block, sc); // Replace the output qubits with the same QC references rewriter.replaceOp(op, adaptor.getOperands()); @@ -764,9 +769,9 @@ struct ConvertQCOSCFForOp final : OpConversionPattern { // Erase default block rewriter.eraseBlock(&newFor.getRegion().front()); - // Inline the region and replace the blockarguments - inlineRegion(op.getRegion(), newFor.getRegion(), 1, - adaptor.getInitArgs().size(), adaptor.getInitArgs(), rewriter); + // Inline the region and replace the block arguments + inlineRegion(op.getRegion(), newFor.getRegion(), 1, adaptor.getInitArgs(), + rewriter); rewriter.replaceOp(op, adaptor.getInitArgs()); @@ -810,11 +815,11 @@ struct ConvertQCOSCFWhileOp final : OpConversionPattern { auto newWhileOp = scf::WhileOp::create(rewriter, op->getLoc(), TypeRange{}, ValueRange{}); - // Inline the regions and replace the blockarguments - inlineRegion(op.getBefore(), newWhileOp.getBefore(), 0, - adaptor.getInits().size(), adaptor.getInits(), rewriter); - inlineRegion(op.getAfter(), newWhileOp.getAfter(), 0, - adaptor.getInits().size(), adaptor.getInits(), rewriter); + // Inline the regions and replace the block arguments + inlineRegion(op.getBefore(), newWhileOp.getBefore(), 0, adaptor.getInits(), + rewriter); + inlineRegion(op.getAfter(), newWhileOp.getAfter(), 0, adaptor.getInits(), + rewriter); rewriter.replaceOp(op, adaptor.getInits()); @@ -855,15 +860,13 @@ struct ConvertQCOIfOp final : OpConversionPattern { // Erase the default empty then block rewriter.eraseBlock(&newThenRegion.front()); - // Inline the region and replace the blockarguments + // Inline the region and replace the block arguments inlineRegion(op.getThenRegion(), newThenRegion, 0, - adaptor.getOperands().size() - 1, adaptor.getOperands().drop_front(1), rewriter); // Inline the else block if it has more than just the yield operation if (oldElseRegion.front().getOperations().size() > 1) { inlineRegion(oldElseRegion, newIf.getElseRegion(), 0, - adaptor.getOperands().size() - 1, adaptor.getOperands().drop_front(1), rewriter); } diff --git a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp index b4a4b0a151..65877a3120 100644 --- a/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp +++ b/mlir/lib/Conversion/QCToQCO/QCToQCO.cpp @@ -391,22 +391,6 @@ static void popModifierFrame(LoweringState& state) { state.modifierFrames.pop_back(); } -/** @brief Adds entry block aliases for modifier target values. */ -template -[[nodiscard]] static ValueRange addModifierAliases(OpType op, - const size_t numTargets, - PatternRewriter& rewriter) { - auto& entryBlock = op.getRegion().front(); - const auto opLoc = op.getLoc(); - const auto qubitType = qco::QubitType::get(op.getContext()); - rewriter.modifyOpInPlace(op, [&] { - for (size_t i = 0; i < numTargets; ++i) { - entryBlock.addArgument(qubitType, opLoc); - } - }); - return entryBlock.getArguments().take_back(numTargets); -} - /** * @brief Inserts extracted qubits that are not required by @p target back into * their tensors. @@ -525,7 +509,8 @@ collectQubitValuesInsideSCFOps(Operation* op, LoweringState* state) { // Iterate through all operations of the current region for (auto& operation : region.front().getOperations()) { // Recursively walk through nested regions - if (operation.getNumRegions() > 0) { + if (operation.getNumRegions() > 0 && + !isa(operation)) { auto [qubits, registers] = collectQubitValuesInsideSCFOps(&operation, state); auto& regionQubitMap = state->regionQubitMap[op]; @@ -1091,8 +1076,8 @@ struct ConvertQCBarrierOp final : StatefulOpConversionPattern { * * @par Example: * ```mlir - * qc.ctrl(%q0) { - * qc.x %q1 : !qc.qubit + * qc.ctrl(%q0) targets(%a0 = %q1) { + * qc.x %a0 : !qc.qubit * } : !qc.qubit * ``` * is converted to @@ -1111,7 +1096,6 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = getState(); auto* operation = op.getOperation(); - const auto numTargets = op.getNumTargets(); const auto qcControls = op.getControls(); const auto qcTargets = op.getTargets(); auto qcoControls = resolveMappedQubits(state, operation, qcControls); @@ -1124,16 +1108,20 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { assignMappedQubits(state, operation, qcControls, qcoOp.getControlsOut()); assignMappedQubits(state, operation, qcTargets, qcoOp.getTargetsOut()); - // Clone body region from QC to QCO + auto qcArgs = op.getRegion().front().getArguments(); + + // Inline region auto& dstRegion = qcoOp.getRegion(); - rewriter.cloneRegionBefore(op.getRegion(), dstRegion, dstRegion.end()); + rewriter.inlineRegionBefore(op.getRegion(), dstRegion, dstRegion.begin()); + auto* block = &dstRegion.front(); + TypeConverter::SignatureConversion sc(block->getNumArguments()); + if (failed(typeConverter->convertSignatureArgs(block->getArgumentTypes(), + sc))) { + return failure(); + } + rewriter.applySignatureConversion(block, sc); - // Create block arguments for QCO targets - auto& entryBlock = dstRegion.front(); - assert(entryBlock.getNumArguments() == 0 && - "QC ctrl region unexpectedly has entry block arguments"); - pushModifierFrame(state, qcTargets, - addModifierAliases(qcoOp, numTargets, rewriter)); + pushModifierFrame(state, qcArgs, qcoOp.getRegion().front().getArguments()); rewriter.eraseOp(op); return success(); @@ -1165,7 +1153,6 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { 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); @@ -1174,16 +1161,20 @@ struct ConvertQCInvOp final : StatefulOpConversionPattern { assignMappedQubits(state, operation, qcTargets, qcoOp.getOutputTargets()); - // Clone body region from QC to QCO + auto qcArgs = op.getRegion().front().getArguments(); + + // Inline region 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 inv region unexpectedly has entry block arguments"); - pushModifierFrame(state, qcTargets, - addModifierAliases(qcoOp, numTargets, rewriter)); + rewriter.inlineRegionBefore(op.getRegion(), dstRegion, dstRegion.begin()); + auto* block = &dstRegion.front(); + TypeConverter::SignatureConversion sc(block->getNumArguments()); + if (failed(typeConverter->convertSignatureArgs(block->getArgumentTypes(), + sc))) { + return failure(); + } + rewriter.applySignatureConversion(block, sc); + + pushModifierFrame(state, qcArgs, qcoOp.getRegion().front().getArguments()); rewriter.eraseOp(op); return success(); diff --git a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp index 98942d998c..e7a6d2910f 100644 --- a/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp +++ b/mlir/lib/Conversion/QCToQIR/QCToQIR.cpp @@ -92,8 +92,8 @@ struct LoweringState : QIRMetadata { DenseMap resultPtrs; /// Modifier information - int64_t inCtrlOp = 0; - DenseMap> controls; + size_t inCtrlOp = 0; + SmallVector controls; /// Allocator and StringSaver for stable StringRefs llvm::BumpPtrAllocator allocator; @@ -174,7 +174,7 @@ convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, // Query state for modifier information const auto inCtrlOp = state.inCtrlOp; const SmallVector controls = - inCtrlOp != 0 ? state.controls[inCtrlOp] : SmallVector{}; + inCtrlOp != 0 ? state.controls : SmallVector{}; const size_t numCtrls = controls.size(); // Define argument types @@ -210,8 +210,10 @@ convertUnitaryToCallOp(QCOpType& op, QCOpAdaptorType& adaptor, // Clean up modifier information if (inCtrlOp != 0) { - state.controls.erase(inCtrlOp); state.inCtrlOp--; + if (state.inCtrlOp == 0) { + state.controls.clear(); + } } // Replace operation with CallOp @@ -315,7 +317,7 @@ struct ConvertQCUnitaryOpQIR : StatefulOpConversionPattern { ConversionPatternRewriter& rewriter) const override { auto& state = this->getState(); const auto inCtrlOp = state.inCtrlOp; - const size_t numCtrls = inCtrlOp != 0 ? state.controls[inCtrlOp].size() : 0; + const size_t numCtrls = inCtrlOp != 0 ? state.controls.size() : 0; const auto fnName = GetFnName(numCtrls); return convertUnitaryToCallOp(op, adaptor, rewriter, this->getContext(), state, fnName, NumTargets, NumParams); @@ -863,16 +865,22 @@ struct ConvertQCCtrlOp final : StatefulOpConversionPattern { LogicalResult matchAndRewrite(CtrlOp op, OpAdaptor adaptor, ConversionPatternRewriter& rewriter) const override { - // Update modifier information auto& state = getState(); - state.inCtrlOp++; + + if (state.inCtrlOp != 0) { + return rewriter.notifyMatchFailure(op, + "Nested CtrlOps are not supported"); + } + + // Update modifier information + state.inCtrlOp = op.getNumBodyUnitaries(); const SmallVector controls(adaptor.getControls().begin(), adaptor.getControls().end()); - state.controls[state.inCtrlOp] = controls; + state.controls = controls; - // Inline region and remove operation - rewriter.inlineBlockBefore(&op.getRegion().front(), op->getBlock(), - op->getIterator()); + // Inline block and remove operation + rewriter.inlineBlockBefore(&op.getRegion().front(), op, + adaptor.getTargets()); rewriter.eraseOp(op); return success(); } diff --git a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp index 5eb022c03a..4bde38d301 100644 --- a/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp +++ b/mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp @@ -223,7 +223,8 @@ QCProgramBuilder& QCProgramBuilder::reset(Value qubit) { const std::variant&(PARAM), ValueRange controls) { \ checkFinalized(); \ auto param = variantToValue(*this, getLoc(), PARAM); \ - CtrlOp::create(*this, controls, [&] { OP_CLASS::create(*this, param); }); \ + ctrl(controls, ValueRange{}, \ + [&](ValueRange /*targets*/) { OP_NAME(param); }); \ return *this; \ } @@ -247,7 +248,7 @@ DEFINE_ZERO_TARGET_ONE_PARAMETER(GPhaseOp, gphase, theta) QCProgramBuilder& QCProgramBuilder::mc##OP_NAME(ValueRange controls, \ Value target) { \ checkFinalized(); \ - CtrlOp::create(*this, controls, [&] { OP_CLASS::create(*this, target); }); \ + ctrl(controls, target, [&](ValueRange targets) { OP_NAME(targets[0]); }); \ return *this; \ } @@ -285,8 +286,8 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdgOp, sxdg) Value target) { \ checkFinalized(); \ auto param = variantToValue(*this, getLoc(), PARAM); \ - CtrlOp::create(*this, controls, \ - [&] { OP_CLASS::create(*this, target, param); }); \ + ctrl(controls, target, \ + [&](ValueRange targets) { OP_NAME(param, targets[0]); }); \ return *this; \ } @@ -321,8 +322,8 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(POp, p, theta) checkFinalized(); \ auto param1 = variantToValue(*this, getLoc(), PARAM1); \ auto param2 = variantToValue(*this, getLoc(), PARAM2); \ - CtrlOp::create(*this, controls, \ - [&] { OP_CLASS::create(*this, target, param1, param2); }); \ + ctrl(controls, target, \ + [&](ValueRange targets) { OP_NAME(param1, param2, targets[0]); }); \ return *this; \ } @@ -360,8 +361,8 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2Op, u2, phi, lambda) auto param1 = variantToValue(*this, getLoc(), PARAM1); \ auto param2 = variantToValue(*this, getLoc(), PARAM2); \ auto param3 = variantToValue(*this, getLoc(), PARAM3); \ - CtrlOp::create(*this, controls, [&] { \ - OP_CLASS::create(*this, target, param1, param2, param3); \ + ctrl(controls, target, [&](ValueRange targets) { \ + OP_NAME(param1, param2, param3, targets[0]); \ }); \ return *this; \ } @@ -386,8 +387,8 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(UOp, u, theta, phi, lambda) QCProgramBuilder& QCProgramBuilder::mc##OP_NAME( \ ValueRange controls, Value qubit0, Value qubit1) { \ checkFinalized(); \ - CtrlOp::create(*this, controls, \ - [&] { OP_CLASS::create(*this, qubit0, qubit1); }); \ + ctrl(controls, ValueRange{qubit0, qubit1}, \ + [&](ValueRange targets) { OP_NAME(targets[0], targets[1]); }); \ return *this; \ } @@ -418,8 +419,8 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECROp, ecr) Value qubit0, Value qubit1) { \ checkFinalized(); \ auto param = variantToValue(*this, getLoc(), PARAM); \ - CtrlOp::create(*this, controls, \ - [&] { OP_CLASS::create(*this, qubit0, qubit1, param); }); \ + ctrl(controls, ValueRange{qubit0, qubit1}, \ + [&](ValueRange targets) { OP_NAME(param, targets[0], targets[1]); }); \ return *this; \ } @@ -455,8 +456,8 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZOp, rzz, theta) checkFinalized(); \ auto param1 = variantToValue(*this, getLoc(), PARAM1); \ auto param2 = variantToValue(*this, getLoc(), PARAM2); \ - CtrlOp::create(*this, controls, [&] { \ - OP_CLASS::create(*this, qubit0, qubit1, param1, param2); \ + ctrl(controls, ValueRange{qubit0, qubit1}, [&](ValueRange targets) { \ + OP_NAME(param1, param2, targets[0], targets[1]); \ }); \ return *this; \ } @@ -478,16 +479,19 @@ QCProgramBuilder& QCProgramBuilder::barrier(ValueRange qubits) { // Modifiers //===----------------------------------------------------------------------===// -QCProgramBuilder& QCProgramBuilder::ctrl(ValueRange controls, - const function_ref& body) { +QCProgramBuilder& +QCProgramBuilder::ctrl(ValueRange controls, ValueRange targets, + const function_ref& body) { checkFinalized(); - CtrlOp::create(*this, controls, body); + CtrlOp::create(*this, controls, targets, body); return *this; } -QCProgramBuilder& QCProgramBuilder::inv(const function_ref& body) { +QCProgramBuilder& +QCProgramBuilder::inv(ValueRange qubits, + const function_ref& body) { checkFinalized(); - InvOp::create(*this, body); + InvOp::create(*this, qubits, body); return *this; } diff --git a/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp index d457fa9a35..059e7dc736 100644 --- a/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Modifiers/CtrlOp.cpp @@ -8,11 +8,15 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/QC/IR/QCOps.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include #include +#include +#include #include #include #include @@ -20,6 +24,7 @@ #include #include +#include using namespace mlir; using namespace mlir::qc; @@ -33,22 +38,46 @@ struct MergeNestedCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - auto* bodyUnitary = op.getBodyUnitary().getOperation(); - auto bodyCtrlOp = dyn_cast(bodyUnitary); - if (!bodyCtrlOp) { + // Require at least one control + // Trivial case is handled by ReduceCtrl + if (op.getNumControls() == 0) { return failure(); } - // add the inner controls as operands to the outer one - op->insertOperands(op.getNumOperands(), bodyCtrlOp.getControls()); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto innerCtrlOp = dyn_cast(op.getBodyUnitary(0).getOperation()); + if (!innerCtrlOp) { + return failure(); + } - // Move the inner unitary op into the outer one's body region and replace - // the outer one with the inner one's results - const OpBuilder::InsertionGuard guard(rewriter); - rewriter.setInsertionPoint(bodyUnitary); - auto* innerUnitaryOp = bodyCtrlOp.getBodyUnitary().getOperation(); - rewriter.moveOpBefore(innerUnitaryOp, bodyUnitary); - rewriter.replaceOp(bodyUnitary, innerUnitaryOp->getResults()); + auto outerControls = op.getControls(); + auto outerTargets = op.getTargets(); + auto innerTargets = innerCtrlOp.getTargets(); + + SmallVector controls; + SmallVector targets; + llvm::append_range(controls, outerControls); + for (auto [arg, qubit] : + llvm::zip_equal(op.getBody()->getArguments(), outerTargets)) { + if (llvm::is_contained(innerTargets, arg)) { + targets.push_back(qubit); + } else { + controls.push_back(qubit); + } + } + + rewriter.replaceOpWithNewOp( + op, controls, targets, [&](ValueRange targetArgs) { + auto* innerCtrlBody = innerCtrlOp.getBody(); + IRMapping mapping; + utils::populateMapping(mapping, *innerCtrlBody, innerTargets, + outerTargets, targets, targetArgs); + for (auto& op : innerCtrlBody->without_terminator()) { + rewriter.clone(op, mapping); + } + }); return success(); } @@ -63,16 +92,29 @@ struct ReduceCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - auto* bodyUnitary = op.getBodyUnitary().getOperation(); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto* innerOp = op.getBodyUnitary(0).getOperation(); + // Inline ops from empty control modifiers, IdOp and BarrierOp - if (op.getNumControls() == 0 || isa(bodyUnitary)) { - rewriter.moveOpBefore(bodyUnitary, op); - rewriter.replaceOp(op, bodyUnitary->getResults()); + if (op.getNumControls() == 0 || isa(innerOp)) { + const auto numTargets = op.getNumTargets(); + auto outerTargets = op.getTargets(); + SmallVector targets; + for (auto target : innerOp->getOperands().take_front(numTargets)) { + targets.push_back( + utils::getValueFromBlockArgument(target, outerTargets)); + } + + rewriter.moveOpBefore(innerOp, op); + innerOp->setOperands(0, numTargets, targets); + rewriter.eraseOp(op); return success(); } // The remaining code explicitly handles GPhaseOp and nothing else - auto gPhaseOp = dyn_cast(bodyUnitary); + auto gPhaseOp = dyn_cast(innerOp); if (!gPhaseOp) { return failure(); } @@ -84,30 +126,59 @@ struct ReduceCtrl final : OpRewritePattern { return success(); } - // Remove the last control and replace with a single POp with the removed - // control as target - auto controls = op.getControls(); - auto target = controls.back(); - controls = controls.drop_back(); - op->setOperands(controls); + // Adjust the segment sizes of the control and target operands + const auto opSegmentsAttrName = CtrlOp::getOperandSegmentSizeAttr(); + auto segmentsAttr = + op->getAttrOfType(opSegmentsAttrName); + auto newSegments = DenseI32ArrayAttr::get( + rewriter.getContext(), {segmentsAttr[0] - 1, segmentsAttr[1] + 1}); + op->setAttr(opSegmentsAttrName, newSegments); + + // Add a block argument for the target qubit + auto arg = op.getBody()->addArgument(QubitType::get(rewriter.getContext()), + op.getLoc()); + // Replace the current GPhaseOp with a PhaseOp const OpBuilder::InsertionGuard guard(rewriter); rewriter.setInsertionPoint(gPhaseOp); - rewriter.replaceOpWithNewOp(gPhaseOp, target, gPhaseOp.getTheta()); + POp::create(rewriter, gPhaseOp.getLoc(), arg, gPhaseOp.getTheta()); + rewriter.eraseOp(gPhaseOp); return success(); } }; +/** + * @brief Erase control modifiers that do not have any body unitaries. + */ +struct EraseEmptyCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + if (op.getNumBodyUnitaries() != 0) { + return failure(); + } + + rewriter.eraseOp(op); + return success(); + } +}; + } // namespace -UnitaryOpInterface CtrlOp::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())); +size_t CtrlOp::getNumBodyUnitaries() { + return llvm::count_if( + *getBody(), [](Operation& op) { return isa(op); }); +} + +UnitaryOpInterface CtrlOp::getBodyUnitary(const size_t i) { + auto unitaries = llvm::make_filter_range( + *getBody(), [](Operation& op) { return isa(op); }); + auto it = std::next(unitaries.begin(), static_cast(i)); + if (it == unitaries.end()) { + llvm::reportFatalUsageError("Unitary index out of bounds"); + } + return cast(*it); } Value CtrlOp::getQubit(const size_t i) { @@ -116,9 +187,9 @@ Value CtrlOp::getQubit(const size_t i) { return getControls()[i]; } if (numControls <= i && i < getNumQubits()) { - return getBodyUnitary().getQubit(i - numControls); + return getTarget(i - numControls); } - llvm::reportFatalUsageError("Invalid qubit index"); + llvm::reportFatalUsageError("Qubit index out of bounds"); } Value CtrlOp::getControl(const size_t i) { @@ -129,37 +200,33 @@ Value CtrlOp::getControl(const size_t i) { } void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, - ValueRange controls, - const function_ref& bodyBuilder) { - const OpBuilder::InsertionGuard guard(odsBuilder); - odsState.addOperands(controls); - auto* region = odsState.addRegion(); - auto& block = region->emplaceBlock(); + ValueRange controls, ValueRange targets, + const function_ref& body) { + build(odsBuilder, odsState, controls, targets); + auto& block = odsState.regions.front()->emplaceBlock(); + + auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < targets.size(); ++i) { + block.addArgument(qubitType, odsState.location); + } + const OpBuilder::InsertionGuard guard(odsBuilder); odsBuilder.setInsertionPointToStart(&block); - bodyBuilder(); + body(block.getArguments()); YieldOp::create(odsBuilder, odsState.location); } LogicalResult CtrlOp::verify() { auto& block = *getBody(); - if (block.getOperations().size() < 2) { - return emitOpError("body region must have at least two operations"); + if (llvm::any_of(*getBody(), [](Operation& op) { + return isa(op); + })) { + return emitOpError("body must not contain non-unitary quantum 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"); - } - } SmallPtrSet uniqueQubits; for (const auto& control : getControls()) { @@ -167,11 +234,9 @@ LogicalResult CtrlOp::verify() { return emitOpError("duplicate control qubit found"); } } - auto bodyUnitary = getBodyUnitary(); - const auto numQubits = bodyUnitary.getNumQubits(); - for (size_t i = 0; i < numQubits; i++) { - if (!uniqueQubits.insert(bodyUnitary.getQubit(i)).second) { - return emitOpError("duplicate qubit found"); + for (const auto& target : getTargets()) { + if (!uniqueQubits.insert(target).second) { + return emitOpError("duplicate target qubit found"); } } @@ -180,5 +245,5 @@ LogicalResult CtrlOp::verify() { void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } diff --git a/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp b/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp index 065fe431be..b1cf6e46ae 100644 --- a/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QC/IR/Modifiers/InvOp.cpp @@ -8,17 +8,24 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/QC/IR/QCOps.h" +#include "mlir/Dialect/Utils/Utils.h" +#include #include +#include #include #include #include +#include #include #include #include #include +#include +#include #include using namespace mlir; @@ -33,20 +40,36 @@ namespace { struct MoveCtrlOutside final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(InvOp invOp, + LogicalResult matchAndRewrite(InvOp op, PatternRewriter& rewriter) const override { - auto bodyUnitary = invOp.getBodyUnitary(); - auto innerCtrlOp = dyn_cast(bodyUnitary.getOperation()); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto innerCtrlOp = dyn_cast(op.getBodyUnitary(0).getOperation()); if (!innerCtrlOp) { return failure(); } - auto controls = innerCtrlOp.getControls(); - rewriter.replaceOpWithNewOp(invOp, controls, [&] { - InvOp::create(rewriter, invOp.getLoc(), [&] { - rewriter.clone(*innerCtrlOp.getBodyUnitary().getOperation()); - }); - }); + const auto numControls = innerCtrlOp.getNumControls(); + const auto numTargets = innerCtrlOp.getNumTargets(); + auto outerQubits = op.getQubits(); + auto controls = outerQubits.take_front(numControls); + auto targets = outerQubits.take_back(numTargets); + + rewriter.replaceOpWithNewOp( + op, controls, targets, [&](ValueRange targetArgs) { + InvOp::create( + rewriter, op.getLoc(), targetArgs, [&](ValueRange qubitArgs) { + auto* innerCtrlBody = innerCtrlOp.getBody(); + IRMapping mapping; + utils::populateMapping(mapping, *innerCtrlBody, + innerCtrlOp.getTargets(), outerQubits, + targets, qubitArgs); + for (auto& op : innerCtrlBody->without_terminator()) { + rewriter.clone(op, mapping); + } + }); + }); return success(); } @@ -62,13 +85,24 @@ struct InlineSelfAdjoint final : OpRewritePattern { LogicalResult matchAndRewrite(InvOp op, PatternRewriter& rewriter) const override { - auto* innerOp = op.getBodyUnitary().getOperation(); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto* innerOp = op.getBodyUnitary(0).getOperation(); if (!isa(innerOp)) { return failure(); } + const auto numQubits = op.getNumQubits(); + auto outerQubits = op.getQubits(); + SmallVector qubits; + for (auto qubit : innerOp->getOperands().take_front(numQubits)) { + qubits.push_back(utils::getValueFromBlockArgument(qubit, outerQubits)); + } + rewriter.moveOpBefore(innerOp, op); + innerOp->setOperands(0, numQubits, qubits); rewriter.replaceOp(op, innerOp->getResults()); return success(); } @@ -85,143 +119,181 @@ struct ReplaceWithKnownGates final : OpRewritePattern { LogicalResult matchAndRewrite(InvOp op, PatternRewriter& rewriter) const override { - auto* innerOp = op.getBodyUnitary().getOperation(); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto* innerOp = op.getBodyUnitary(0).getOperation(); + + auto loc = op.getLoc(); + auto outerQubits = op.getQubits(); return TypeSwitch(innerOp) .Case([&](auto g) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), g.getTheta()); + Value negTheta = arith::NegFOp::create(rewriter, loc, g.getTheta()); rewriter.replaceOpWithNewOp(op, negTheta); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + .Case([&](auto t) { + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(t.getTarget(0), outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + .Case([&](auto tdg) { + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(tdg.getTarget(0), outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + .Case([&](auto s) { + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(s.getTarget(0), outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + .Case([&](auto sdg) { + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(sdg.getTarget(0), outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + .Case([&](auto sx) { + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(sx.getTarget(0), outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getTarget(0)); + .Case([&](auto sxdg) { + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(sxdg.getTarget(0), outerQubits)); return success(); }) .Case([&](auto p) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), p.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, p.getTheta()); + rewriter.replaceOpWithNewOp( + op, utils::getValueFromBlockArgument(p.getTarget(0), outerQubits), + negTheta); return success(); }) .Case([&](auto r) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), r.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), negTheta, - r.getPhi()); + auto negTheta = arith::NegFOp::create(rewriter, loc, r.getTheta()); + rewriter.replaceOpWithNewOp( + op, utils::getValueFromBlockArgument(r.getTarget(0), outerQubits), + negTheta, r.getPhi()); return success(); }) .Case([&](auto rx) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rx.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rx.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rx.getTarget(0), outerQubits), + negTheta); return success(); }) .Case([&](auto u) { - Value newPhi = - arith::NegFOp::create(rewriter, op.getLoc(), u.getLambda()); - Value newLambda = - arith::NegFOp::create(rewriter, op.getLoc(), u.getPhi()); - Value newTheta = - arith::NegFOp::create(rewriter, op.getLoc(), u.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), newTheta, - newPhi, newLambda); + Value newPhi = arith::NegFOp::create(rewriter, loc, u.getLambda()); + Value newLambda = arith::NegFOp::create(rewriter, loc, u.getPhi()); + Value newTheta = arith::NegFOp::create(rewriter, loc, u.getTheta()); + rewriter.replaceOpWithNewOp( + op, utils::getValueFromBlockArgument(u.getTarget(0), outerQubits), + newTheta, newPhi, newLambda); return success(); }) - .Case([&](auto u) { - auto pi = arith::ConstantOp::create( - rewriter, op.getLoc(), - rewriter.getF64FloatAttr(std::numbers::pi)); - Value newPhi = - arith::NegFOp::create(rewriter, op.getLoc(), u.getLambda()); - newPhi = arith::SubFOp::create(rewriter, op.getLoc(), newPhi, pi); - Value newLambda = - arith::NegFOp::create(rewriter, op.getLoc(), u.getPhi()); - newLambda = - arith::AddFOp::create(rewriter, op.getLoc(), newLambda, pi); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), newPhi, - newLambda); + .Case([&](auto u2) { + Value pi = arith::ConstantOp::create( + rewriter, loc, rewriter.getF64FloatAttr(std::numbers::pi)); + Value newPhi = arith::NegFOp::create(rewriter, loc, u2.getLambda()); + newPhi = arith::SubFOp::create(rewriter, loc, newPhi, pi); + Value newLambda = arith::NegFOp::create(rewriter, loc, u2.getPhi()); + newLambda = arith::AddFOp::create(rewriter, loc, newLambda, pi); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(u2.getTarget(0), outerQubits), + newPhi, newLambda); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getTarget(1), - op.getTarget(0)); + .Case([&](auto dcx) { + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(dcx.getTarget(1), outerQubits), + utils::getValueFromBlockArgument(dcx.getTarget(0), outerQubits)); return success(); }) .Case([&](auto rxx) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rxx.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), - op.getTarget(1), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rxx.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rxx.getTarget(0), outerQubits), + utils::getValueFromBlockArgument(rxx.getTarget(1), outerQubits), + negTheta); return success(); }) .Case([&](auto ry) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), ry.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, ry.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(ry.getTarget(0), outerQubits), + negTheta); return success(); }) .Case([&](auto ryy) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), ryy.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), - op.getTarget(1), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, ryy.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(ryy.getTarget(0), outerQubits), + utils::getValueFromBlockArgument(ryy.getTarget(1), outerQubits), + negTheta); return success(); }) .Case([&](auto rz) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rz.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rz.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rz.getTarget(0), outerQubits), + negTheta); return success(); }) .Case([&](auto rzx) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rzx.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), - op.getTarget(1), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rzx.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rzx.getTarget(0), outerQubits), + utils::getValueFromBlockArgument(rzx.getTarget(1), outerQubits), + negTheta); return success(); }) .Case([&](auto rzz) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rzz.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), - op.getTarget(1), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rzz.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rzz.getTarget(0), outerQubits), + utils::getValueFromBlockArgument(rzz.getTarget(1), outerQubits), + negTheta); return success(); }) .Case([&](auto xxminusyy) { - Value negTheta = arith::NegFOp::create(rewriter, op.getLoc(), - xxminusyy.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), - op.getTarget(1), negTheta, - xxminusyy.getBeta()); + Value negTheta = + arith::NegFOp::create(rewriter, loc, xxminusyy.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(xxminusyy.getTarget(0), + outerQubits), + utils::getValueFromBlockArgument(xxminusyy.getTarget(1), + outerQubits), + negTheta, xxminusyy.getBeta()); return success(); }) .Case([&](auto xxplusyy) { Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), xxplusyy.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getTarget(0), - op.getTarget(1), negTheta, - xxplusyy.getBeta()); + arith::NegFOp::create(rewriter, loc, xxplusyy.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(xxplusyy.getTarget(0), + outerQubits), + utils::getValueFromBlockArgument(xxplusyy.getTarget(1), + outerQubits), + negTheta, xxplusyy.getBeta()); return success(); }) .Default([&](auto) { return failure(); }); @@ -233,68 +305,104 @@ struct ReplaceWithKnownGates final : OpRewritePattern { */ struct CancelNestedInv final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(InvOp invOp, + LogicalResult matchAndRewrite(InvOp op, PatternRewriter& rewriter) const override { - auto innerUnitary = invOp.getBodyUnitary(); - auto innerInvOp = dyn_cast(innerUnitary.getOperation()); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto innerInvOp = dyn_cast(op.getBodyUnitary(0).getOperation()); if (!innerInvOp) { return failure(); } - auto* innerInnerUnitary = innerInvOp.getBodyUnitary().getOperation(); - rewriter.moveOpBefore(innerInnerUnitary, invOp); - rewriter.replaceOp(invOp, innerInnerUnitary->getResults()); + if (innerInvOp.getNumBodyUnitaries() != 1) { + return failure(); + } + auto* innerInnerOp = innerInvOp.getBodyUnitary(0).getOperation(); + + const auto numQubits = op.getNumQubits(); + auto outerQubits = op.getQubits(); + auto innerQubits = innerInvOp.getQubits(); + SmallVector qubits; + for (auto qubit : innerInnerOp->getOperands().take_front(numQubits)) { + auto innerQubit = utils::getValueFromBlockArgument(qubit, innerQubits); + qubits.push_back( + utils::getValueFromBlockArgument(innerQubit, outerQubits)); + } + + rewriter.moveOpBefore(innerInnerOp, op); + innerInnerOp->setOperands(0, numQubits, qubits); + rewriter.replaceOp(op, innerInnerOp->getResults()); + return success(); + } +}; + +/** + * @brief Erase inverse modifiers that do not have any body unitaries. + */ +struct EraseEmptyInv final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(InvOp op, + PatternRewriter& rewriter) const override { + if (op.getNumBodyUnitaries() != 0) { + return failure(); + } + rewriter.eraseOp(op); return success(); } }; } // namespace -UnitaryOpInterface InvOp::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())); +size_t InvOp::getNumBodyUnitaries() { + return llvm::count_if( + *getBody(), [](Operation& op) { return isa(op); }); +} + +UnitaryOpInterface InvOp::getBodyUnitary(const size_t i) { + auto unitaries = llvm::make_filter_range( + *getBody(), [](Operation& op) { return isa(op); }); + auto it = std::next(unitaries.begin(), static_cast(i)); + if (it == unitaries.end()) { + llvm::reportFatalUsageError("Unitary index out of bounds"); + } + return cast(*it); } void InvOp::build(OpBuilder& odsBuilder, OperationState& odsState, - const function_ref& bodyBuilder) { - const OpBuilder::InsertionGuard guard(odsBuilder); - auto* region = odsState.addRegion(); - auto& block = region->emplaceBlock(); + ValueRange qubits, + const function_ref& body) { + build(odsBuilder, odsState, qubits); + auto& block = odsState.regions.front()->emplaceBlock(); + auto qubitType = QubitType::get(odsBuilder.getContext()); + for (size_t i = 0; i < qubits.size(); ++i) { + block.addArgument(qubitType, odsState.location); + } + + const OpBuilder::InsertionGuard guard(odsBuilder); odsBuilder.setInsertionPointToStart(&block); - bodyBuilder(); + body(block.getArguments()); YieldOp::create(odsBuilder, odsState.location); } LogicalResult InvOp::verify() { auto& block = *getBody(); - if (block.getOperations().size() < 2) { - return emitOpError("body region must have at least two operations"); + if (llvm::any_of(*getBody(), [](Operation& op) { + return isa(op); + })) { + return emitOpError("body must not contain non-unitary quantum 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 InvOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); + ReplaceWithKnownGates, EraseEmptyInv>(context); } diff --git a/mlir/lib/Dialect/QC/IR/QCOps.cpp b/mlir/lib/Dialect/QC/IR/QCOps.cpp index 5b93c2ebaa..6a72833861 100644 --- a/mlir/lib/Dialect/QC/IR/QCOps.cpp +++ b/mlir/lib/Dialect/QC/IR/QCOps.cpp @@ -11,6 +11,14 @@ #include "mlir/Dialect/QC/IR/QCOps.h" #include "mlir/Dialect/QC/IR/QCDialect.h" // IWYU pragma: associated +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include // The following headers are needed for some template instantiations. // IWYU pragma: begin_keep @@ -21,6 +29,17 @@ using namespace mlir; using namespace mlir::qc; +static ParseResult +parseTargetAliasing(OpAsmParser& parser, Region& region, + SmallVectorImpl& operands) { + return utils::parseTargetAliasing(parser, region, operands); +} + +static void printTargetAliasing(OpAsmPrinter& printer, Operation* /*op*/, + Region& region, OperandRange targetsIn) { + utils::printTargetAliasing(printer, region, targetsIn); +} + //===----------------------------------------------------------------------===// // Dialect //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QC/Translation/CMakeLists.txt b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt index a514f33df6..c1d403de3a 100644 --- a/mlir/lib/Dialect/QC/Translation/CMakeLists.txt +++ b/mlir/lib/Dialect/QC/Translation/CMakeLists.txt @@ -9,13 +9,15 @@ add_mlir_library( MLIRQCTranslation TranslateQuantumComputationToQC.cpp + TranslateQASM3ToQC.cpp LINK_LIBS MLIRArithDialect MLIRFuncDialect MLIRSCFDialect MLIRQCDialect MLIRQCProgramBuilder - MQT::CoreIR) + MQT::CoreIR + MQT::CoreQASM) mqt_mlir_target_use_project_options(MLIRQCTranslation) diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp new file mode 100644 index 0000000000..0415bf2a3a --- /dev/null +++ b/mlir/lib/Dialect/QC/Translation/TranslateQASM3ToQC.cpp @@ -0,0 +1,1042 @@ +/* + * 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/Translation/TranslateQASM3ToQC.h" + +#include "ir/Definitions.hpp" +#include "ir/operations/OpType.hpp" +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" +#include "mlir/Dialect/QC/IR/QCOps.h" +#include "qasm3/Exception.hpp" +#include "qasm3/Gate.hpp" +#include "qasm3/InstVisitor.hpp" +#include "qasm3/NestedEnvironment.hpp" +#include "qasm3/Parser.hpp" +#include "qasm3/Statement.hpp" +#include "qasm3/StdGates.hpp" +#include "qasm3/Types.hpp" +#include "qasm3/passes/ConstEvalPass.hpp" +#include "qasm3/passes/TypeCheckPass.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qc { + +namespace { + +/// Signature: (builder, gate-operands, evaluated-parameters). +/// For gates with implicit controls (cx, ccx, ...) all qubits including +/// the controls are in the qubits array, matching QASM3 operand order. +using GateFn = + std::function)>; + +} // namespace + +/** + * @brief Build the static gate-name → GateFn dispatch table. + * + * @details Each entry maps a QASM3 gate identifier to a lambda that emits the + * corresponding QC dialect op via QCProgramBuilder. + */ +static llvm::StringMap buildGateDispatch() { + llvm::StringMap d; + + // 0-target, 1-param + d["gphase"] = [](auto& b, auto /*q*/, auto p) { b.gphase(p[0]); }; + + // 1-target, 0-param + d["id"] = [](auto& b, auto q, auto) { b.id(q[0]); }; + d["x"] = [](auto& b, auto q, auto) { b.x(q[0]); }; + d["y"] = [](auto& b, auto q, auto) { b.y(q[0]); }; + d["z"] = [](auto& b, auto q, auto) { b.z(q[0]); }; + d["h"] = [](auto& b, auto q, auto) { b.h(q[0]); }; + d["s"] = [](auto& b, auto q, auto) { b.s(q[0]); }; + d["sdg"] = [](auto& b, auto q, auto) { b.sdg(q[0]); }; + d["t"] = [](auto& b, auto q, auto) { b.t(q[0]); }; + d["tdg"] = [](auto& b, auto q, auto) { b.tdg(q[0]); }; + d["sx"] = [](auto& b, auto q, auto) { b.sx(q[0]); }; + d["sxdg"] = [](auto& b, auto q, auto) { b.sxdg(q[0]); }; + + // 1-target, 1-param + d["rx"] = [](auto& b, auto q, auto p) { b.rx(p[0], q[0]); }; + d["ry"] = [](auto& b, auto q, auto p) { b.ry(p[0], q[0]); }; + d["rz"] = [](auto& b, auto q, auto p) { b.rz(p[0], q[0]); }; + d["p"] = [](auto& b, auto q, auto p) { b.p(p[0], q[0]); }; + d["u1"] = [](auto& b, auto q, auto p) { b.p(p[0], q[0]); }; // alias + d["phase"] = [](auto& b, auto q, auto p) { b.p(p[0], q[0]); }; // alias + + // 1-target, 2-param + d["r"] = [](auto& b, auto q, auto p) { b.r(p[0], p[1], q[0]); }; + d["u2"] = [](auto& b, auto q, auto p) { b.u2(p[0], p[1], q[0]); }; + + // 1-target, 3-param + d["U"] = [](auto& b, auto q, auto p) { b.u(p[0], p[1], p[2], q[0]); }; + d["u3"] = [](auto& b, auto q, auto p) { + b.u(p[0], p[1], p[2], q[0]); + }; // alias + d["u"] = [](auto& b, auto q, auto p) { + b.u(p[0], p[1], p[2], q[0]); + }; // alias + + // 1-ctrl + 1-target, 0-param (q[0]=ctrl, q[1]=target) + d["cx"] = [](auto& b, auto q, auto) { b.cx(q[0], q[1]); }; + d["cnot"] = [](auto& b, auto q, auto) { b.cx(q[0], q[1]); }; // alias + d["cy"] = [](auto& b, auto q, auto) { b.cy(q[0], q[1]); }; + d["cz"] = [](auto& b, auto q, auto) { b.cz(q[0], q[1]); }; + d["ch"] = [](auto& b, auto q, auto) { b.ch(q[0], q[1]); }; + d["csx"] = [](auto& b, auto q, auto) { b.csx(q[0], q[1]); }; + + // 1-ctrl + 1-target, 1-param + d["crx"] = [](auto& b, auto q, auto p) { b.crx(p[0], q[0], q[1]); }; + d["cry"] = [](auto& b, auto q, auto p) { b.cry(p[0], q[0], q[1]); }; + d["crz"] = [](auto& b, auto q, auto p) { b.crz(p[0], q[0], q[1]); }; + d["cp"] = [](auto& b, auto q, auto p) { b.cp(p[0], q[0], q[1]); }; + d["cphase"] = [](auto& b, auto q, auto p) { + b.cp(p[0], q[0], q[1]); + }; // alias + + // 2-ctrl + 1-target, 0-param (q[0],q[1]=ctrl, q[2]=target) + d["ccx"] = [](auto& b, auto q, auto) { b.mcx({q[0], q[1]}, q[2]); }; + d["toffoli"] = [](auto& b, auto q, auto) { + b.mcx({q[0], q[1]}, q[2]); + }; // alias + d["ccz"] = [](auto& b, auto q, auto) { b.mcz({q[0], q[1]}, q[2]); }; + + // 2-target, 0-param + d["swap"] = [](auto& b, auto q, auto) { b.swap(q[0], q[1]); }; + d["iswap"] = [](auto& b, auto q, auto) { b.iswap(q[0], q[1]); }; + d["dcx"] = [](auto& b, auto q, auto) { b.dcx(q[0], q[1]); }; + d["ecr"] = [](auto& b, auto q, auto) { b.ecr(q[0], q[1]); }; + + // 1-ctrl + 2-target, 0-param (q[0]=ctrl, q[1],q[2]=targets) + d["cswap"] = [](auto& b, auto q, auto) { b.cswap(q[0], q[1], q[2]); }; + d["fredkin"] = [](auto& b, auto q, auto) { + b.cswap(q[0], q[1], q[2]); + }; // alias + + // 2-target, 2-param + d["xx_plus_yy"] = [](auto& b, auto q, auto p) { + b.xx_plus_yy(p[0], p[1], q[0], q[1]); + }; + d["xx_minus_yy"] = [](auto& b, auto q, auto p) { + b.xx_minus_yy(p[0], p[1], q[0], q[1]); + }; + + // 2-target, 1-param + d["rxx"] = [](auto& b, auto q, auto p) { b.rxx(p[0], q[0], q[1]); }; + d["ryy"] = [](auto& b, auto q, auto p) { b.ryy(p[0], q[0], q[1]); }; + d["rzx"] = [](auto& b, auto q, auto p) { b.rzx(p[0], q[0], q[1]); }; + d["rzz"] = [](auto& b, auto q, auto p) { b.rzz(p[0], q[0], q[1]); }; + + // MCX variants: q[0..N-2] are controls, q[N-1] is the target. + // These are not in stdgates.inc but are widely used (Qiskit-style). + auto mcxFn = [](auto& b, auto q, auto) { b.mcx(q.drop_back(1), q.back()); }; + d["mcx"] = mcxFn; + d["mcx_gray"] = mcxFn; + d["mcphase"] = [](auto& b, auto q, auto p) { + b.mcp(p[0], q.drop_back(1), q.back()); + }; + // vchain/recursive carry ancilla qubits; strip them using Qiskit's formula + d["mcx_vchain"] = [](auto& b, auto q, auto) { + const size_t n = q.size() - ((q.size() + 1) / 2) + 2; + b.mcx(q.slice(0, n - 1), q[n - 1]); + }; + d["mcx_recursive"] = [](auto& b, auto q, auto) { + const size_t n = (q.size() > 5) ? q.size() - 1 : q.size(); + b.mcx(q.slice(0, n - 1), q[n - 1]); + }; + + return d; +} + +namespace { + +/// Static gate dispatch table, built once at startup. +const llvm::StringMap GATE_DISPATCH = buildGateDispatch(); + +/// Local qubit scope used during compound gate body expansion. +/// Maps argument name → vector of MLIR qubit Values. +using QubitScope = llvm::StringMap>; + +/** + * @brief AST visitor that translates a QASM3 program directly into the QC + * dialect. + * + * @details Implements qasm3::InstVisitor to walk the AST produced by + * qasm3::Parser and emit QC dialect ops via QCProgramBuilder, bypassing + * qc::QuantumComputation. Const-evaluation and type-checking passes run in + * lock-step with the walk. + */ +class MLIRQasmImporter final : public qasm3::InstVisitor { +public: + explicit MLIRQasmImporter(MLIRContext* ctx) + : builder(ctx), typeCheckPass(constEvalPass), + gates(qasm3::STANDARD_GATES) { + initBuiltins(); + builder.initialize(); + } + + void + visitProgram(const std::vector>& program) { + for (const auto& stmt : program) { + constEvalPass.processStatement(*stmt); + typeCheckPass.processStatement(*stmt); + stmt->accept(this); + } + } + + OwningOpRef finalize() { return builder.finalize(); } + +private: + QCProgramBuilder builder; + qasm3::const_eval::ConstEvalPass constEvalPass; + qasm3::type_checking::TypeCheckPass typeCheckPass; + qasm3::NestedEnvironment> + declarations; + + /// Top-level qubit registers: register name → SSA Values + QubitScope qubitRegisters; + + /// Classical bit registers: register name → ClassicalRegister + llvm::StringMap classicalRegisters; + + /// Measurement result tracking: register name → vector of i1 Values. + /// Updated each time a measure is emitted. Used for if/else conditions. + llvm::StringMap> bitValues; + + /// Gate library: standard gates + user-defined compound gates + std::map> gates; + + bool openQASM2CompatMode{false}; + + //===--- Initialization -----------------------------------------------===// + + /// Seed the const-eval and type-check passes with QASM3 built-in constants + /// (pi, euler, tau) and prime the type environment with their types. + void initBuiltins() { + using namespace qasm3::const_eval; + using namespace qasm3::type_checking; + auto floatTy = InferredType{std::dynamic_pointer_cast( + std::make_shared>(qasm3::Float, 64))}; + + auto addConstant = [&](const std::string& name, double val) { + constEvalPass.addConst(name, ConstEvalValue(val)); + typeCheckPass.addBuiltin(name, floatTy); + }; + + addConstant("pi", ::qc::PI); + addConstant("π", ::qc::PI); + addConstant("tau", ::qc::TAU); + addConstant("τ", ::qc::TAU); + addConstant("euler", ::qc::E); + addConstant("ℇ", ::qc::E); + + // MCX variants: not in OQ3 stdlib, variable qubit arity. + // GateInfo fields are not arity-checked for StandardGate in this importer; + // the GATE_DISPATCH lambdas handle the variable-arity logic at call time. + const qasm3::GateInfo mcxInfo{.nControls = 0, + .nTargets = 0, + .nParameters = 0, + .type = ::qc::OpType::X}; + const qasm3::GateInfo mcphaseInfo{.nControls = 0, + .nTargets = 0, + .nParameters = 1, + .type = ::qc::OpType::P}; + gates["mcx"] = std::make_shared(mcxInfo); + gates["mcx_gray"] = std::make_shared(mcxInfo); + gates["mcx_vchain"] = std::make_shared(mcxInfo); + gates["mcx_recursive"] = std::make_shared(mcxInfo); + gates["mcphase"] = std::make_shared(mcphaseInfo); + } + +public: + //===--- InstVisitor overrides ----------------------------------------===// + + void visitVersionDeclaration(const std::shared_ptr + versionDeclaration) override { + if (versionDeclaration->version < 3) { + openQASM2CompatMode = true; + } + } + + void visitDeclarationStatement( + std::shared_ptr stmt) override { + const auto& id = stmt->identifier; + if (declarations.find(id).has_value()) { + throw qasm3::CompilerError("Identifier '" + id + "' already declared.", + stmt->debugInfo); + } + declarations.emplace(id, stmt); + + const auto ty = std::get<1>(stmt->type); + const auto sizedTy = + std::dynamic_pointer_cast>(ty); + if (!sizedTy) { + throw qasm3::CompilerError("Only sized types are supported.", + stmt->debugInfo); + } + const auto size = static_cast(sizedTy->getDesignator()); + + switch (sizedTy->type) { + case qasm3::Qubit: { + auto reg = builder.allocQubitRegister(size); + SmallVector qubits; + qubits.reserve(static_cast(size)); + for (int64_t i = 0; i < size; ++i) { + qubits.push_back(reg[static_cast(i)]); + } + qubitRegisters[id] = std::move(qubits); + break; + } + case qasm3::Bit: + case qasm3::Int: + case qasm3::Uint: { + classicalRegisters[id] = builder.allocClassicalBitRegister(size, id); + break; + } + default: + throw qasm3::CompilerError("Unsupported declaration type.", + stmt->debugInfo); + } + + // Handle initializer (measure only) + if (stmt->expression) { + const auto& expr = stmt->expression->expression; + if (const auto measureExpr = + std::dynamic_pointer_cast(expr)) { + auto lhsId = std::make_shared(id); + visitMeasureAssignment(lhsId, measureExpr, stmt->debugInfo); + return; + } + if (stmt->isConst) { + return; // nothing to emit + } + throw qasm3::CompilerError( + "Only measure statements are supported as initializers.", + stmt->debugInfo); + } + } + + void visitAssignmentStatement( + std::shared_ptr stmt) override { + const auto& id = stmt->identifier->identifier; + assert(declarations.find(id).has_value() && "Checked by type check pass"); + assert(!declarations.find(id)->get()->isConst && + "Checked by type check pass"); + + const auto& expr = stmt->expression->expression; + if (const auto measureExpr = + std::dynamic_pointer_cast(expr)) { + visitMeasureAssignment(stmt->identifier, measureExpr, stmt->debugInfo); + return; + } + + throw qasm3::CompilerError("Classical computation not yet supported.", + stmt->debugInfo); + } + + void + visitGateStatement(std::shared_ptr stmt) override { + auto id = stmt->identifier; + if (stmt->isOpaque) { + if (!gates.contains(id)) { + throw qasm3::CompilerError("Unsupported opaque gate '" + id + "'.", + stmt->debugInfo); + } + return; + } + if (gates.contains(id)) { + if (std::dynamic_pointer_cast(gates[id])) { + return; // ignore redeclaration of standard gate + } + throw qasm3::CompilerError("Gate '" + id + "' already declared.", + stmt->debugInfo); + } + std::vector paramNames; + for (const auto& p : stmt->parameters->identifiers) { + if (std::ranges::find(paramNames, p->identifier) != paramNames.end()) { + throw qasm3::CompilerError("Parameter '" + p->identifier + + "' already declared in gate '" + id + + "'.", + stmt->debugInfo); + } + paramNames.push_back(p->identifier); + } + std::vector qubitNames; + for (const auto& q : stmt->qubits->identifiers) { + if (std::ranges::find(qubitNames, q->identifier) != qubitNames.end()) { + throw qasm3::CompilerError("Qubit '" + q->identifier + + "' already declared in gate '" + id + + "'.", + stmt->debugInfo); + } + qubitNames.push_back(q->identifier); + } + gates[id] = std::make_shared( + std::move(paramNames), std::move(qubitNames), stmt->statements); + } + + void visitGateCallStatement( + std::shared_ptr stmt) override { + applyGateCallStatement(stmt, qubitRegisters); + } + + /** + * @brief Emit measure ops for \p target = measure \p measureExpr. + * + * @details Handles both full-register and single-bit assignments; wires + * measure results into the classical register's bit-value map for later use. + */ + void visitMeasureAssignment( + const std::shared_ptr& target, + const std::shared_ptr& measureExpr, + const std::shared_ptr& debugInfo) { + auto qubits = resolveGateOperand(measureExpr->gate, debugInfo); + auto bits = resolveClassicalBits(target, debugInfo); + if (qubits.size() != bits.size()) { + throw qasm3::CompilerError( + "Classical and quantum registers must have the same width in measure " + "statement. Classical register '" + + target->identifier + "' has " + std::to_string(bits.size()) + + " bits, but quantum register '" + measureExpr->gate->getName() + + "' has " + std::to_string(qubits.size()) + " qubits.", + debugInfo); + } + for (size_t i = 0; i < qubits.size(); ++i) { + // Use the MeasureOp directly to capture the i1 result for if/else + auto result = + MeasureOp::create(builder, qubits[i], + builder.getStringAttr(bits[i].registerName), + builder.getI64IntegerAttr(bits[i].registerSize), + builder.getI64IntegerAttr(bits[i].registerIndex)) + .getResult(); + + // Track the result for use in if/else conditions + const auto& regName = bits[i].registerName; + auto& regBits = bitValues[regName]; + const auto idx = static_cast(bits[i].registerIndex); + if (regBits.size() <= idx) { + regBits.resize(idx + 1); + } + regBits[idx] = result; + } + } + + void visitBarrierStatement( + std::shared_ptr stmt) override { + SmallVector qubits; + for (const auto& gate : stmt->gates) { + auto resolved = resolveGateOperand(gate, stmt->debugInfo); + qubits.append(resolved.begin(), resolved.end()); + } + builder.barrier(qubits); + } + + void + visitResetStatement(std::shared_ptr stmt) override { + for (auto q : resolveGateOperand(stmt->gate, stmt->debugInfo)) { + builder.reset(q); + } + } + + void visitIfStatement(std::shared_ptr stmt) override { + if (stmt->thenStatements.empty() && stmt->elseStatements.empty()) { + return; + } + + auto condition = translateCondition(stmt->condition, stmt->debugInfo); + const bool hasElse = !stmt->elseStatements.empty(); + + auto ifOp = + scf::IfOp::create(builder, condition, /*withElseRegion=*/hasElse); + + // Then block + builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); + emitBlockStatements(stmt->thenStatements, stmt->debugInfo); + + // Else block + if (hasElse) { + builder.setInsertionPointToStart(&ifOp.getElseRegion().front()); + emitBlockStatements(stmt->elseStatements, stmt->debugInfo); + } + + // Restore insertion point after the if op + builder.setInsertionPointAfter(ifOp); + } + + void + visitInitialLayout(std::shared_ptr layout) override { + throw qasm3::CompilerError( + "Initial layout pragmas are not supported in direct MLIR import.", + layout->debugInfo); + } + + void visitOutputPermutation( + std::shared_ptr perm) override { + throw qasm3::CompilerError( + "Output permutation pragmas are not supported in direct MLIR import.", + perm->debugInfo); + } + + //===--- Core gate application ----------------------------------------===// + + /// Apply a gate call statement, resolving qubits from \p scope. + /// For top-level calls pass \p qubitRegisters; for compound gate bodies + /// pass the local argument scope. + void + applyGateCallStatement(const std::shared_ptr& stmt, + const QubitScope& scope) { + const auto& id = stmt->identifier; + + auto it = gates.find(id); + // `resolvedId` may differ from `id` when OQ2 compat strips 'c' prefixes. + std::string resolvedId = id; + size_t implicitCompatControls = 0; + + // OQ2 compat mode: strip leading 'c' prefixes and treat each as + // an additional positive control. E.g. "cmygate q0, q1" with compat on + // becomes ctrl @ mygate q0, q1 when "mygate" is in the gate library. + if (it == gates.end() && openQASM2CompatMode) { + while (!resolvedId.empty() && resolvedId.front() == 'c') { + resolvedId = resolvedId.substr(1); + ++implicitCompatControls; + } + if (implicitCompatControls > 0) { + it = gates.find(resolvedId); + } + } + if (it == gates.end()) { + throw qasm3::CompilerError("Unknown gate '" + id + "'.", stmt->debugInfo); + } + + // Evaluate parameters to doubles + std::vector params; + params.reserve(stmt->arguments.size()); + for (const auto& arg : stmt->arguments) { + auto result = constEvalPass.visit(arg); + if (!result.has_value()) { + throw qasm3::CompilerError( + "Gate parameter could not be const-evaluated.", stmt->debugInfo); + } + params.push_back(result->toExpr()->asFP()); + } + + // Parse modifiers: accumulate pos/neg controls and invert flag + bool invert = false; + size_t nModifierControls = 0; + // (count, isPositive) per ctrl modifier, in order + SmallVector> ctrlSpec; + for (const auto& mod : stmt->modifiers) { + if (std::dynamic_pointer_cast(mod)) { + invert = !invert; + } else if (const auto* ctrlMod = + dynamic_cast(mod.get())) { + const size_t n = + evaluatePositiveConstant(ctrlMod->expression, stmt->debugInfo, 1); + ctrlSpec.emplace_back(n, ctrlMod->ctrlType); + nModifierControls += n; + } else { + throw qasm3::CompilerError( + "Only ctrl/negctrl/inv modifiers are supported.", stmt->debugInfo); + } + } + + // Expand each operand to its qubit Values + std::vector> expandedOperands; + expandedOperands.reserve(stmt->operands.size()); + for (const auto& operand : stmt->operands) { + expandedOperands.push_back( + resolveGateOperandInScope(operand, scope, stmt->debugInfo)); + } + + // First nModifierControls slots are modifier-derived controls + SmallVector posControls; + SmallVector negControls; + size_t ctrlIdx = 0; + for (const auto& [n, positive] : ctrlSpec) { + for (size_t i = 0; i < n; ++i, ++ctrlIdx) { + if (ctrlIdx >= expandedOperands.size() || + expandedOperands[ctrlIdx].size() != 1) { + throw qasm3::CompilerError("Control operand must be a single qubit.", + stmt->debugInfo); + } + if (positive) { + posControls.push_back(expandedOperands[ctrlIdx][0]); + } else { + negControls.push_back(expandedOperands[ctrlIdx][0]); + } + } + } + + // OQ2 compat implicit controls follow modifier controls + for (size_t i = 0; i < implicitCompatControls; ++i, ++ctrlIdx) { + if (ctrlIdx >= expandedOperands.size() || + expandedOperands[ctrlIdx].size() != 1) { + throw qasm3::CompilerError( + "Implicit OQ2 control operand must be a single qubit.", + stmt->debugInfo); + } + posControls.push_back(expandedOperands[ctrlIdx][0]); + } + const size_t totalCtrlCount = nModifierControls + implicitCompatControls; + + // Remaining slots are the gate's own operands (may broadcast) + std::vector> gateOperands( + expandedOperands.begin() + static_cast(totalCtrlCount), + expandedOperands.end()); + + // Compound gate: inline expand + if (const auto* compound = + dynamic_cast(it->second.get())) { + applyCompoundGate(*compound, gateOperands, posControls, negControls, + params, invert, stmt->debugInfo); + return; + } + + // Standard gate: validate param count then determine broadcast width + if (it->second->getNParameters() != params.size()) { + throw qasm3::CompilerError( + "Gate '" + id + "' takes " + + std::to_string(it->second->getNParameters()) + + " parameters, but " + std::to_string(params.size()) + + " were supplied.", + stmt->debugInfo); + } + + // Standard gate: determine broadcast width + size_t broadcastWidth = 0; + for (const auto& ops : gateOperands) { + if (ops.size() > 1) { + if (broadcastWidth == 0) { + broadcastWidth = ops.size(); + } else if (broadcastWidth != ops.size()) { + throw qasm3::CompilerError( + "Broadcast operands must all have the same width.", + stmt->debugInfo); + } + } + } + if (broadcastWidth == 0) { + broadcastWidth = 1; + } + + const auto dispIt = GATE_DISPATCH.find(resolvedId); + if (dispIt == GATE_DISPATCH.end()) { + throw qasm3::CompilerError("No MLIR mapping for gate '" + id + "'.", + stmt->debugInfo); + } + + for (size_t b = 0; b < broadcastWidth; ++b) { + SmallVector iterQubits; + iterQubits.reserve(gateOperands.size()); + for (const auto& ops : gateOperands) { + iterQubits.push_back(ops.size() > 1 ? ops[b] : ops[0]); + } + + // Check that no qubit appears twice across targets and controls. + llvm::SmallDenseSet seen; + for (auto q : concat(iterQubits, posControls, negControls)) { + if (!seen.insert(q).second) { + throw qasm3::CompilerError("Duplicate qubit in gate '" + id + + "' operands.", + stmt->debugInfo); + } + } + + emitGate(dispIt->second, iterQubits, params, posControls, negControls, + invert); + } + } + + /// Emit a single gate application, wrapping with ctrl/inv as needed. + void emitGate(const GateFn& gateFn, ArrayRef targets, + ArrayRef params, ArrayRef posControls, + ArrayRef negControls, bool invert) { + auto inner = [&](ValueRange qubits) { gateFn(builder, qubits, params); }; + + auto withInv = [&](ValueRange qubits) { + if (invert) { + builder.inv(qubits, function_ref(inner)); + } else { + inner(qubits); + } + }; + + SmallVector controls; + controls.append(posControls.begin(), posControls.end()); + controls.append(negControls.begin(), negControls.end()); + + if (controls.empty()) { + withInv(targets); + return; + } + + for (auto q : negControls) { + builder.x(q); + } + builder.ctrl(controls, targets, function_ref(withInv)); + for (auto q : negControls) { + builder.x(q); + } + } + + /// Inline-expand a compound (user-defined) gate. + void applyCompoundGate(const qasm3::CompoundGate& gate, + const std::vector>& gateOperands, + ArrayRef posControls, + ArrayRef negControls, ArrayRef params, + bool invert, + const std::shared_ptr& debugInfo) { + if (gate.targetNames.size() != gateOperands.size()) { + throw qasm3::CompilerError("Compound gate operand count mismatch.", + debugInfo); + } + if (gate.parameterNames.size() != params.size()) { + throw qasm3::CompilerError("Compound gate parameter count mismatch.", + debugInfo); + } + + // Build local scope: argument name → Values + llvm::SmallVector targets; + llvm::StringMap> targetsMap; + for (size_t i = 0; i < gate.targetNames.size(); ++i) { + for (auto target : gateOperands[i]) { + auto* it = llvm::find(targets, target); + if (it == targets.end()) { + targets.push_back(target); + } + targetsMap[gate.targetNames[i]].push_back( + std::distance(targets.begin(), it)); + } + } + + // Bind parameters as constants + constEvalPass.pushEnv(); + for (size_t i = 0; i < gate.parameterNames.size(); ++i) { + constEvalPass.addConst(gate.parameterNames[i], + qasm3::const_eval::ConstEvalValue(params[i])); + } + + auto bodyFn = [&](ValueRange qubits) { + QubitScope localScope; + for (const auto& [name, indices] : targetsMap) { + SmallVector args; + for (auto index : indices) { + args.push_back(qubits[index]); + } + localScope[name] = std::move(args); + } + for (const auto& bodyStmt : gate.body) { + if (const auto gateCall = + std::dynamic_pointer_cast(bodyStmt)) { + applyGateCallStatement(gateCall, localScope); + } else if (const auto barrier = + std::dynamic_pointer_cast( + bodyStmt)) { + SmallVector qubits; + for (const auto& g : barrier->gates) { + auto resolved = + resolveGateOperandInScope(g, localScope, barrier->debugInfo); + qubits.append(resolved.begin(), resolved.end()); + } + builder.barrier(qubits); + } else if (const auto reset = + std::dynamic_pointer_cast( + bodyStmt)) { + for (auto q : resolveGateOperandInScope(reset->gate, localScope, + reset->debugInfo)) { + builder.reset(q); + } + } + } + }; + + auto withInv = [&](ValueRange qubits) { + if (invert) { + builder.inv(qubits, function_ref(bodyFn)); + } else { + bodyFn(qubits); + } + }; + + SmallVector controls; + controls.append(posControls.begin(), posControls.end()); + controls.append(negControls.begin(), negControls.end()); + + if (controls.empty()) { + withInv(targets); + } else { + for (auto q : negControls) { + builder.x(q); + } + builder.ctrl(controls, targets, function_ref(withInv)); + for (auto q : negControls) { + builder.x(q); + } + } + + constEvalPass.popEnv(); + } + + //===--- If/else helpers ------------------------------------------------===// + + /// Emit quantum statements inside an if/else block. + void emitBlockStatements( + const std::vector>& statements, + const std::shared_ptr& debugInfo) { + for (const auto& statement : statements) { + auto gateCall = + std::dynamic_pointer_cast(statement); + if (gateCall == nullptr) { + throw qasm3::CompilerError( + "Only quantum statements are supported in if/else blocks.", + debugInfo); + } + applyGateCallStatement(gateCall, qubitRegisters); + } + } + + /// Translate a QASM3 condition expression to an i1 MLIR Value. + /// Supports: + /// - Single bit: `c[0]` or `!c[0]` / `~c[0]` + [[nodiscard]] Value + translateCondition(const std::shared_ptr& condition, + const std::shared_ptr& debugInfo) { + // Case 1: Register comparison (creg == N, creg != N, etc.) + if (const auto binaryExpr = + std::dynamic_pointer_cast(condition)) { + throw qasm3::CompilerError( + "Register comparisons cannot be translated to QC at the moment.", + debugInfo); + } + + // Case 2: Unary negation (!c[0] or ~c[0]) + if (const auto unaryExpr = + std::dynamic_pointer_cast(condition)) { + assert(unaryExpr->op == qasm3::UnaryExpression::LogicalNot || + unaryExpr->op == qasm3::UnaryExpression::BitwiseNot); + const auto idExpr = std::dynamic_pointer_cast( + unaryExpr->operand); + if (!idExpr) { + throw qasm3::CompilerError("Unary expression has unsupported operand.", + debugInfo); + } + auto bitVal = lookupBitValue(idExpr, debugInfo); + // Negate: XOR with true + auto trueVal = + arith::ConstantOp::create( + builder, builder.getIntegerAttr(builder.getI1Type(), 1)) + .getResult(); + return arith::XOrIOp::create(builder, bitVal, trueVal).getResult(); + } + + // Case 3: Single bit (c[0] — truthy) + if (const auto idExpr = + std::dynamic_pointer_cast(condition)) { + return lookupBitValue(idExpr, debugInfo); + } + + throw qasm3::CompilerError( + "Unsupported condition expression in if statement.", debugInfo); + } + + /// Look up the most recent measurement result for a single classical bit. + [[nodiscard]] Value + lookupBitValue(const std::shared_ptr& idExpr, + const std::shared_ptr& debugInfo) const { + const auto& regName = idExpr->identifier; + auto it = bitValues.find(regName); + if (it == bitValues.end()) { + throw qasm3::CompilerError( + "Classical register '" + regName + + "' has no measurement results to use in condition.", + debugInfo); + } + const auto& regBits = it->second; + + // Single bit — must be indexed + if (idExpr->indices.empty()) { + if (regBits.size() != 1) { + throw qasm3::CompilerError( + "Condition on full register '" + regName + + "' requires a comparison operator (e.g. creg == 0).", + debugInfo); + } + if (!regBits[0]) { + throw qasm3::CompilerError( + "Bit 0 of register '" + regName + + "' was not measured before use in condition.", + debugInfo); + } + return regBits[0]; + } + + const auto idx = evaluatePositiveConstant( + idExpr->indices[0]->indexExpressions[0], debugInfo); + if (idx >= regBits.size() || !regBits[idx]) { + throw qasm3::CompilerError( + "Bit " + std::to_string(idx) + " of register '" + regName + + "' was not measured before use in condition.", + debugInfo); + } + return regBits[idx]; + } + + //===--- Operand resolution helpers ------------------------------------===// + + /// Resolve a gate operand against the top-level qubit register map. + SmallVector + resolveGateOperand(const std::shared_ptr& operand, + const std::shared_ptr& debugInfo) { + return resolveGateOperandInScope(operand, qubitRegisters, debugInfo); + } + + /** + * @brief Resolve a gate operand against \p scope. + */ + SmallVector resolveGateOperandInScope( + const std::shared_ptr& operand, + const QubitScope& scope, + const std::shared_ptr& debugInfo) { + if (operand->isHardwareQubit()) { + return {builder.staticQubit(operand->getHardwareQubit())}; + } + + const auto idExpr = operand->getIdentifier(); + const auto& name = idExpr->identifier; + + auto it = scope.find(name); + if (it == scope.end()) { + throw qasm3::CompilerError("Unknown qubit register '" + name + "'.", + debugInfo); + } + const auto& qubits = it->second; + + if (idExpr->indices.empty()) { + return qubits; // full register + } + + if (idExpr->indices.size() > 1) { + throw qasm3::CompilerError("Only single-index expressions are supported.", + debugInfo); + } + const auto& indexExpression = idExpr->indices[0]->indexExpressions[0]; + const auto idx = evaluatePositiveConstant(indexExpression, debugInfo); + if (idx >= qubits.size()) { + throw qasm3::CompilerError("Qubit index out of bounds.", debugInfo); + } + return {qubits[idx]}; + } + + /** + * Resolve \p target to a list of classical bits in a known register. + * Returns all bits for an unindexed identifier, or a single bit otherwise. + */ + [[nodiscard]] std::vector resolveClassicalBits( + const std::shared_ptr& target, + const std::shared_ptr& debugInfo) const { + const auto& name = target->identifier; + auto it = classicalRegisters.find(name); + if (it == classicalRegisters.end()) { + throw qasm3::CompilerError("Unknown classical register '" + name + "'.", + debugInfo); + } + const auto& creg = it->second; + + std::vector bits; + if (target->indices.empty()) { + for (int64_t i = 0; i < creg.size; ++i) { + bits.push_back(creg[i]); + } + return bits; + } + const auto& indexExpression = target->indices[0]->indexExpressions[0]; + const auto idx = evaluatePositiveConstant(indexExpression, debugInfo); + bits.push_back(creg[static_cast(idx)]); + return bits; + } + + /// Evaluate \p expr as a non-negative integer constant. + /// Returns \p defaultValue if \p expr is null; throws on non-constant input. + static size_t + evaluatePositiveConstant(const std::shared_ptr& expr, + const std::shared_ptr& debugInfo, + size_t defaultValue = 0) { + if (!expr) { + return defaultValue; + } + const auto constVal = std::dynamic_pointer_cast(expr); + if (!constVal) { + throw qasm3::CompilerError("Expected a constant integer expression.", + debugInfo); + } + return static_cast(constVal->getUInt()); + } +}; + +} // namespace + +//===----------------------------------------------------------------------===// +// Public API +//===----------------------------------------------------------------------===// + +OwningOpRef translateQASM3ToQC(MLIRContext* context, + std::istream& input) { + try { + qasm3::Parser parser(input); + const auto program = parser.parseProgram(); + MLIRQasmImporter importer(context); + importer.visitProgram(program); + return importer.finalize(); + } catch (const qasm3::CompilerError& e) { + llvm::errs() << "QASM3 import error: " << e.what() << "\n"; + return nullptr; + } catch (const std::exception& e) { + llvm::errs() << "QASM3 import error: " << e.what() << "\n"; + return nullptr; + } +} + +OwningOpRef translateQASM3ToQC(MLIRContext* context, + const std::string& filename) { + std::ifstream file(filename); + if (!file.good()) { + llvm::errs() << "Could not open file '" << filename << "'\n"; + return nullptr; + } + return translateQASM3ToQC(context, file); +} + +} // namespace mlir::qc diff --git a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp index ac70cd4269..7a2cf23651 100644 --- a/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp +++ b/mlir/lib/Dialect/QC/Translation/TranslateQuantumComputationToQC.cpp @@ -10,8 +10,10 @@ #include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" +#include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" #include "ir/Register.hpp" +#include "ir/operations/CompoundOperation.hpp" #include "ir/operations/Control.hpp" #include "ir/operations/IfElseOperation.hpp" #include "ir/operations/NonUnitaryOperation.hpp" @@ -19,6 +21,7 @@ #include "ir/operations/Operation.hpp" #include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" +#include #include #include #include @@ -73,6 +76,30 @@ struct TranslationState { /// Whether the translation is currently processing an IfElseOperation bool inIfElse = false; + + /// Whether the translation is currently within a control modifier + bool inCtrlOp = false; + + /// Mapping from physical qubit index to block argument + DenseMap targetArgs; + + /// Control qubits of the current CompoundOperation + DenseSet<::qc::Qubit> compoundControls; + + [[nodiscard]] Value getQubit(size_t index) const { + if (inCtrlOp) { + auto it = targetArgs.find(index); + if (it == targetArgs.end()) { + llvm::reportFatalInternalError("Qubit index out of bounds"); + } + return it->second; + } + + if (index >= qubits.size()) { + llvm::reportFatalInternalError("Qubit index out of bounds"); + } + return qubits[index]; + }; }; } // namespace @@ -222,7 +249,7 @@ static void addMeasureOp(QCProgramBuilder& builder, const auto& classics = measureOp.getClassics(); for (size_t i = 0; i < targets.size(); ++i) { - const auto& qubit = state.qubits[targets[i]]; + const auto& qubit = state.getQubit(targets[i]); const auto bitIdx = static_cast(classics[i]); const auto& [mem, localIdx] = state.bitMap[bitIdx]; const auto& bit = mem[static_cast(localIdx)]; @@ -239,13 +266,13 @@ static void addMeasureOp(QCProgramBuilder& builder, * * @param builder The QCProgramBuilder used to create operations * @param operation The reset operation to translate - * @param qubits Flat vector of qubit values indexed by physical qubit index + * @param state The translation state */ static void addResetOp(QCProgramBuilder& builder, const ::qc::Operation& operation, - const SmallVector& qubits) { + TranslationState& state) { for (const auto& target : operation.getTargets()) { - auto qubit = qubits[target]; + auto qubit = state.getQubit(target); builder.reset(qubit); } } @@ -258,18 +285,21 @@ static void addResetOp(QCProgramBuilder& builder, * the qubit values corresponding to positive controls. * * @param operation The operation containing controls - * @param qubits Flat vector of qubit values indexed by physical qubit index + * @param state The translation state * @return Vector of qubit values corresponding to positive controls */ static SmallVector getControls(const ::qc::Operation& operation, - const SmallVector& qubits) { + TranslationState& state) { SmallVector controls; for (const auto& [control, type] : operation.getControls()) { + if (state.compoundControls.contains(control)) { + continue; + } if (type == ::qc::Control::Type::Neg) { llvm::reportFatalInternalError( "Negative controls cannot be translated to QC at the moment"); } - controls.push_back(qubits[control]); + controls.push_back(state.getQubit(control)); } return controls; } @@ -286,13 +316,13 @@ static SmallVector getControls(const ::qc::Operation& operation, * \ * @param builder The QCProgramBuilder used to create operations \ * @param operation The OP_CORE operation to translate \ - * @param qubits Flat vector of qubit values indexed by physical qubit index \ + * @param state The translation state \ */ \ static void add##OP_CORE##Op(QCProgramBuilder& builder, \ const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ - const auto& target = qubits[operation.getTargets()[0]]; \ - if (const auto& controls = getControls(operation, qubits); \ + TranslationState& state) { \ + const auto& target = state.getQubit(operation.getTargets()[0]); \ + if (const auto& controls = getControls(operation, state); \ controls.empty()) { \ builder.OP_QC(target); \ } else { \ @@ -326,14 +356,14 @@ DEFINE_ONE_TARGET_ZERO_PARAMETER(SXdg, sxdg) * \ * @param builder The QCProgramBuilder used to create operations \ * @param operation The OP_CORE operation to translate \ - * @param qubits Flat vector of qubit values indexed by physical qubit index \ + * @param state The translation state \ */ \ static void add##OP_CORE##Op(QCProgramBuilder& builder, \ const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + TranslationState& state) { \ const auto& param = operation.getParameter()[0]; \ - const auto& target = qubits[operation.getTargets()[0]]; \ - if (const auto& controls = getControls(operation, qubits); \ + const auto& target = state.getQubit(operation.getTargets()[0]); \ + if (const auto& controls = getControls(operation, state); \ controls.empty()) { \ builder.OP_QC(param, target); \ } else { \ @@ -358,15 +388,15 @@ DEFINE_ONE_TARGET_ONE_PARAMETER(P, p) * \ * @param builder The QCProgramBuilder used to create operations \ * @param operation The OP_CORE operation to translate \ - * @param qubits Flat vector of qubit values indexed by physical qubit index \ + * @param state The translation state \ */ \ static void add##OP_CORE##Op(QCProgramBuilder& builder, \ const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + TranslationState& state) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ - const auto& target = qubits[operation.getTargets()[0]]; \ - if (const auto& controls = getControls(operation, qubits); \ + const auto& target = state.getQubit(operation.getTargets()[0]); \ + if (const auto& controls = getControls(operation, state); \ controls.empty()) { \ builder.OP_QC(param1, param2, target); \ } else { \ @@ -391,16 +421,16 @@ DEFINE_ONE_TARGET_TWO_PARAMETER(U2, u2) * \ * @param builder The QCProgramBuilder used to create operations \ * @param operation The OP_CORE operation to translate \ - * @param qubits Flat vector of qubit values indexed by physical qubit index \ + * @param state The translation state \ */ \ static void add##OP_CORE##Op(QCProgramBuilder& builder, \ const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + TranslationState& state) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ const auto& param3 = operation.getParameter()[2]; \ - const auto& target = qubits[operation.getTargets()[0]]; \ - if (const auto& controls = getControls(operation, qubits); \ + const auto& target = state.getQubit(operation.getTargets()[0]); \ + if (const auto& controls = getControls(operation, state); \ controls.empty()) { \ builder.OP_QC(param1, param2, param3, target); \ } else { \ @@ -424,14 +454,14 @@ DEFINE_ONE_TARGET_THREE_PARAMETER(U, u) * \ * @param builder The QCProgramBuilder used to create operations \ * @param operation The OP_CORE operation to translate \ - * @param qubits Flat vector of qubit values indexed by physical qubit index \ + * @param state The translation state \ */ \ static void add##OP_CORE##Op(QCProgramBuilder& builder, \ const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ - const auto& target0 = qubits[operation.getTargets()[0]]; \ - const auto& target1 = qubits[operation.getTargets()[1]]; \ - if (const auto& controls = getControls(operation, qubits); \ + TranslationState& state) { \ + const auto& target0 = state.getQubit(operation.getTargets()[0]); \ + const auto& target1 = state.getQubit(operation.getTargets()[1]); \ + if (const auto& controls = getControls(operation, state); \ controls.empty()) { \ builder.OP_QC(target0, target1); \ } else { \ @@ -448,14 +478,18 @@ DEFINE_TWO_TARGET_ZERO_PARAMETER(ECR, ecr) static void addISWAPdgOp(QCProgramBuilder& builder, const ::qc::Operation& operation, - const SmallVector& qubits) { - auto target0 = qubits[operation.getTargets()[0]]; - auto target1 = qubits[operation.getTargets()[1]]; - if (const auto& controls = getControls(operation, qubits); controls.empty()) { - builder.inv([&] { builder.iswap(target0, target1); }); + TranslationState& state) { + auto target0 = state.getQubit(operation.getTargets()[0]); + auto target1 = state.getQubit(operation.getTargets()[1]); + if (const auto& controls = getControls(operation, state); controls.empty()) { + builder.inv({target0, target1}, [&](ValueRange qubits) { + builder.iswap(qubits[0], qubits[1]); + }); } else { - builder.ctrl(controls, [&] { - builder.inv([&] { builder.iswap(target0, target1); }); + builder.ctrl(controls, {target0, target1}, [&](ValueRange targets) { + builder.inv(targets, [&](ValueRange qubits) { + builder.iswap(qubits[0], qubits[1]); + }); }); } } @@ -472,15 +506,15 @@ static void addISWAPdgOp(QCProgramBuilder& builder, * \ * @param builder The QCProgramBuilder used to create operations \ * @param operation The OP_CORE operation to translate \ - * @param qubits Flat vector of qubit values indexed by physical qubit index \ + * @param state The translation state \ */ \ static void add##OP_CORE##Op(QCProgramBuilder& builder, \ const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + TranslationState& state) { \ const auto& param = operation.getParameter()[0]; \ - const auto& target0 = qubits[operation.getTargets()[0]]; \ - const auto& target1 = qubits[operation.getTargets()[1]]; \ - if (const auto& controls = getControls(operation, qubits); \ + const auto& target0 = state.getQubit(operation.getTargets()[0]); \ + const auto& target1 = state.getQubit(operation.getTargets()[1]); \ + if (const auto& controls = getControls(operation, state); \ controls.empty()) { \ builder.OP_QC(param, target0, target1); \ } else { \ @@ -507,16 +541,16 @@ DEFINE_TWO_TARGET_ONE_PARAMETER(RZZ, rzz) * \ * @param builder The QCProgramBuilder used to create operations \ * @param operation The OP_CORE operation to translate \ - * @param qubits Flat vector of qubit values indexed by physical qubit index \ + * @param state The translation state \ */ \ static void add##OP_CORE##Op(QCProgramBuilder& builder, \ const ::qc::Operation& operation, \ - const SmallVector& qubits) { \ + TranslationState& state) { \ const auto& param1 = operation.getParameter()[0]; \ const auto& param2 = operation.getParameter()[1]; \ - const auto& target0 = qubits[operation.getTargets()[0]]; \ - const auto& target1 = qubits[operation.getTargets()[1]]; \ - if (const auto& controls = getControls(operation, qubits); \ + const auto& target0 = state.getQubit(operation.getTargets()[0]); \ + const auto& target1 = state.getQubit(operation.getTargets()[1]); \ + if (const auto& controls = getControls(operation, state); \ controls.empty()) { \ builder.OP_QC(param1, param2, target0, target1); \ } else { \ @@ -533,10 +567,10 @@ DEFINE_TWO_TARGET_TWO_PARAMETER(XXminusYY, xx_minus_yy) static void addBarrierOp(QCProgramBuilder& builder, const ::qc::Operation& operation, - const SmallVector& qubits) { + TranslationState& state) { SmallVector targets; for (const auto& targetIdx : operation.getTargets()) { - targets.push_back(qubits[targetIdx]); + targets.push_back(state.getQubit(targetIdx)); } builder.barrier(targets); } @@ -546,6 +580,72 @@ static LogicalResult translateOperation(QCProgramBuilder& builder, const ::qc::Operation& operation, TranslationState& state); +// CompoundOp + +static LogicalResult addCompoundOp(QCProgramBuilder& builder, + const ::qc::Operation& operation, + TranslationState& state) { + const auto& compoundOp = + dynamic_cast(operation); + if (const auto& controls = getControls(operation, state); controls.empty()) { + for (const auto& op : compoundOp) { + if (failed(translateOperation(builder, *op, state))) { + return failure(); + } + } + } else { + // Collect targets + DenseMap targetMap; + for (const auto& op : compoundOp) { + if (dynamic_cast(op.get()) != nullptr) { + llvm::reportFatalInternalError("Nested CompoundOperations cannot be " + "translated to QC at the moment"); + } + for (const auto& target : op->getTargets()) { + if (!targetMap.contains(target)) { + targetMap[target] = state.getQubit(target); + } + } + for (const auto& control : op->getControls()) { + if (compoundOp.getControls().contains(control)) { + continue; + } + const auto& qubit = control.qubit; + if (!targetMap.contains(qubit)) { + targetMap[qubit] = state.getQubit(qubit); + } + } + } + for (const auto& [control, _] : compoundOp.getControls()) { + state.compoundControls.insert(control); + } + SmallVector> sortedPairs(targetMap.begin(), + targetMap.end()); + llvm::sort(sortedPairs.begin(), sortedPairs.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + SmallVector targets; + for (const auto& pair : sortedPairs) { + targets.push_back(pair.second); + } + // Build control modifier + builder.ctrl(controls, targets, [&](ValueRange targetArgs) { + state.inCtrlOp = true; + for (size_t i = 0; i < sortedPairs.size(); ++i) { + state.targetArgs[sortedPairs[i].first] = targetArgs[i]; + } + for (const auto& op : compoundOp) { + if (failed(translateOperation(builder, *op, state))) { + llvm::reportFatalInternalError("Failed to translate operation inside " + "controlled CompoundOperation"); + } + } + state.targetArgs.clear(); + state.inCtrlOp = false; + }); + } + return success(); +} + // IfElseOp static LogicalResult addIfElseOp(QCProgramBuilder& builder, @@ -622,7 +722,7 @@ static LogicalResult addIfElseOp(QCProgramBuilder& builder, #define ADD_OP_CASE(OP_CORE) \ case ::qc::OpType::OP_CORE: \ - add##OP_CORE##Op(builder, operation, qubits); \ + add##OP_CORE##Op(builder, operation, state); \ return success(); /** @@ -636,7 +736,6 @@ static LogicalResult addIfElseOp(QCProgramBuilder& builder, static LogicalResult translateOperation(QCProgramBuilder& builder, const ::qc::Operation& operation, TranslationState& state) { - const auto& qubits = state.qubits; switch (operation.getType()) { case ::qc::OpType::Measure: addMeasureOp(builder, operation, state); @@ -672,7 +771,12 @@ static LogicalResult translateOperation(QCProgramBuilder& builder, ADD_OP_CASE(XXminusYY) ADD_OP_CASE(Barrier) case ::qc::OpType::iSWAPdg: - addISWAPdgOp(builder, operation, qubits); + addISWAPdgOp(builder, operation, state); + return success(); + case ::qc::OpType::Compound: + if (failed(addCompoundOp(builder, operation, state))) { + return failure(); + } return success(); case ::qc::OpType::IfElse: if (failed(addIfElseOp(builder, operation, state))) { @@ -759,8 +863,10 @@ OwningOpRef translateQuantumComputationToQC( // Allocate result map SmallVector results(quantumComputation.getNcbits(), nullptr); - TranslationState state{ - .qubits = qubits, .bitMap = bitMap, .results = std::move(results)}; + TranslationState state{.qubits = qubits, + .bitMap = bitMap, + .results = std::move(results), + .targetArgs = DenseMap{}}; // Translate operations if (translateOperations(builder, quantumComputation, state).failed()) { diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp index 25fc88d084..d9a67fafca 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/CtrlOp.cpp @@ -10,6 +10,7 @@ #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include using namespace mlir; @@ -42,38 +44,53 @@ struct MergeNestedCtrl final : OpRewritePattern { LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - // Require at least one positive control + // Require at least one control // Trivial case is handled by ReduceCtrl - const auto numOuterControls = op.getNumControls(); - if (numOuterControls == 0) { + if (op.getNumControls() == 0) { return failure(); } - auto bodyCtrlOp = dyn_cast(op.getBodyUnitary().getOperation()); - if (!bodyCtrlOp) { + if (op.getNumBodyUnitaries() != 1) { return failure(); } - const auto numInnerControls = bodyCtrlOp.getNumControls(); - auto outerControls = op.getControlsIn(); + auto innerCtrlOp = dyn_cast(op.getBodyUnitary(0).getOperation()); + if (!innerCtrlOp) { + return failure(); + } + auto outerTargets = op.getTargetsIn(); - auto newAdditionalControls = outerTargets.take_front(numInnerControls); - auto newTargets = outerTargets.drop_front(numInnerControls); - auto newControls = llvm::to_vector( - llvm::concat(outerControls, newAdditionalControls)); + auto outerControls = op.getControlsIn(); + auto innerTargets = innerCtrlOp.getTargetsIn(); + + SmallVector controls; + SmallVector targets; + llvm::append_range(controls, outerControls); + for (auto [arg, qubit] : + llvm::zip_equal(op.getBody()->getArguments(), outerTargets)) { + if (llvm::is_contained(innerTargets, arg)) { + targets.push_back(qubit); + } else { + controls.push_back(qubit); + } + } rewriter.replaceOpWithNewOp( - op, newControls, newTargets, - [&](ValueRange newTargetArgs) -> SmallVector { + op, controls, targets, + [&](ValueRange targetArgs) -> SmallVector { + auto* innerCtrlBody = innerCtrlOp.getBody(); IRMapping mapping; - auto* innerBody = bodyCtrlOp.getBody(); - for (size_t i = 0; i < bodyCtrlOp.getNumTargets(); ++i) { - mapping.map(innerBody->getArgument(i), newTargetArgs[i]); + utils::populateMapping(mapping, *innerCtrlBody, innerTargets, + outerTargets, targets, targetArgs); + for (auto& op : innerCtrlBody->without_terminator()) { + rewriter.clone(op, mapping); } - - return rewriter - .clone(*bodyCtrlOp.getBodyUnitary().getOperation(), mapping) - ->getResults(); + SmallVector yields; + for (auto value : innerCtrlBody->getTerminator()->getOperands()) { + yields.push_back(mapping.lookup(value)); + } + return yields; }); + return success(); } }; @@ -87,20 +104,31 @@ struct ReduceCtrl final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite(CtrlOp op, PatternRewriter& rewriter) const override { - auto* bodyUnitary = op.getBodyUnitary().getOperation(); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto* innerOp = op.getBodyUnitary(0).getOperation(); + // Inline ops from empty control modifiers, IdOp and BarrierOp - if (op.getNumControls() == 0 || isa(bodyUnitary)) { - rewriter.moveOpBefore(bodyUnitary, op); - bodyUnitary->setOperands(0, op.getNumTargets(), op.getTargetsIn()); + if (op.getNumControls() == 0 || isa(innerOp)) { + const auto numTargets = op.getNumTargets(); + auto outerTargets = op.getTargetsIn(); + SmallVector targets; + for (auto target : innerOp->getOperands().take_front(numTargets)) { + targets.push_back( + utils::getValueFromBlockArgument(target, outerTargets)); + } + + rewriter.moveOpBefore(innerOp, op); + innerOp->setOperands(0, numTargets, targets); rewriter.replaceAllUsesWith(op.getControlsOut(), op.getControlsIn()); - rewriter.replaceAllUsesWith(op.getTargetsOut(), - bodyUnitary->getResults()); + rewriter.replaceAllUsesWith(op.getTargetsOut(), innerOp->getResults()); rewriter.eraseOp(op); return success(); } // The remaining code explicitly handles GPhaseOp and nothing else - auto gPhaseOp = dyn_cast(bodyUnitary); + auto gPhaseOp = dyn_cast(innerOp); if (!gPhaseOp) { return failure(); } @@ -136,22 +164,44 @@ struct ReduceCtrl final : OpRewritePattern { auto yieldOp = cast(op.getBody()->back()); yieldOp->setOperands(pOp->getResults()); - // erase the GPhaseOp + // Erase the GPhaseOp rewriter.eraseOp(gPhaseOp); return success(); } }; +/** + * @brief Erase control modifiers that do not have any body unitaries. + */ +struct EraseEmptyCtrl final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + if (op.getNumBodyUnitaries() != 0) { + return failure(); + } + + rewriter.replaceOp(op, op.getOperands()); + return success(); + } +}; + } // namespace -UnitaryOpInterface CtrlOp::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())); +size_t CtrlOp::getNumBodyUnitaries() { + return llvm::count_if( + *getBody(), [](Operation& op) { return isa(op); }); +} + +UnitaryOpInterface CtrlOp::getBodyUnitary(const size_t i) { + auto unitaries = llvm::make_filter_range( + *getBody(), [](Operation& op) { return isa(op); }); + auto it = std::next(unitaries.begin(), static_cast(i)); + if (it == unitaries.end()) { + llvm::reportFatalUsageError("Unitary index out of bounds"); + } + return cast(*it); } Value CtrlOp::getInputQubit(const size_t i) { @@ -162,7 +212,7 @@ Value CtrlOp::getInputQubit(const size_t i) { if (numControls <= i && i < getNumQubits()) { return getTargetsIn()[i - numControls]; } - llvm::reportFatalUsageError("Invalid qubit index"); + llvm::reportFatalUsageError("Qubit index out of bounds"); } Value CtrlOp::getOutputQubit(const size_t i) { @@ -173,7 +223,7 @@ Value CtrlOp::getOutputQubit(const size_t i) { if (numControls <= i && i < getNumQubits()) { return getTargetsOut()[i - numControls]; } - llvm::reportFatalUsageError("Invalid qubit index"); + llvm::reportFatalUsageError("Qubit index out of bounds"); } Value CtrlOp::getInputTarget(const size_t i) { @@ -238,7 +288,7 @@ void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, build(odsBuilder, odsState, controls, targets); auto& block = odsState.regions.front()->emplaceBlock(); - const auto qubitType = QubitType::get(odsBuilder.getContext()); + auto qubitType = QubitType::get(odsBuilder.getContext()); for (size_t i = 0; i < targets.size(); ++i) { block.addArgument(qubitType, odsState.location); } @@ -251,40 +301,33 @@ void CtrlOp::build(OpBuilder& odsBuilder, OperationState& odsState, LogicalResult CtrlOp::verify() { auto& block = *getBody(); - if (block.getOperations().size() < 2) { - return emitOpError("body region must have at least two operations"); + if (llvm::any_of(*getBody(), [](Operation& op) { + return isa(op); + })) { + return emitOpError("body must not contain non-unitary quantum operations"); } + if (!isa(block.back())) { + return emitOpError( + "last operation in body region must be a yield operation"); + } + const auto numTargets = getNumTargets(); if (block.getArguments().size() != numTargets) { return emitOpError( "number of block arguments must match the number of targets"); } - const auto qubitType = QubitType::get(getContext()); + 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"); - } - } SmallPtrSet uniqueQubitsIn; for (const auto& control : getControlsIn()) { @@ -298,36 +341,14 @@ LogicalResult CtrlOp::verify() { } } - 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; - } - } - SmallPtrSet uniqueQubitsOut; for (const auto& control : getControlsOut()) { if (!uniqueQubitsOut.insert(control).second) { return emitOpError("duplicate control qubit found"); } } - for (size_t i = 0; i < numQubits; i++) { - if (!uniqueQubitsOut.insert(bodyUnitary.getOutputQubit(i)).second) { + for (size_t i = 0; i < numTargets; i++) { + if (!uniqueQubitsOut.insert(block.back().getOperand(i)).second) { return emitOpError("duplicate qubit found"); } } @@ -337,15 +358,19 @@ LogicalResult CtrlOp::verify() { void CtrlOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { - results.add(context); + results.add(context); } std::optional CtrlOp::getUnitaryMatrix() { - auto&& bodyUnitary = getBodyUnitary(); + if (getNumBodyUnitaries() != 1) { + return std::nullopt; + } + + auto bodyUnitary = getBodyUnitary(0); if (!bodyUnitary) { return std::nullopt; } - auto&& targetMatrix = bodyUnitary.getUnitaryMatrix(); + auto targetMatrix = bodyUnitary.getUnitaryMatrix(); if (!targetMatrix) { return std::nullopt; } diff --git a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp index d82a64f819..1f0e6d556c 100644 --- a/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp +++ b/mlir/lib/Dialect/QCO/IR/Modifiers/InvOp.cpp @@ -10,8 +10,10 @@ #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 @@ -25,6 +27,7 @@ #include #include +#include #include #include @@ -40,36 +43,42 @@ namespace { struct MoveCtrlOutside final : OpRewritePattern { using OpRewritePattern::OpRewritePattern; - LogicalResult matchAndRewrite(InvOp invOp, + LogicalResult matchAndRewrite(InvOp op, PatternRewriter& rewriter) const override { - auto bodyUnitary = invOp.getBodyUnitary(); - auto innerCtrlOp = dyn_cast(bodyUnitary.getOperation()); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto innerCtrlOp = dyn_cast(op.getBodyUnitary(0).getOperation()); if (!innerCtrlOp) { return failure(); } const auto numControls = innerCtrlOp.getNumControls(); const auto numTargets = innerCtrlOp.getNumTargets(); - auto invTargets = invOp.getInputQubits(); - auto controls = invTargets.take_front(numControls); - auto targets = invTargets.take_back(numTargets); + auto outerQubits = op.getQubitsIn(); + auto controls = outerQubits.take_front(numControls); + auto targets = outerQubits.take_back(numTargets); rewriter.replaceOpWithNewOp( - invOp, controls, targets, - [&](ValueRange newTargetArgs) -> SmallVector { + op, controls, targets, + [&](ValueRange targetArgs) -> SmallVector { return InvOp::create( - rewriter, invOp.getLoc(), newTargetArgs, - [&](ValueRange invArgs) -> SmallVector { + rewriter, op.getLoc(), targetArgs, + [&](ValueRange qubitArgs) -> SmallVector { + auto* innerCtrlBody = innerCtrlOp.getBody(); IRMapping mapping; - auto* innerBody = innerCtrlOp.getBody(); - for (size_t i = 0; i < innerCtrlOp.getNumTargets(); - ++i) { - mapping.map(innerBody->getArgument(i), invArgs[i]); + utils::populateMapping(mapping, *innerCtrlBody, + innerCtrlOp.getTargetsIn(), + outerQubits, targets, qubitArgs); + for (auto& op : innerCtrlBody->without_terminator()) { + rewriter.clone(op, mapping); + } + SmallVector yields; + for (auto value : + innerCtrlBody->getTerminator()->getOperands()) { + yields.push_back(mapping.lookup(value)); } - auto* cloned = rewriter.clone( - *innerCtrlOp.getBodyUnitary().getOperation(), - mapping); - return cloned->getResults(); + return yields; }) .getResults(); }); @@ -88,14 +97,24 @@ struct InlineSelfAdjoint final : OpRewritePattern { LogicalResult matchAndRewrite(InvOp op, PatternRewriter& rewriter) const override { - auto* innerOp = op.getBodyUnitary().getOperation(); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto* innerOp = op.getBodyUnitary(0).getOperation(); if (!isa(innerOp)) { return failure(); } + const auto numQubits = op.getNumQubits(); + auto outerQubits = op.getInputQubits(); + SmallVector qubits; + for (auto qubit : innerOp->getOperands().take_front(numQubits)) { + qubits.push_back(utils::getValueFromBlockArgument(qubit, outerQubits)); + } + rewriter.moveOpBefore(innerOp, op); - innerOp->setOperands(0, op.getNumQubits(), op.getInputQubits()); + innerOp->setOperands(0, numQubits, qubits); rewriter.replaceOp(op, innerOp->getResults()); return success(); } @@ -112,138 +131,192 @@ struct ReplaceWithKnownGates final : OpRewritePattern { LogicalResult matchAndRewrite(InvOp op, PatternRewriter& rewriter) const override { - auto* innerOp = op.getBodyUnitary().getOperation(); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto* innerOp = op.getBodyUnitary(0).getOperation(); + + auto loc = op.getLoc(); + auto outerQubits = op.getInputQubits(); return TypeSwitch(innerOp) .Case([&](auto g) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), g.getTheta()); + Value negTheta = arith::NegFOp::create(rewriter, loc, g.getTheta()); rewriter.replaceOpWithNewOp(op, negTheta); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + .Case([&](auto t) { + rewriter.replaceOpWithNewOp( + op, utils::getValueFromBlockArgument(t.getInputTarget(0), + outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + .Case([&](auto tdg) { + rewriter.replaceOpWithNewOp( + op, utils::getValueFromBlockArgument(tdg.getInputTarget(0), + outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + .Case([&](auto s) { + rewriter.replaceOpWithNewOp( + op, utils::getValueFromBlockArgument(s.getInputTarget(0), + outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + .Case([&](auto sdg) { + rewriter.replaceOpWithNewOp( + op, utils::getValueFromBlockArgument(sdg.getInputTarget(0), + outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + .Case([&](auto sx) { + rewriter.replaceOpWithNewOp( + op, utils::getValueFromBlockArgument(sx.getInputTarget(0), + outerQubits)); return success(); }) - .Case([&](auto) { - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0)); + .Case([&](auto sxdg) { + rewriter.replaceOpWithNewOp( + op, utils::getValueFromBlockArgument(sxdg.getInputTarget(0), + outerQubits)); return success(); }) .Case([&](auto p) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), p.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, p.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(p.getInputTarget(0), + outerQubits), + negTheta); return success(); }) .Case([&](auto r) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), r.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), negTheta, - r.getPhi()); + Value negTheta = arith::NegFOp::create(rewriter, loc, r.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(r.getInputTarget(0), + outerQubits), + negTheta, r.getPhi()); return success(); }) .Case([&](auto rx) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rx.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rx.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rx.getInputTarget(0), + outerQubits), + negTheta); return success(); }) .Case([&](auto u) { - Value newPhi = - arith::NegFOp::create(rewriter, op.getLoc(), u.getLambda()); - Value newLambda = - arith::NegFOp::create(rewriter, op.getLoc(), u.getPhi()); - Value newTheta = - arith::NegFOp::create(rewriter, op.getLoc(), u.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), newTheta, - newPhi, newLambda); + Value newPhi = arith::NegFOp::create(rewriter, loc, u.getLambda()); + Value newLambda = arith::NegFOp::create(rewriter, loc, u.getPhi()); + Value newTheta = arith::NegFOp::create(rewriter, loc, u.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(u.getInputTarget(0), + outerQubits), + newTheta, newPhi, newLambda); return success(); }) - .Case([&](auto u) { + .Case([&](auto u2) { auto pi = arith::ConstantOp::create( - rewriter, op.getLoc(), - rewriter.getF64FloatAttr(std::numbers::pi)); - Value newPhi = - arith::NegFOp::create(rewriter, op.getLoc(), u.getLambda()); - newPhi = arith::SubFOp::create(rewriter, op.getLoc(), newPhi, pi); - Value newLambda = - arith::NegFOp::create(rewriter, op.getLoc(), u.getPhi()); - newLambda = - arith::AddFOp::create(rewriter, op.getLoc(), newLambda, pi); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), newPhi, - newLambda); + rewriter, loc, rewriter.getF64FloatAttr(std::numbers::pi)); + Value newPhi = arith::NegFOp::create(rewriter, loc, u2.getLambda()); + newPhi = arith::SubFOp::create(rewriter, loc, newPhi, pi); + Value newLambda = arith::NegFOp::create(rewriter, loc, u2.getPhi()); + newLambda = arith::AddFOp::create(rewriter, loc, newLambda, pi); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(u2.getInputTarget(0), + outerQubits), + newPhi, newLambda); return success(); }) .Case([&](auto rxx) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rxx.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), - op.getInputTarget(1), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rxx.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rxx.getInputTarget(0), + outerQubits), + utils::getValueFromBlockArgument(rxx.getInputTarget(1), + outerQubits), + negTheta); return success(); }) .Case([&](auto ry) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), ry.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, ry.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(ry.getInputTarget(0), + outerQubits), + negTheta); return success(); }) .Case([&](auto ryy) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), ryy.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), - op.getInputTarget(1), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, ryy.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(ryy.getInputTarget(0), + outerQubits), + utils::getValueFromBlockArgument(ryy.getInputTarget(1), + outerQubits), + negTheta); return success(); }) .Case([&](auto rz) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rz.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rz.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rz.getInputTarget(0), + outerQubits), + negTheta); return success(); }) .Case([&](auto rzx) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rzx.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), - op.getInputTarget(1), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rzx.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rzx.getInputTarget(0), + outerQubits), + utils::getValueFromBlockArgument(rzx.getInputTarget(1), + outerQubits), + negTheta); return success(); }) .Case([&](auto rzz) { - Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), rzz.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), - op.getInputTarget(1), negTheta); + Value negTheta = arith::NegFOp::create(rewriter, loc, rzz.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(rzz.getInputTarget(0), + outerQubits), + utils::getValueFromBlockArgument(rzz.getInputTarget(1), + outerQubits), + negTheta); return success(); }) .Case([&](auto xxminusyy) { - Value negTheta = arith::NegFOp::create(rewriter, op.getLoc(), - xxminusyy.getTheta()); + Value negTheta = + arith::NegFOp::create(rewriter, loc, xxminusyy.getTheta()); rewriter.replaceOpWithNewOp( - op, op.getInputTarget(0), op.getInputTarget(1), negTheta, - xxminusyy.getBeta()); + op, + utils::getValueFromBlockArgument(xxminusyy.getInputTarget(0), + outerQubits), + utils::getValueFromBlockArgument(xxminusyy.getInputTarget(1), + outerQubits), + negTheta, xxminusyy.getBeta()); return success(); }) .Case([&](auto xxplusyy) { Value negTheta = - arith::NegFOp::create(rewriter, op.getLoc(), xxplusyy.getTheta()); - rewriter.replaceOpWithNewOp(op, op.getInputTarget(0), - op.getInputTarget(1), - negTheta, xxplusyy.getBeta()); + arith::NegFOp::create(rewriter, loc, xxplusyy.getTheta()); + rewriter.replaceOpWithNewOp( + op, + utils::getValueFromBlockArgument(xxplusyy.getInputTarget(0), + outerQubits), + utils::getValueFromBlockArgument(xxplusyy.getInputTarget(1), + outerQubits), + negTheta, xxplusyy.getBeta()); return success(); }) .Default([&](auto) { return failure(); }); @@ -258,30 +331,67 @@ struct CancelNestedInv final : OpRewritePattern { LogicalResult matchAndRewrite(InvOp op, PatternRewriter& rewriter) const override { - auto* innerUnitary = op.getBodyUnitary().getOperation(); - auto innerInvOp = dyn_cast(innerUnitary); + if (op.getNumBodyUnitaries() != 1) { + return failure(); + } + auto innerInvOp = dyn_cast(op.getBodyUnitary(0).getOperation()); if (!innerInvOp) { return failure(); } - auto* innerInnerUnitary = innerInvOp.getBodyUnitary().getOperation(); - rewriter.moveOpBefore(innerInnerUnitary, op); - innerInnerUnitary->setOperands(0, op.getNumQubits(), op.getInputQubits()); - rewriter.replaceOp(op, innerInnerUnitary->getResults()); + if (innerInvOp.getNumBodyUnitaries() != 1) { + return failure(); + } + auto* innerInnerOp = innerInvOp.getBodyUnitary(0).getOperation(); + + const auto numQubits = op.getNumQubits(); + auto outerQubits = op.getInputQubits(); + auto innerQubits = innerInvOp.getInputQubits(); + SmallVector qubits; + for (auto qubit : innerInnerOp->getOperands().take_front(numQubits)) { + auto innerQubit = utils::getValueFromBlockArgument(qubit, innerQubits); + qubits.push_back( + utils::getValueFromBlockArgument(innerQubit, outerQubits)); + } + rewriter.moveOpBefore(innerInnerOp, op); + innerInnerOp->setOperands(0, numQubits, qubits); + rewriter.replaceOp(op, innerInnerOp->getResults()); + return success(); + } +}; + +/** + * @brief Erase inverse modifiers that do not have any body unitaries. + */ +struct EraseEmptyInv final : OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + LogicalResult matchAndRewrite(InvOp op, + PatternRewriter& rewriter) const override { + if (op.getNumBodyUnitaries() != 0) { + return failure(); + } + + rewriter.replaceOp(op, op.getOperands()); return success(); } }; } // namespace -UnitaryOpInterface InvOp::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())); +size_t InvOp::getNumBodyUnitaries() { + return llvm::count_if( + *getBody(), [](Operation& op) { return isa(op); }); +} + +UnitaryOpInterface InvOp::getBodyUnitary(const size_t i) { + auto unitaries = llvm::make_filter_range( + *getBody(), [](Operation& op) { return isa(op); }); + auto it = std::next(unitaries.begin(), static_cast(i)); + if (it == unitaries.end()) { + llvm::reportFatalUsageError("Unitary index out of bounds"); + } + return cast(*it); } Value InvOp::getInputQubit(const size_t i) { @@ -322,7 +432,7 @@ void InvOp::build(OpBuilder& odsBuilder, OperationState& odsState, build(odsBuilder, odsState, qubits); auto& block = odsState.regions.front()->emplaceBlock(); - const auto qubitType = QubitType::get(odsBuilder.getContext()); + auto qubitType = QubitType::get(odsBuilder.getContext()); for (size_t i = 0; i < qubits.size(); ++i) { block.addArgument(qubitType, odsState.location); } @@ -335,60 +445,38 @@ void InvOp::build(OpBuilder& odsBuilder, OperationState& odsState, LogicalResult InvOp::verify() { auto& block = *getBody(); - if (block.getOperations().size() < 2) { - return emitOpError("body region must have at least two operations"); + if (llvm::any_of(*getBody(), [](Operation& op) { + return isa(op); + })) { + return emitOpError("body must not contain non-unitary quantum operations"); + } + if (!isa(block.back())) { + return emitOpError( + "last operation in body region must be a yield operation"); } + const auto numTargets = getNumTargets(); if (block.getArguments().size() != numTargets) { return emitOpError( "number of block arguments must match the number of targets"); } - const auto qubitType = QubitType::get(getContext()); + 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; + SmallPtrSet uniqueQubitsIn; + for (const auto& target : getQubitsIn()) { + if (!uniqueQubitsIn.insert(target).second) { + return emitOpError("duplicate qubit found"); } } @@ -398,15 +486,19 @@ LogicalResult InvOp::verify() { void InvOp::getCanonicalizationPatterns(RewritePatternSet& results, MLIRContext* context) { results.add(context); + CancelNestedInv, EraseEmptyInv>(context); } std::optional InvOp::getUnitaryMatrix() { - auto&& bodyUnitary = getBodyUnitary(); + if (getNumBodyUnitaries() != 1) { + return std::nullopt; + } + + auto bodyUnitary = getBodyUnitary(0); if (!bodyUnitary) { return std::nullopt; } - auto&& targetMatrix = bodyUnitary.getUnitaryMatrix(); + auto targetMatrix = bodyUnitary.getUnitaryMatrix(); if (!targetMatrix) { return std::nullopt; } diff --git a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp index a3ce816081..f1cb23a849 100644 --- a/mlir/lib/Dialect/QCO/IR/QCOOps.cpp +++ b/mlir/lib/Dialect/QCO/IR/QCOOps.cpp @@ -11,6 +11,7 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" // IWYU pragma: associated +#include "mlir/Dialect/Utils/Utils.h" #include #include @@ -37,57 +38,12 @@ using namespace mlir::qco; static ParseResult parseTargetAliasing(OpAsmParser& parser, Region& region, SmallVectorImpl& operands) { - // 1. Parse the opening parenthesis - if (parser.parseLParen()) { - return failure(); - } - - // Temporary storage for block arguments we are about to create - SmallVector blockArgs; - - // 2. Prepare to parse the list - if (failed(parser.parseOptionalRParen())) { - do { - OpAsmParser::Argument newArg; // The "new" variable name - OpAsmParser::UnresolvedOperand oldOperand; // The "old" input variable - - // Parse "%new" - if (parser.parseArgument(newArg)) { - return failure(); - } - - // Parse "=" - if (parser.parseEqual()) { - return failure(); - } - - // Parse "%old" - if (parser.parseOperand(oldOperand)) { - return failure(); - } - operands.push_back(oldOperand); - - // Hard-code QubitType since targets in qco.ctrl are always qubits. - // This avoids double-binding type($targets_in) in the assembly format - // while keeping the parser simple and the assembly format clean. - newArg.type = QubitType::get(parser.getBuilder().getContext()); - blockArgs.push_back(newArg); - - } while (succeeded(parser.parseOptionalComma())); - - if (parser.parseRParen()) { - return failure(); - } - } - - // 4. Parse the Region - // We explicitly pass the blockArgs we just parsed so they become the entry - // block! - if (parser.parseRegion(region, blockArgs)) { - return failure(); - } + return utils::parseTargetAliasing(parser, region, operands); +} - return success(); +static void printTargetAliasing(OpAsmPrinter& printer, Operation* /*op*/, + Region& region, OperandRange targetsIn) { + utils::printTargetAliasing(printer, region, targetsIn); } ParseResult IfOp::parse(::mlir::OpAsmParser& parser, @@ -213,30 +169,6 @@ void IfOp::print(OpAsmPrinter& p) { p.printOptionalAttrDict((*this)->getAttrs()); } -static void printTargetAliasing(OpAsmPrinter& printer, Operation* /*op*/, - Region& region, OperandRange targetsIn) { - printer << "("; - if (region.empty()) { - printer << ") "; - printer.printRegion(region, false); - return; - } - Block& entryBlock = region.front(); - - const auto numTargets = targetsIn.size(); - for (unsigned i = 0; i < numTargets; ++i) { - if (i > 0) { - printer << ", "; - } - printer.printOperand(entryBlock.getArgument(i)); - printer << " = "; - printer.printOperand(targetsIn[i]); - } - printer << ") "; - - printer.printRegion(region, false); -} - //===----------------------------------------------------------------------===// // Dialect //===----------------------------------------------------------------------===// diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp index 0ca22726a1..3d874533b6 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp @@ -162,7 +162,8 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { if (!cnotGate) { return failure(); } - if (!isa(cnotGate.getBodyUnitary()) || + if (cnotGate.getNumBodyUnitaries() != 1 || + !isa(cnotGate.getBodyUnitary(0)) || cnotGate.getOutputTarget(0) != inQubitHadamard) { return failure(); } diff --git a/mlir/lib/Support/IRVerification.cpp b/mlir/lib/Support/IRVerification.cpp index eaac426f0a..0221464606 100644 --- a/mlir/lib/Support/IRVerification.cpp +++ b/mlir/lib/Support/IRVerification.cpp @@ -10,6 +10,7 @@ #include "mlir/Support/IRVerification.h" +#include "mlir/Dialect/QC/IR/QCOps.h" #include "mlir/Dialect/QTensor/IR/QTensorUtils.h" #include @@ -33,11 +34,13 @@ #include #include #include +#include #include #include #include #include +#include #include using namespace mlir; @@ -469,7 +472,6 @@ static bool areOperationsEquivalent(Operation* lhs, Operation* rhs, if (!rhsConst) { return false; } - if (!areConstantAttributesEquivalent(lhsConst.getValue(), rhsConst.getValue())) { return false; @@ -513,17 +515,37 @@ static bool areOperationsEquivalent(Operation* lhs, Operation* rhs, return false; } - // Check operands according to value mapping - for (auto [lhsOperand, rhsOperand] : - llvm::zip(lhs->getOperands(), rhs->getOperands())) { - if (auto it = valueMap.find(lhsOperand); it != valueMap.end()) { - // Value already mapped, must match - if (it->second != rhsOperand) { + ValueRange lhsOperands; + ValueRange rhsOperands; + if (auto lhsCtrl = dyn_cast(lhs)) { + auto rhsCtrl = dyn_cast(rhs); + if (!rhsCtrl) { + return false; + } + if (lhsCtrl.getTargets().size() != rhsCtrl.getTargets().size()) { + return false; + } + for (auto [lhsTarget, lhsArg] : + llvm::zip(lhsCtrl.getTargets(), lhsCtrl.getBody()->getArguments())) { + auto rhsTarget = valueMap[lhsTarget]; + if (!llvm::is_contained(rhsCtrl.getTargets(), rhsTarget)) { return false; } - } else { - // Establish new mapping - valueMap[lhsOperand] = rhsOperand; + auto it = llvm::find(rhsCtrl.getTargets(), rhsTarget); + auto index = std::distance(rhsCtrl.getTargets().begin(), it); + valueMap[lhsArg] = rhsCtrl.getBody()->getArgument(index); + } + lhsOperands = lhsCtrl.getControls(); + rhsOperands = rhsCtrl.getControls(); + } else { + lhsOperands = lhs->getOperands(); + rhsOperands = rhs->getOperands(); + } + + // Check operands according to value mapping + for (auto [lhsOperand, rhsOperand] : llvm::zip(lhsOperands, rhsOperands)) { + if (!areValuesEquivalent(lhsOperand, rhsOperand, valueMap)) { + return false; } } @@ -725,7 +747,9 @@ static bool areBlocksEquivalent(Block& lhs, Block& rhs, if (lhsArg.getType() != rhsArg.getType()) { return false; } - valueMap[lhsArg] = rhsArg; + if (!valueMap.contains(lhsArg)) { + valueMap[lhsArg] = rhsArg; + } } // Collect all operations diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 0781ed84e7..994eaf071b 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -8,14 +8,11 @@ * Licensed under the MIT License */ -#include "ir/QuantumComputation.hpp" #include "mlir/Compiler/CompilerPipeline.h" #include "mlir/Dialect/QC/IR/QCDialect.h" -#include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" +#include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" -#include "qasm3/Exception.hpp" -#include "qasm3/Importer.hpp" #include #include @@ -33,9 +30,9 @@ #include #include #include +#include #include -#include #include #include @@ -86,24 +83,11 @@ static llvm::cl::opt enableHadamardLifting( llvm::cl::init(false)); /** - * @brief Load and parse a .qasm file + * @brief Load and parse a .qasm file, dispatching to the chosen import path. */ static OwningOpRef loadQASMFile(llvm::StringRef filename, MLIRContext* context) { - try { - // Parse the input QASM file - const ::qc::QuantumComputation qc = - qasm3::Importer::importf(filename.str()); - // Translate to MLIR dialect QC - return translateQuantumComputationToQC(context, qc); - } catch (const qasm3::CompilerError& exception) { - llvm::errs() << "Failed to parse QASM file '" << filename << "': '" - << exception.what() << "'\n"; - } catch (const std::exception& exception) { - llvm::errs() << "Failed to load QASM file '" << filename << "': '" - << exception.what() << "'\n"; - } - return nullptr; + return qc::translateQASM3ToQC(context, filename.str()); } /** @@ -153,7 +137,7 @@ int main(int argc, char** argv) { DialectRegistry registry; registry .insert(); MLIRContext context(registry); diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 44618eedae..d9072d6630 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -686,6 +686,9 @@ INSTANTIATE_TEST_SUITE_P( "MultipleControlledXXMinusYY", MQT_NAMED_BUILDER(qc::multipleControlledXxMinusYY), nullptr, MQT_NAMED_BUILDER(mlir::qc::multipleControlledXxMinusYY), - MQT_NAMED_BUILDER(mlir::qir::multipleControlledXxMinusYY)})); + MQT_NAMED_BUILDER(mlir::qir::multipleControlledXxMinusYY)}, + CompilerPipelineTestCase{"CtrlTwo", MQT_NAMED_BUILDER(qc::ctrlTwo), + nullptr, MQT_NAMED_BUILDER(mlir::qc::ctrlTwo), + MQT_NAMED_BUILDER(mlir::qir::ctrlTwo)})); } // namespace mqt::test::compiler diff --git a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp index 4bd2b24615..7dda9ccfda 100644 --- a/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp +++ b/mlir/unittests/Conversion/QCOToQC/test_qco_to_qc.cpp @@ -144,6 +144,20 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qc::allocDeallocPair)})); /// @} +/// \name QCOToQC/Modifiers/CtrlOp.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCOCtrlOpTest, QCOToQCTest, + testing::Values(QCOToQCTestCase{"CtrlTwo", MQT_NAMED_BUILDER(qco::ctrlTwo), + MQT_NAMED_BUILDER(qc::ctrlTwo)}, + QCOToQCTestCase{"CtrlTwoMixed", + MQT_NAMED_BUILDER(qco::ctrlTwoMixed), + MQT_NAMED_BUILDER(qc::ctrlTwoMixed)}, + QCOToQCTestCase{"CtrlInvTwo", + MQT_NAMED_BUILDER(qco::ctrlInvTwo), + MQT_NAMED_BUILDER(qc::ctrlInvTwo)})); +/// @} + /// \name QCOToQC/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( @@ -160,7 +174,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qc::dcx)}, QCOToQCTestCase{"InverseMultipleControlledDCX", MQT_NAMED_BUILDER(qco::inverseMultipleControlledDcx), - MQT_NAMED_BUILDER(qc::multipleControlledDcx)})); + MQT_NAMED_BUILDER(qc::multipleControlledDcx)}, + QCOToQCTestCase{"InvTwo", MQT_NAMED_BUILDER(qco::invTwo), + MQT_NAMED_BUILDER(qc::invTwo)}, + QCOToQCTestCase{"InvCtrlTwo", MQT_NAMED_BUILDER(qco::invCtrlTwo), + MQT_NAMED_BUILDER(qc::ctrlInvTwo)})); /// @} /// \name QCOToQC/Operations/StandardGates/BarrierOp.cpp diff --git a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp index 3f0df25542..00b2c7fe7b 100644 --- a/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp +++ b/mlir/unittests/Conversion/QCToQCO/test_qc_to_qco.cpp @@ -143,6 +143,20 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qco::allocSinkPair)})); /// @} +/// \name QCToQCO/Modifiers/CtrlOp.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P( + QCCtrlOpTest, QCToQCOTest, + testing::Values(QCToQCOTestCase{"CtrlTwo", MQT_NAMED_BUILDER(qc::ctrlTwo), + MQT_NAMED_BUILDER(qco::ctrlTwo)}, + QCToQCOTestCase{"CtrlTwoMixed", + MQT_NAMED_BUILDER(qc::ctrlTwoMixed), + MQT_NAMED_BUILDER(qco::ctrlTwoMixed)}, + QCToQCOTestCase{"CtrlInvTwo", + MQT_NAMED_BUILDER(qc::ctrlInvTwo), + MQT_NAMED_BUILDER(qco::ctrlInvTwo)})); +/// @} + /// \name QCToQCO/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( @@ -151,10 +165,13 @@ INSTANTIATE_TEST_SUITE_P( // iSWAP cannot be inverted with current canonicalization QCToQCOTestCase{"InverseiSWAP", MQT_NAMED_BUILDER(qc::inverseIswap), MQT_NAMED_BUILDER(qco::inverseIswap)}, - QCToQCOTestCase{ - "InverseMultipleControllediSWAP", - MQT_NAMED_BUILDER(qc::inverseMultipleControlledIswap), - MQT_NAMED_BUILDER(qco::inverseMultipleControlledIswap)})); + QCToQCOTestCase{"InverseMultipleControllediSWAP", + MQT_NAMED_BUILDER(qc::inverseMultipleControlledIswap), + MQT_NAMED_BUILDER(qco::inverseMultipleControlledIswap)}, + QCToQCOTestCase{"InvTwo", MQT_NAMED_BUILDER(qc::invTwo), + MQT_NAMED_BUILDER(qco::invTwo)}, + QCToQCOTestCase{"InvCtrlTwo", MQT_NAMED_BUILDER(qc::invCtrlTwo), + MQT_NAMED_BUILDER(qco::ctrlInvTwo)})); /// @} /// \name QCToQCO/Operations/StandardGates/BarrierOp.cpp diff --git a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp index cd8bcf6073..6de8bf8483 100644 --- a/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp +++ b/mlir/unittests/Conversion/QCToQIR/test_qc_to_qir.cpp @@ -649,3 +649,11 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(qc::allocDeallocPair), MQT_NAMED_BUILDER(qir::emptyQIR)})); /// @} + +/// \name QCToQIR/Modifiers/CtrlOp.cpp +/// @{ +INSTANTIATE_TEST_SUITE_P(QCToQIRCtrlOpTest, QCToQIRTest, + testing::Values(QCToQIRTestCase{ + "NestedCtrlTwo", MQT_NAMED_BUILDER(qc::ctrlTwo), + MQT_NAMED_BUILDER(qir::ctrlTwo)})); +/// @} diff --git a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp index 97e4627363..4d0f56912b 100644 --- a/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp +++ b/mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp @@ -117,26 +117,31 @@ TEST_F(QCTest, BuilderRejectsMixedStaticAndDynamicQubitAllocationModes) { /// @{ INSTANTIATE_TEST_SUITE_P( QCCtrlOpTest, QCTest, - testing::Values(QCTestCase{"TrivialCtrl", MQT_NAMED_BUILDER(trivialCtrl), - MQT_NAMED_BUILDER(rxx)}, - QCTestCase{"NestedCtrl", MQT_NAMED_BUILDER(nestedCtrl), - MQT_NAMED_BUILDER(multipleControlledRxx)}, - QCTestCase{"TripleNestedCtrl", - MQT_NAMED_BUILDER(tripleNestedCtrl), - MQT_NAMED_BUILDER(tripleControlledRxx)}, - QCTestCase{"CtrlInvSandwich", - MQT_NAMED_BUILDER(ctrlInvSandwich), - MQT_NAMED_BUILDER(multipleControlledRxx)}, - QCTestCase{"DoubleNestedCtrlTwoQubits", - MQT_NAMED_BUILDER(doubleNestedCtrlTwoQubits), - MQT_NAMED_BUILDER(fourControlledRxx)})); + testing::Values( + QCTestCase{"TrivialCtrl", MQT_NAMED_BUILDER(trivialCtrl), + MQT_NAMED_BUILDER(rxx)}, + QCTestCase{"EmptyCtrl", MQT_NAMED_BUILDER(emptyCtrl), + MQT_NAMED_BUILDER(rxx)}, + QCTestCase{"NestedCtrl", MQT_NAMED_BUILDER(nestedCtrl), + MQT_NAMED_BUILDER(multipleControlledRxx)}, + QCTestCase{"TripleNestedCtrl", MQT_NAMED_BUILDER(tripleNestedCtrl), + MQT_NAMED_BUILDER(tripleControlledRxx)}, + QCTestCase{"CtrlInvSandwich", MQT_NAMED_BUILDER(ctrlInvSandwich), + MQT_NAMED_BUILDER(multipleControlledRxx)}, + QCTestCase{"DoubleNestedCtrlTwoQubits", + MQT_NAMED_BUILDER(doubleNestedCtrlTwoQubits), + MQT_NAMED_BUILDER(fourControlledRxx)}, + QCTestCase{"NestedCtrlTwo", MQT_NAMED_BUILDER(nestedCtrlTwo), + MQT_NAMED_BUILDER(ctrlTwo)})); /// @} /// \name QC/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QCInvOpTest, QCTest, - testing::Values(QCTestCase{"NestedInv", MQT_NAMED_BUILDER(nestedInv), + testing::Values(QCTestCase{"EmptyInv", MQT_NAMED_BUILDER(emptyInv), + MQT_NAMED_BUILDER(rxx)}, + QCTestCase{"NestedInv", MQT_NAMED_BUILDER(nestedInv), MQT_NAMED_BUILDER(rxx)}, QCTestCase{"TripleNestedInv", MQT_NAMED_BUILDER(tripleNestedInv), diff --git a/mlir/unittests/Dialect/QC/Translation/CMakeLists.txt b/mlir/unittests/Dialect/QC/Translation/CMakeLists.txt index de8c687a8f..c711b25c8a 100644 --- a/mlir/unittests/Dialect/QC/Translation/CMakeLists.txt +++ b/mlir/unittests/Dialect/QC/Translation/CMakeLists.txt @@ -8,7 +8,7 @@ file(GLOB_RECURSE TEST_SOURCES "*.cpp") -set(target_name mqt-core-mlir-unittest-quantum-computation-translation) +set(target_name mqt-core-mlir-unittest-qc-translation) add_executable(${target_name} ${TEST_SOURCES}) target_link_libraries( @@ -19,6 +19,7 @@ target_link_libraries( MLIRQCProgramBuilder MLIRQCTranslation MLIRQCPrograms + MLIRQASMPrograms MLIRQuantumComputationPrograms MQT::CoreIR) diff --git a/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp new file mode 100644 index 0000000000..639ef7253b --- /dev/null +++ b/mlir/unittests/Dialect/QC/Translation/test_qasm3_translation.cpp @@ -0,0 +1,352 @@ +/* + * 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 "TestCaseUtils.h" +#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h" +#include "mlir/Dialect/QC/IR/QCDialect.h" +#include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h" +#include "mlir/Support/IRVerification.h" +#include "mlir/Support/Passes.h" +#include "qasm_programs.h" +#include "qc_programs.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace mlir; + +namespace { + +struct QASM3TranslationTestCase { + std::string name; + std::string source; + mqt::test::NamedBuilder referenceBuilder; + + friend std::ostream& operator<<(std::ostream& os, + const QASM3TranslationTestCase& test); +}; + +// NOLINTNEXTLINE(llvm-prefer-static-over-anonymous-namespace) +std::ostream& operator<<(std::ostream& os, + const QASM3TranslationTestCase& test) { + return os << "QASM3Translation{" << test.name << ", reference=" + << mqt::test::displayName(test.referenceBuilder.name) << "}"; +} + +class QASM3TranslationTest + : public testing::TestWithParam { +protected: + std::unique_ptr context; + + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + } +}; + +} // namespace + +TEST_P(QASM3TranslationTest, ProgramEquivalence) { + const auto name = " (" + GetParam().name + ")"; + const auto& source = GetParam().source; + const auto referenceBuilder = GetParam().referenceBuilder; + mqt::test::DeferredPrinter printer; + + std::istringstream input(source); + auto translated = qc::translateQASM3ToQC(context.get(), input); + ASSERT_TRUE(translated); + printer.record(translated.get(), "Translated QC IR" + name); + EXPECT_TRUE(verify(*translated).succeeded()); + + EXPECT_TRUE(runQCCleanupPipeline(translated.get()).succeeded()); + printer.record(translated.get(), "Canonicalized Translated QC IR" + name); + EXPECT_TRUE(verify(*translated).succeeded()); + + auto reference = + qc::QCProgramBuilder::build(context.get(), referenceBuilder.fn); + ASSERT_TRUE(reference); + printer.record(reference.get(), "Reference QC IR" + name); + EXPECT_TRUE(verify(*reference).succeeded()); + + EXPECT_TRUE(runQCCleanupPipeline(reference.get()).succeeded()); + printer.record(reference.get(), "Canonicalized Reference QC IR" + name); + EXPECT_TRUE(verify(*reference).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(translated.get(), reference.get())); +} + +INSTANTIATE_TEST_SUITE_P( + QASM3TranslationProgramsTest, QASM3TranslationTest, + testing::Values( + + QASM3TranslationTestCase{"AllocQubit", qasm::allocQubit, + MQT_NAMED_BUILDER(qc::allocQubit)}, + QASM3TranslationTestCase{"AllocQubitRegister", qasm::allocQubitRegister, + MQT_NAMED_BUILDER(qc::allocQubitRegister)}, + QASM3TranslationTestCase{ + "AllocMultipleQubitRegisters", qasm::allocMultipleQubitRegisters, + MQT_NAMED_BUILDER(qc::allocMultipleQubitRegisters)}, + QASM3TranslationTestCase{"AllocLargeRegister", qasm::allocLargeRegister, + MQT_NAMED_BUILDER(qc::allocLargeRegister)}, + QASM3TranslationTestCase{ + "SingleMeasurementToSingleBit", qasm::singleMeasurementToSingleBit, + MQT_NAMED_BUILDER(qc::singleMeasurementToSingleBit)}, + QASM3TranslationTestCase{ + "RepeatedMeasurementToSameBit", qasm::repeatedMeasurementToSameBit, + MQT_NAMED_BUILDER(qc::repeatedMeasurementToSameBit)}, + QASM3TranslationTestCase{ + "RepeatedMeasurementToDifferentBits", + qasm::repeatedMeasurementToDifferentBits, + MQT_NAMED_BUILDER(qc::repeatedMeasurementToDifferentBits)}, + QASM3TranslationTestCase{ + "MultipleClassicalRegistersAndMeasurements", + qasm::multipleClassicalRegistersAndMeasurements, + MQT_NAMED_BUILDER(qc::multipleClassicalRegistersAndMeasurements)}, + QASM3TranslationTestCase{ + "ResetQubitAfterSingleOp", qasm::resetQubitAfterSingleOp, + MQT_NAMED_BUILDER(qc::resetQubitAfterSingleOp)}, + QASM3TranslationTestCase{ + "ResetMultipleQubitsAfterSingleOp", + qasm::resetMultipleQubitsAfterSingleOp, + MQT_NAMED_BUILDER(qc::resetMultipleQubitsAfterSingleOp)}, + QASM3TranslationTestCase{ + "RepeatedResetAfterSingleOp", qasm::repeatedResetAfterSingleOp, + MQT_NAMED_BUILDER(qc::repeatedResetAfterSingleOp)}, + QASM3TranslationTestCase{"GlobalPhase", qasm::globalPhase, + MQT_NAMED_BUILDER(qc::globalPhase)}, + QASM3TranslationTestCase{"Identity", qasm::identity, + MQT_NAMED_BUILDER(qc::identity)}, + QASM3TranslationTestCase{ + "SingleControlledIdentity", qasm::singleControlledIdentity, + MQT_NAMED_BUILDER(qc::singleControlledIdentity)}, + QASM3TranslationTestCase{ + "MultipleControlledIdentity", qasm::multipleControlledIdentity, + MQT_NAMED_BUILDER(qc::multipleControlledIdentity)}, + QASM3TranslationTestCase{"X", qasm::x, MQT_NAMED_BUILDER(qc::x)}, + QASM3TranslationTestCase{"SingleControlledX", qasm::singleControlledX, + MQT_NAMED_BUILDER(qc::singleControlledX)}, + QASM3TranslationTestCase{"MultipleControlledX", + qasm::multipleControlledX, + MQT_NAMED_BUILDER(qc::multipleControlledX)}, + QASM3TranslationTestCase{"Y", qasm::y, MQT_NAMED_BUILDER(qc::y)}, + QASM3TranslationTestCase{"SingleControlledY", qasm::singleControlledY, + MQT_NAMED_BUILDER(qc::singleControlledY)}, + QASM3TranslationTestCase{"MultipleControlledY", + qasm::multipleControlledY, + MQT_NAMED_BUILDER(qc::multipleControlledY)}, + QASM3TranslationTestCase{"Z", qasm::z, MQT_NAMED_BUILDER(qc::z)}, + QASM3TranslationTestCase{"SingleControlledZ", qasm::singleControlledZ, + MQT_NAMED_BUILDER(qc::singleControlledZ)}, + QASM3TranslationTestCase{"MultipleControlledZ", + qasm::multipleControlledZ, + MQT_NAMED_BUILDER(qc::multipleControlledZ)}, + QASM3TranslationTestCase{"H", qasm::h, MQT_NAMED_BUILDER(qc::h)}, + QASM3TranslationTestCase{"SingleControlledH", qasm::singleControlledH, + MQT_NAMED_BUILDER(qc::singleControlledH)}, + QASM3TranslationTestCase{"MultipleControlledH", + qasm::multipleControlledH, + MQT_NAMED_BUILDER(qc::multipleControlledH)}, + QASM3TranslationTestCase{"S", qasm::s, MQT_NAMED_BUILDER(qc::s)}, + QASM3TranslationTestCase{"SingleControlledS", qasm::singleControlledS, + MQT_NAMED_BUILDER(qc::singleControlledS)}, + QASM3TranslationTestCase{"MultipleControlledS", + qasm::multipleControlledS, + MQT_NAMED_BUILDER(qc::multipleControlledS)}, + QASM3TranslationTestCase{"Sdg", qasm::sdg, MQT_NAMED_BUILDER(qc::sdg)}, + QASM3TranslationTestCase{"SingleControlledSdg", + qasm::singleControlledSdg, + MQT_NAMED_BUILDER(qc::singleControlledSdg)}, + QASM3TranslationTestCase{"MultipleControlledSdg", + qasm::multipleControlledSdg, + MQT_NAMED_BUILDER(qc::multipleControlledSdg)}, + QASM3TranslationTestCase{"T", qasm::t_, MQT_NAMED_BUILDER(qc::t_)}, + QASM3TranslationTestCase{"SingleControlledT", qasm::singleControlledT, + MQT_NAMED_BUILDER(qc::singleControlledT)}, + QASM3TranslationTestCase{"MultipleControlledT", + qasm::multipleControlledT, + MQT_NAMED_BUILDER(qc::multipleControlledT)}, + QASM3TranslationTestCase{"Tdg", qasm::tdg, MQT_NAMED_BUILDER(qc::tdg)}, + QASM3TranslationTestCase{"SingleControlledTdg", + qasm::singleControlledTdg, + MQT_NAMED_BUILDER(qc::singleControlledTdg)}, + QASM3TranslationTestCase{"MultipleControlledTdg", + qasm::multipleControlledTdg, + MQT_NAMED_BUILDER(qc::multipleControlledTdg)}, + QASM3TranslationTestCase{"SX", qasm::sx, MQT_NAMED_BUILDER(qc::sx)}, + QASM3TranslationTestCase{"SingleControlledSX", qasm::singleControlledSx, + MQT_NAMED_BUILDER(qc::singleControlledSx)}, + QASM3TranslationTestCase{"MultipleControlledSX", + qasm::multipleControlledSx, + MQT_NAMED_BUILDER(qc::multipleControlledSx)}, + QASM3TranslationTestCase{"SXdg", qasm::sxdg, + MQT_NAMED_BUILDER(qc::sxdg)}, + QASM3TranslationTestCase{"SingleControlledSXdg", + qasm::singleControlledSxdg, + MQT_NAMED_BUILDER(qc::singleControlledSxdg)}, + QASM3TranslationTestCase{"MultipleControlledSXdg", + qasm::multipleControlledSxdg, + MQT_NAMED_BUILDER(qc::multipleControlledSxdg)}, + QASM3TranslationTestCase{"RX", qasm::rx, MQT_NAMED_BUILDER(qc::rx)}, + QASM3TranslationTestCase{"SingleControlledRX", qasm::singleControlledRx, + MQT_NAMED_BUILDER(qc::singleControlledRx)}, + QASM3TranslationTestCase{"MultipleControlledRX", + qasm::multipleControlledRx, + MQT_NAMED_BUILDER(qc::multipleControlledRx)}, + QASM3TranslationTestCase{"RY", qasm::ry, MQT_NAMED_BUILDER(qc::ry)}, + QASM3TranslationTestCase{"SingleControlledRY", qasm::singleControlledRy, + MQT_NAMED_BUILDER(qc::singleControlledRy)}, + QASM3TranslationTestCase{"MultipleControlledRY", + qasm::multipleControlledRy, + MQT_NAMED_BUILDER(qc::multipleControlledRy)}, + QASM3TranslationTestCase{"RZ", qasm::rz, MQT_NAMED_BUILDER(qc::rz)}, + QASM3TranslationTestCase{"SingleControlledRZ", qasm::singleControlledRz, + MQT_NAMED_BUILDER(qc::singleControlledRz)}, + QASM3TranslationTestCase{"MultipleControlledRZ", + qasm::multipleControlledRz, + MQT_NAMED_BUILDER(qc::multipleControlledRz)}, + QASM3TranslationTestCase{"P", qasm::p, MQT_NAMED_BUILDER(qc::p)}, + QASM3TranslationTestCase{"SingleControlledP", qasm::singleControlledP, + MQT_NAMED_BUILDER(qc::singleControlledP)}, + QASM3TranslationTestCase{"MultipleControlledP", + qasm::multipleControlledP, + MQT_NAMED_BUILDER(qc::multipleControlledP)}, + QASM3TranslationTestCase{"R", qasm::r, MQT_NAMED_BUILDER(qc::r)}, + QASM3TranslationTestCase{"SingleControlledR", qasm::singleControlledR, + MQT_NAMED_BUILDER(qc::singleControlledR)}, + QASM3TranslationTestCase{"MultipleControlledR", + qasm::multipleControlledR, + MQT_NAMED_BUILDER(qc::multipleControlledR)}, + QASM3TranslationTestCase{"U2", qasm::u2, MQT_NAMED_BUILDER(qc::u2)}, + QASM3TranslationTestCase{"SingleControlledU2", qasm::singleControlledU2, + MQT_NAMED_BUILDER(qc::singleControlledU2)}, + QASM3TranslationTestCase{"MultipleControlledU2", + qasm::multipleControlledU2, + MQT_NAMED_BUILDER(qc::multipleControlledU2)}, + QASM3TranslationTestCase{"U", qasm::u, MQT_NAMED_BUILDER(qc::u)}, + QASM3TranslationTestCase{"SingleControlledU", qasm::singleControlledU, + MQT_NAMED_BUILDER(qc::singleControlledU)}, + QASM3TranslationTestCase{"MultipleControlledU", + qasm::multipleControlledU, + MQT_NAMED_BUILDER(qc::multipleControlledU)}, + QASM3TranslationTestCase{"SWAP", qasm::swap, + MQT_NAMED_BUILDER(qc::swap)}, + QASM3TranslationTestCase{"SingleControlledSWAP", + qasm::singleControlledSwap, + MQT_NAMED_BUILDER(qc::singleControlledSwap)}, + QASM3TranslationTestCase{"MultipleControlledSWAP", + qasm::multipleControlledSwap, + MQT_NAMED_BUILDER(qc::multipleControlledSwap)}, + QASM3TranslationTestCase{"iSWAP", qasm::iswap, + MQT_NAMED_BUILDER(qc::iswap)}, + QASM3TranslationTestCase{"SingleControllediSWAP", + qasm::singleControlledIswap, + MQT_NAMED_BUILDER(qc::singleControlledIswap)}, + QASM3TranslationTestCase{ + "MultipleControllediSWAP", qasm::multipleControlledIswap, + MQT_NAMED_BUILDER(qc::multipleControlledIswap)}, + QASM3TranslationTestCase{"InverseISWAP", qasm::inverseIswap, + MQT_NAMED_BUILDER(qc::inverseIswap)}, + QASM3TranslationTestCase{ + "InverseMultiControlledISWAP", qasm::inverseMultipleControlledIswap, + MQT_NAMED_BUILDER(qc::inverseMultipleControlledIswap)}, + QASM3TranslationTestCase{"DCX", qasm::dcx, MQT_NAMED_BUILDER(qc::dcx)}, + QASM3TranslationTestCase{"SingleControlledDCX", + qasm::singleControlledDcx, + MQT_NAMED_BUILDER(qc::singleControlledDcx)}, + QASM3TranslationTestCase{"MultipleControlledDCX", + qasm::multipleControlledDcx, + MQT_NAMED_BUILDER(qc::multipleControlledDcx)}, + QASM3TranslationTestCase{"ECR", qasm::ecr, MQT_NAMED_BUILDER(qc::ecr)}, + QASM3TranslationTestCase{"SingleControlledECR", + qasm::singleControlledEcr, + MQT_NAMED_BUILDER(qc::singleControlledEcr)}, + QASM3TranslationTestCase{"MultipleControlledECR", + qasm::multipleControlledEcr, + MQT_NAMED_BUILDER(qc::multipleControlledEcr)}, + QASM3TranslationTestCase{"RXX", qasm::rxx, MQT_NAMED_BUILDER(qc::rxx)}, + QASM3TranslationTestCase{"SingleControlledRXX", + qasm::singleControlledRxx, + MQT_NAMED_BUILDER(qc::singleControlledRxx)}, + QASM3TranslationTestCase{"MultipleControlledRXX", + qasm::multipleControlledRxx, + MQT_NAMED_BUILDER(qc::multipleControlledRxx)}, + QASM3TranslationTestCase{"TripleControlledRXX", + qasm::tripleControlledRxx, + MQT_NAMED_BUILDER(qc::tripleControlledRxx)}, + QASM3TranslationTestCase{"RYY", qasm::ryy, MQT_NAMED_BUILDER(qc::ryy)}, + QASM3TranslationTestCase{"SingleControlledRYY", + qasm::singleControlledRyy, + MQT_NAMED_BUILDER(qc::singleControlledRyy)}, + QASM3TranslationTestCase{"MultipleControlledRYY", + qasm::multipleControlledRyy, + MQT_NAMED_BUILDER(qc::multipleControlledRyy)}, + QASM3TranslationTestCase{"RZX", qasm::rzx, MQT_NAMED_BUILDER(qc::rzx)}, + QASM3TranslationTestCase{"SingleControlledRZX", + qasm::singleControlledRzx, + MQT_NAMED_BUILDER(qc::singleControlledRzx)}, + QASM3TranslationTestCase{"MultipleControlledRZX", + qasm::multipleControlledRzx, + MQT_NAMED_BUILDER(qc::multipleControlledRzx)}, + QASM3TranslationTestCase{"RZZ", qasm::rzz, MQT_NAMED_BUILDER(qc::rzz)}, + QASM3TranslationTestCase{"SingleControlledRZZ", + qasm::singleControlledRzz, + MQT_NAMED_BUILDER(qc::singleControlledRzz)}, + QASM3TranslationTestCase{"MultipleControlledRZZ", + qasm::multipleControlledRzz, + MQT_NAMED_BUILDER(qc::multipleControlledRzz)}, + QASM3TranslationTestCase{"XXPlusYY", qasm::xxPlusYY, + MQT_NAMED_BUILDER(qc::xxPlusYY)}, + QASM3TranslationTestCase{ + "SingleControlledXXPlusYY", qasm::singleControlledXxPlusYY, + MQT_NAMED_BUILDER(qc::singleControlledXxPlusYY)}, + QASM3TranslationTestCase{ + "MultipleControlledXXPlusYY", qasm::multipleControlledXxPlusYY, + MQT_NAMED_BUILDER(qc::multipleControlledXxPlusYY)}, + QASM3TranslationTestCase{"XXMinusYY", qasm::xxMinusYY, + MQT_NAMED_BUILDER(qc::xxMinusYY)}, + QASM3TranslationTestCase{ + "SingleControlledXXMinusYY", qasm::singleControlledXxMinusYY, + MQT_NAMED_BUILDER(qc::singleControlledXxMinusYY)}, + QASM3TranslationTestCase{ + "MultipleControlledXXMinusYY", qasm::multipleControlledXxMinusYY, + MQT_NAMED_BUILDER(qc::multipleControlledXxMinusYY)}, + QASM3TranslationTestCase{"Barrier", qasm::barrier, + MQT_NAMED_BUILDER(qc::barrier)}, + QASM3TranslationTestCase{"BarrierTwoQubits", qasm::barrierTwoQubits, + MQT_NAMED_BUILDER(qc::barrierTwoQubits)}, + QASM3TranslationTestCase{"BarrierMultipleQubits", + qasm::barrierMultipleQubits, + MQT_NAMED_BUILDER(qc::barrierMultipleQubits)}, + QASM3TranslationTestCase{"CtrlTwo", qasm::ctrlTwo, + MQT_NAMED_BUILDER(mlir::qc::ctrlTwo)}, + QASM3TranslationTestCase{"CtrlTwoMixed", qasm::ctrlTwoMixed, + MQT_NAMED_BUILDER(mlir::qc::ctrlTwoMixed)}, + QASM3TranslationTestCase{"SimpleIf", qasm::simpleIf, + MQT_NAMED_BUILDER(mlir::qc::simpleIf)}, + QASM3TranslationTestCase{"IfTwoQubits", qasm::ifTwoQubits, + MQT_NAMED_BUILDER(mlir::qc::ifTwoQubits)}, + QASM3TranslationTestCase{"IfElse", qasm::ifElse, + MQT_NAMED_BUILDER(mlir::qc::ifElse)})); diff --git a/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp b/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp index 0e5d53783b..b47c9f97a7 100644 --- a/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp +++ b/mlir/unittests/Dialect/QC/Translation/test_quantum_computation_translation.cpp @@ -418,9 +418,18 @@ INSTANTIATE_TEST_SUITE_P( "BarrierMultipleQubits", MQT_NAMED_BUILDER(qc::barrierMultipleQubits), MQT_NAMED_BUILDER(mlir::qc::barrierMultipleQubits)}, + QuantumComputationTranslationTestCase{ + "CtrlTwo", MQT_NAMED_BUILDER(qc::ctrlTwo), + MQT_NAMED_BUILDER(mlir::qc::ctrlTwo)}, + QuantumComputationTranslationTestCase{ + "CtrlTwoMixed", MQT_NAMED_BUILDER(qc::ctrlTwoMixed), + MQT_NAMED_BUILDER(mlir::qc::ctrlTwoMixed)}, QuantumComputationTranslationTestCase{ "SimpleIf", MQT_NAMED_BUILDER(qc::simpleIf), MQT_NAMED_BUILDER(mlir::qc::simpleIf)}, + QuantumComputationTranslationTestCase{ + "IfTwoQubits", MQT_NAMED_BUILDER(qc::ifTwoQubits), + MQT_NAMED_BUILDER(mlir::qc::ifTwoQubits)}, QuantumComputationTranslationTestCase{ "IfElse", MQT_NAMED_BUILDER(qc::ifElse), MQT_NAMED_BUILDER(mlir::qc::ifElse)})); diff --git a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp index 413f29336d..883c7d32d2 100644 --- a/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp +++ b/mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp @@ -221,33 +221,40 @@ INSTANTIATE_TEST_SUITE_P( /// @{ INSTANTIATE_TEST_SUITE_P( QCOCtrlOpTest, QCOTest, - testing::Values(QCOTestCase{"TrivialCtrl", MQT_NAMED_BUILDER(trivialCtrl), - MQT_NAMED_BUILDER(rxx)}, - QCOTestCase{"NestedCtrl", MQT_NAMED_BUILDER(nestedCtrl), - MQT_NAMED_BUILDER(multipleControlledRxx)}, - QCOTestCase{"TripleNestedCtrl", - MQT_NAMED_BUILDER(tripleNestedCtrl), - MQT_NAMED_BUILDER(tripleControlledRxx)}, - QCOTestCase{"CtrlInvSandwich", - MQT_NAMED_BUILDER(ctrlInvSandwich), - MQT_NAMED_BUILDER(multipleControlledRxx)}, - QCOTestCase{"DoubleNestedCtrlTwoQubits", - MQT_NAMED_BUILDER(doubleNestedCtrlTwoQubits), - MQT_NAMED_BUILDER(fourControlledRxx)})); + testing::Values( + QCOTestCase{"TrivialCtrl", MQT_NAMED_BUILDER(trivialCtrl), + MQT_NAMED_BUILDER(rxx)}, + QCOTestCase{"EmptyCtrl", MQT_NAMED_BUILDER(emptyCtrl), + MQT_NAMED_BUILDER(rxx)}, + QCOTestCase{"NestedCtrl", MQT_NAMED_BUILDER(nestedCtrl), + MQT_NAMED_BUILDER(multipleControlledRxx)}, + QCOTestCase{"TripleNestedCtrl", MQT_NAMED_BUILDER(tripleNestedCtrl), + MQT_NAMED_BUILDER(tripleControlledRxx)}, + QCOTestCase{"CtrlInvSandwich", MQT_NAMED_BUILDER(ctrlInvSandwich), + MQT_NAMED_BUILDER(multipleControlledRxx)}, + QCOTestCase{"DoubleNestedCtrlTwoQubits", + MQT_NAMED_BUILDER(doubleNestedCtrlTwoQubits), + MQT_NAMED_BUILDER(fourControlledRxx)}, + QCOTestCase{"NestedCtrlTwo", MQT_NAMED_BUILDER(nestedCtrlTwo), + MQT_NAMED_BUILDER(ctrlTwo)})); /// @} /// \name QCO/Modifiers/InvOp.cpp /// @{ INSTANTIATE_TEST_SUITE_P( QCOInvOpTest, QCOTest, - testing::Values(QCOTestCase{"NestedInv", MQT_NAMED_BUILDER(nestedInv), + testing::Values(QCOTestCase{"EmptyInv", MQT_NAMED_BUILDER(emptyInv), + MQT_NAMED_BUILDER(rxx)}, + QCOTestCase{"NestedInv", MQT_NAMED_BUILDER(nestedInv), MQT_NAMED_BUILDER(rxx)}, QCOTestCase{"TripleNestedInv", MQT_NAMED_BUILDER(tripleNestedInv), MQT_NAMED_BUILDER(rxx)}, QCOTestCase{"InvControlSandwich", MQT_NAMED_BUILDER(invCtrlSandwich), - MQT_NAMED_BUILDER(singleControlledRxx)})); + MQT_NAMED_BUILDER(singleControlledRxx)}, + QCOTestCase{"InvCtrlTwo", MQT_NAMED_BUILDER(invCtrlTwo), + MQT_NAMED_BUILDER(ctrlInvTwo)})); /// @} /// \name QCO/Operations/StandardGates/BarrierOp.cpp @@ -959,6 +966,10 @@ INSTANTIATE_TEST_SUITE_P( MQT_NAMED_BUILDER(inverseMultipleControlledX), MQT_NAMED_BUILDER(multipleControlledX)}, QCOTestCase{"TwoX", MQT_NAMED_BUILDER(twoX), + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"ControlledTwoX", MQT_NAMED_BUILDER(controlledTwoX), + MQT_NAMED_BUILDER(emptyQCO)}, + QCOTestCase{"InverseTwoX", MQT_NAMED_BUILDER(inverseTwoX), MQT_NAMED_BUILDER(emptyQCO)})); /// @} diff --git a/mlir/unittests/programs/CMakeLists.txt b/mlir/unittests/programs/CMakeLists.txt index 8b58449e76..6bd97fef77 100644 --- a/mlir/unittests/programs/CMakeLists.txt +++ b/mlir/unittests/programs/CMakeLists.txt @@ -6,6 +6,10 @@ # # Licensed under the MIT License +add_library(MLIRQASMPrograms qasm_programs.cpp) +target_sources(MLIRQASMPrograms PUBLIC FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} FILES + qasm_programs.h) + add_library(MLIRQCPrograms qc_programs.cpp) target_link_libraries(MLIRQCPrograms PUBLIC MLIRQCProgramBuilder) target_sources(MLIRQCPrograms PUBLIC FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} FILES diff --git a/mlir/unittests/programs/qasm_programs.cpp b/mlir/unittests/programs/qasm_programs.cpp new file mode 100644 index 0000000000..a0fae54429 --- /dev/null +++ b/mlir/unittests/programs/qasm_programs.cpp @@ -0,0 +1,699 @@ +/* + * 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 "qasm_programs.h" + +#include + +// NOLINTBEGIN(readability-identifier-naming) +namespace mlir::qasm { + +const std::string allocQubit = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit q; +)qasm"; + +const std::string allocQubitRegister = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +)qasm"; + +const std::string allocMultipleQubitRegisters = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q0; +qubit[3] q1; +)qasm"; + +const std::string allocLargeRegister = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[100] q; +)qasm"; + +const std::string singleMeasurementToSingleBit = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +bit[1] c; +measure q[0] -> c[0]; +)qasm"; + +const std::string repeatedMeasurementToSameBit = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +bit[1] c; +measure q[0] -> c[0]; +measure q[0] -> c[0]; +measure q[0] -> c[0]; +)qasm"; + +const std::string repeatedMeasurementToDifferentBits = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +bit[3] c; +measure q[0] -> c[0]; +measure q[0] -> c[1]; +measure q[0] -> c[2]; +)qasm"; + +const std::string multipleClassicalRegistersAndMeasurements = + R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +bit[1] c0; +bit[2] c1; +measure q[0] -> c0[0]; +measure q[1] -> c1[0]; +measure q[2] -> c1[1]; +)qasm"; + +const std::string resetQubitAfterSingleOp = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +h q[0]; +reset q[0]; +)qasm"; + +const std::string resetMultipleQubitsAfterSingleOp = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +h q[0]; +reset q[0]; +h q[1]; +reset q[1]; +)qasm"; + +const std::string repeatedResetAfterSingleOp = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +h q[0]; +reset q[0]; +reset q[0]; +reset q[0]; +)qasm"; + +const std::string globalPhase = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +gphase(0.123); +)qasm"; + +const std::string identity = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +id q[0]; +)qasm"; + +const std::string singleControlledIdentity = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ id q[0], q[1]; +)qasm"; + +const std::string multipleControlledIdentity = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ id q[0], q[1], q[2]; +)qasm"; + +const std::string x = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +x q[0]; +)qasm"; + +const std::string singleControlledX = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ x q[0], q[1]; +)qasm"; + +const std::string multipleControlledX = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ x q[0], q[1], q[2]; +)qasm"; + +const std::string y = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +y q[0]; +)qasm"; + +const std::string singleControlledY = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ y q[0], q[1]; +)qasm"; + +const std::string multipleControlledY = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ y q[0], q[1], q[2]; +)qasm"; + +const std::string z = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +z q[0]; +)qasm"; + +const std::string singleControlledZ = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ z q[0], q[1]; +)qasm"; + +const std::string multipleControlledZ = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ z q[0], q[1], q[2]; +)qasm"; + +const std::string h = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +h q[0]; +)qasm"; + +const std::string singleControlledH = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ h q[0], q[1]; +)qasm"; + +const std::string multipleControlledH = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ h q[0], q[1], q[2]; +)qasm"; + +const std::string s = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +s q[0]; +)qasm"; + +const std::string singleControlledS = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ s q[0], q[1]; +)qasm"; + +const std::string multipleControlledS = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ s q[0], q[1], q[2]; +)qasm"; + +const std::string sdg = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +sdg q[0]; +)qasm"; + +const std::string singleControlledSdg = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ sdg q[0], q[1]; +)qasm"; + +const std::string multipleControlledSdg = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ sdg q[0], q[1], q[2]; +)qasm"; + +const std::string t_ = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +t q[0]; +)qasm"; + +const std::string singleControlledT = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ t q[0], q[1]; +)qasm"; + +const std::string multipleControlledT = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ t q[0], q[1], q[2]; +)qasm"; + +const std::string tdg = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +tdg q[0]; +)qasm"; + +const std::string singleControlledTdg = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ tdg q[0], q[1]; +)qasm"; + +const std::string multipleControlledTdg = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ tdg q[0], q[1], q[2]; +)qasm"; + +const std::string sx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +sx q[0]; +)qasm"; + +const std::string singleControlledSx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ sx q[0], q[1]; +)qasm"; + +const std::string multipleControlledSx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ sx q[0], q[1], q[2]; +)qasm"; + +const std::string sxdg = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +sxdg q[0]; +)qasm"; + +const std::string singleControlledSxdg = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ sxdg q[0], q[1]; +)qasm"; + +const std::string multipleControlledSxdg = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ sxdg q[0], q[1], q[2]; +)qasm"; + +const std::string rx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +rx(0.123) q[0]; +)qasm"; + +const std::string singleControlledRx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ rx(0.123) q[0], q[1]; +)qasm"; + +const std::string multipleControlledRx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ rx(0.123) q[0], q[1], q[2]; +)qasm"; + +const std::string ry = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +ry(0.456) q[0]; +)qasm"; + +const std::string singleControlledRy = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ ry(0.456) q[0], q[1]; +)qasm"; + +const std::string multipleControlledRy = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ ry(0.456) q[0], q[1], q[2]; +)qasm"; + +const std::string rz = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +rz(0.789) q[0]; +)qasm"; + +const std::string singleControlledRz = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ rz(0.789) q[0], q[1]; +)qasm"; + +const std::string multipleControlledRz = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ rz(0.789) q[0], q[1], q[2]; +)qasm"; + +const std::string p = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +p(0.123) q[0]; +)qasm"; + +const std::string singleControlledP = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ p(0.123) q[0], q[1]; +)qasm"; + +const std::string multipleControlledP = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ p(0.123) q[0], q[1], q[2]; +)qasm"; + +const std::string r = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +r(0.123, 0.456) q[0]; +)qasm"; + +const std::string singleControlledR = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ r(0.123, 0.456) q[0], q[1]; +)qasm"; + +const std::string multipleControlledR = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ r(0.123, 0.456) q[0], q[1], q[2]; +)qasm"; + +const std::string u2 = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +u2(0.234, 0.567) q[0]; +)qasm"; + +const std::string singleControlledU2 = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ u2(0.234, 0.567) q[0], q[1]; +)qasm"; + +const std::string multipleControlledU2 = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ u2(0.234, 0.567) q[0], q[1], q[2]; +)qasm"; + +const std::string u = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +u(0.1, 0.2, 0.3) q[0]; +)qasm"; + +const std::string singleControlledU = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ctrl @ u(0.1, 0.2, 0.3) q[0], q[1]; +)qasm"; + +const std::string multipleControlledU = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl(2) @ u(0.1, 0.2, 0.3) q[0], q[1], q[2]; +)qasm"; + +const std::string swap = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +swap q[0], q[1]; +)qasm"; + +const std::string singleControlledSwap = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ swap q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledSwap = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ swap q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string iswap = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +iswap q[0], q[1]; +)qasm"; + +const std::string singleControlledIswap = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ iswap q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledIswap = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ iswap q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string inverseIswap = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +inv @ iswap q[0], q[1]; +)qasm"; + +const std::string inverseMultipleControlledIswap = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +inv @ ctrl(2) @ iswap q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string dcx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +dcx q[0], q[1]; +)qasm"; + +const std::string singleControlledDcx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ dcx q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledDcx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ dcx q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string ecr = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ecr q[0], q[1]; +)qasm"; + +const std::string singleControlledEcr = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ ecr q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledEcr = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ ecr q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string rxx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +rxx(0.123) q[0], q[1]; +)qasm"; + +const std::string singleControlledRxx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ rxx(0.123) q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledRxx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ rxx(0.123) q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string tripleControlledRxx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[5] q; +ctrl(3) @ rxx(0.123) q[0], q[1], q[2], q[3], q[4]; +)qasm"; + +const std::string ryy = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +ryy(0.123) q[0], q[1]; +)qasm"; + +const std::string singleControlledRyy = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ ryy(0.123) q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledRyy = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ ryy(0.123) q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string rzx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +rzx(0.123) q[0], q[1]; +)qasm"; + +const std::string singleControlledRzx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ rzx(0.123) q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledRzx = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ rzx(0.123) q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string rzz = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +rzz(0.123) q[0], q[1]; +)qasm"; + +const std::string singleControlledRzz = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ rzz(0.123) q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledRzz = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ rzz(0.123) q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string xxPlusYY = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +xx_plus_yy(0.123, 0.456) q[0], q[1]; +)qasm"; + +const std::string singleControlledXxPlusYY = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ xx_plus_yy(0.123, 0.456) q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledXxPlusYY = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ xx_plus_yy(0.123, 0.456) q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string xxMinusYY = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +xx_minus_yy(0.123, 0.456) q[0], q[1]; +)qasm"; + +const std::string singleControlledXxMinusYY = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +ctrl @ xx_minus_yy(0.123, 0.456) q[0], q[1], q[2]; +)qasm"; + +const std::string multipleControlledXxMinusYY = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +ctrl(2) @ xx_minus_yy(0.123, 0.456) q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string barrier = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +barrier q[0]; +)qasm"; + +const std::string barrierTwoQubits = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +barrier q[0], q[1]; +)qasm"; + +const std::string barrierMultipleQubits = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +barrier q[0], q[1], q[2]; +)qasm"; + +const std::string ctrlTwo = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +gate compound q0, q1 { + x q0; + rxx(0.123) q0, q1; +} +ctrl(2) @ compound q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string ctrlTwoMixed = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[4] q; +gate compound q0, q1 { + ctrl @ x q0, q1; + rxx(0.123) q0, q1; +} +ctrl(2) @ compound q[0], q[1], q[2], q[3]; +)qasm"; + +const std::string simpleIf = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +h q[0]; +bit c = measure q[0]; +if (c) { + x q[0]; +} +)qasm"; + +const std::string ifTwoQubits = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +h q[0]; +bit c = measure q[0]; +if (c) { + x q[0]; + x q[1]; +} +)qasm"; + +const std::string ifElse = R"qasm(OPENQASM 3.0; +include "stdgates.inc"; +qubit[1] q; +h q[0]; +bit c = measure q[0]; +if (c) { + x q[0]; +} else { + z q[0]; +} +)qasm"; + +} // namespace mlir::qasm +// NOLINTEND(readability-identifier-naming) diff --git a/mlir/unittests/programs/qasm_programs.h b/mlir/unittests/programs/qasm_programs.h new file mode 100644 index 0000000000..4e33a08645 --- /dev/null +++ b/mlir/unittests/programs/qasm_programs.h @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include + +// NOLINTBEGIN(readability-identifier-naming) +namespace mlir::qasm { + +/// Allocates a single qubit. +extern const std::string allocQubit; + +/// Allocates a qubit register of size `2`. +extern const std::string allocQubitRegister; + +/// Allocates two qubit registers of size `2` and `3`. +extern const std::string allocMultipleQubitRegisters; + +/// Allocates a large qubit register. +extern const std::string allocLargeRegister; + +/// Measures a single qubit into a single classical bit. +extern const std::string singleMeasurementToSingleBit; + +/// Repeatedly measures a single qubit into the same classical bit. +extern const std::string repeatedMeasurementToSameBit; + +/// Repeatedly measures a single qubit into different classical bits. +extern const std::string repeatedMeasurementToDifferentBits; + +/// Measures multiple qubits into multiple classical bits. +extern const std::string multipleClassicalRegistersAndMeasurements; + +/// Resets a single qubit after a single operation. +extern const std::string resetQubitAfterSingleOp; + +/// Resets multiple qubits after a single operation. +extern const std::string resetMultipleQubitsAfterSingleOp; + +/// Repeatedly resets a single qubit after a single operation. +extern const std::string repeatedResetAfterSingleOp; + +/// Creates a circuit with just a global phase. +extern const std::string globalPhase; + +/// Creates a circuit with just an identity gate. +extern const std::string identity; + +/// Creates a controlled identity gate with a single control qubit. +extern const std::string singleControlledIdentity; + +/// Creates a multi-controlled identity gate with multiple control qubits. +extern const std::string multipleControlledIdentity; + +/// Creates a circuit with just an X gate. +extern const std::string x; + +/// Creates a circuit with a single controlled X gate. +extern const std::string singleControlledX; + +/// Creates a circuit with a multi-controlled X gate. +extern const std::string multipleControlledX; + +/// Creates a circuit with just a Y gate. +extern const std::string y; + +/// Creates a circuit with a single controlled Y gate. +extern const std::string singleControlledY; + +/// Creates a circuit with a multi-controlled Y gate. +extern const std::string multipleControlledY; + +/// Creates a circuit with just a Z gate. +extern const std::string z; + +/// Creates a circuit with a single controlled Z gate. +extern const std::string singleControlledZ; + +/// Creates a circuit with a multi-controlled Z gate. +extern const std::string multipleControlledZ; + +/// Creates a circuit with just an H gate. +extern const std::string h; + +/// Creates a circuit with a single controlled H gate. +extern const std::string singleControlledH; + +/// Creates a circuit with a multi-controlled H gate. +extern const std::string multipleControlledH; + +/// Creates a circuit with just an S gate. +extern const std::string s; + +/// Creates a circuit with a single controlled S gate. +extern const std::string singleControlledS; + +/// Creates a circuit with a multi-controlled S gate. +extern const std::string multipleControlledS; + +/// Creates a circuit with just an Sdg gate. +extern const std::string sdg; + +/// Creates a circuit with a single controlled Sdg gate. +extern const std::string singleControlledSdg; + +/// Creates a circuit with a multi-controlled Sdg gate. +extern const std::string multipleControlledSdg; + +/// Creates a circuit with just a T gate. +extern const std::string t_; + +/// Creates a circuit with a single controlled T gate. +extern const std::string singleControlledT; + +/// Creates a circuit with a multi-controlled T gate. +extern const std::string multipleControlledT; + +/// Creates a circuit with just a Tdg gate. +extern const std::string tdg; + +/// Creates a circuit with a single controlled Tdg gate. +extern const std::string singleControlledTdg; + +/// Creates a circuit with a multi-controlled Tdg gate. +extern const std::string multipleControlledTdg; + +/// Creates a circuit with just an SX gate. +extern const std::string sx; + +/// Creates a circuit with a single controlled SX gate. +extern const std::string singleControlledSx; + +/// Creates a circuit with a multi-controlled SX gate. +extern const std::string multipleControlledSx; + +/// Creates a circuit with just an SXdg gate. +extern const std::string sxdg; + +/// Creates a circuit with a single controlled SXdg gate. +extern const std::string singleControlledSxdg; + +/// Creates a circuit with a multi-controlled SXdg gate. +extern const std::string multipleControlledSxdg; + +/// Creates a circuit with just an RX gate. +extern const std::string rx; + +/// Creates a circuit with a single controlled RX gate. +extern const std::string singleControlledRx; + +/// Creates a circuit with a multi-controlled RX gate. +extern const std::string multipleControlledRx; + +/// Creates a circuit with just an RY gate. +extern const std::string ry; + +/// Creates a circuit with a single controlled RY gate. +extern const std::string singleControlledRy; + +/// Creates a circuit with a multi-controlled RY gate. +extern const std::string multipleControlledRy; + +/// Creates a circuit with just an RZ gate. +extern const std::string rz; + +/// Creates a circuit with a single controlled RZ gate. +extern const std::string singleControlledRz; + +/// Creates a circuit with a multi-controlled RZ gate. +extern const std::string multipleControlledRz; + +/// Creates a circuit with just a P gate. +extern const std::string p; + +/// Creates a circuit with a single controlled P gate. +extern const std::string singleControlledP; + +/// Creates a circuit with a multi-controlled P gate. +extern const std::string multipleControlledP; + +/// Creates a circuit with just an R gate. +extern const std::string r; + +/// Creates a circuit with a single controlled R gate. +extern const std::string singleControlledR; + +/// Creates a circuit with a multi-controlled R gate. +extern const std::string multipleControlledR; + +/// Creates a circuit with just a U2 gate. +extern const std::string u2; + +/// Creates a circuit with a single controlled U2 gate. +extern const std::string singleControlledU2; + +/// Creates a circuit with a multi-controlled U2 gate. +extern const std::string multipleControlledU2; + +/// Creates a circuit with just a U gate. +extern const std::string u; + +/// Creates a circuit with a single controlled U gate. +extern const std::string singleControlledU; + +/// Creates a circuit with a multi-controlled U gate. +extern const std::string multipleControlledU; + +/// Creates a circuit with just a SWAP gate. +extern const std::string swap; + +/// Creates a circuit with a single controlled SWAP gate. +extern const std::string singleControlledSwap; + +/// Creates a circuit with a multi-controlled SWAP gate. +extern const std::string multipleControlledSwap; + +/// Creates a circuit with just an iSWAP gate. +extern const std::string iswap; + +/// Creates a circuit with a single controlled iSWAP gate. +extern const std::string singleControlledIswap; + +/// Creates a circuit with a multi-controlled iSWAP gate. +extern const std::string multipleControlledIswap; + +/// Creates a circuit with an inverse modifier applied to an iSWAP gate. +extern const std::string inverseIswap; + +/// Creates a circuit with an inverse modifier applied to a controlled iSWAP +/// gate. +extern const std::string inverseMultipleControlledIswap; + +/// Creates a circuit with just a DCX gate. +extern const std::string dcx; + +/// Creates a circuit with a single controlled DCX gate. +extern const std::string singleControlledDcx; + +/// Creates a circuit with a multi-controlled DCX gate. +extern const std::string multipleControlledDcx; + +/// Creates a circuit with just an ECR gate. +extern const std::string ecr; + +/// Creates a circuit with a single controlled ECR gate. +extern const std::string singleControlledEcr; + +/// Creates a circuit with a multi-controlled ECR gate. +extern const std::string multipleControlledEcr; + +/// Creates a circuit with just an RXX gate. +extern const std::string rxx; + +/// Creates a circuit with a single controlled RXX gate. +extern const std::string singleControlledRxx; + +/// Creates a circuit with a multi-controlled RXX gate. +extern const std::string multipleControlledRxx; + +/// Creates a circuit with a triple-controlled RXX gate. +extern const std::string tripleControlledRxx; + +/// Creates a circuit with just an RYY gate. +extern const std::string ryy; + +/// Creates a circuit with a single controlled RYY gate. +extern const std::string singleControlledRyy; + +/// Creates a circuit with a multi-controlled RYY gate. +extern const std::string multipleControlledRyy; + +/// Creates a circuit with just an RZX gate. +extern const std::string rzx; + +/// Creates a circuit with a single controlled RZX gate. +extern const std::string singleControlledRzx; + +/// Creates a circuit with a multi-controlled RZX gate. +extern const std::string multipleControlledRzx; + +/// Creates a circuit with just an RZZ gate. +extern const std::string rzz; + +/// Creates a circuit with a single controlled RZZ gate. +extern const std::string singleControlledRzz; + +/// Creates a circuit with a multi-controlled RZZ gate. +extern const std::string multipleControlledRzz; + +/// Creates a circuit with just an XXPlusYY gate. +extern const std::string xxPlusYY; + +/// Creates a circuit with a single controlled XXPlusYY gate. +extern const std::string singleControlledXxPlusYY; + +/// Creates a circuit with a multi-controlled XXPlusYY gate. +extern const std::string multipleControlledXxPlusYY; + +/// Creates a circuit with just an XXMinusYY gate. +extern const std::string xxMinusYY; + +/// Creates a circuit with a single controlled XXMinusYY gate. +extern const std::string singleControlledXxMinusYY; + +/// Creates a circuit with a multi-controlled XXMinusYY gate. +extern const std::string multipleControlledXxMinusYY; + +/// Creates a circuit with a barrier. +extern const std::string barrier; + +/// Creates a circuit with a barrier on two qubits. +extern const std::string barrierTwoQubits; + +/// Creates a circuit with a barrier on multiple qubits. +extern const std::string barrierMultipleQubits; + +/// Creates a circuit with a control modifier applied to two gates. +extern const std::string ctrlTwo; + +/// Creates a circuit with a control modifier applied to a controlled and a +/// non-controlled gate. +extern const std::string ctrlTwoMixed; + +/// Creates a circuit with a simple if operation with one qubit. +extern const std::string simpleIf; + +/// Creates a circuit with an if operation with two qubits. +extern const std::string ifTwoQubits; + +/// Creates a circuit with an if operation with an else branch. +extern const std::string ifElse; + +} // namespace mlir::qasm +// NOLINTEND(readability-identifier-naming) diff --git a/mlir/unittests/programs/qc_programs.cpp b/mlir/unittests/programs/qc_programs.cpp index 373452252a..232b8cbdee 100644 --- a/mlir/unittests/programs/qc_programs.cpp +++ b/mlir/unittests/programs/qc_programs.cpp @@ -62,7 +62,7 @@ void staticQubitsWithCtrl(QCProgramBuilder& b) { void staticQubitsWithInv(QCProgramBuilder& b) { auto q0 = b.staticQubit(0); - b.inv([&]() { b.t(q0); }); + b.inv({q0}, [&](ValueRange qubits) { b.t(qubits[0]); }); } void staticQubitsWithDuplicates(QCProgramBuilder& b) { @@ -75,7 +75,7 @@ void staticQubitsWithDuplicates(QCProgramBuilder& b) { b.p(std::numbers::pi / 2., q1a); b.rzz(0.123, q0b, q1b); b.cx(q0b, q1b); - b.inv([&]() { b.t(q0a); }); + b.inv({q0a}, [&](ValueRange qubits) { b.t(qubits[0]); }); } void staticQubitsCanonical(QCProgramBuilder& b) { @@ -86,7 +86,7 @@ void staticQubitsCanonical(QCProgramBuilder& b) { b.p(std::numbers::pi / 2., q1); b.rzz(0.123, q0, q1); b.cx(q0, q1); - b.inv([&]() { b.t(q0); }); + b.inv({q0}, [&](ValueRange qubits) { b.t(qubits[0]); }); } void allocDeallocPair(QCProgramBuilder& b) { @@ -194,7 +194,8 @@ void multipleControlledGlobalPhase(QCProgramBuilder& b) { void nestedControlledGlobalPhase(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.ctrl(q[0], [&] { b.cgphase(0.123, q[1]); }); + b.ctrl(q[0], {q[1]}, + [&](ValueRange targets) { b.cgphase(0.123, targets[0]); }); } void trivialControlledGlobalPhase(QCProgramBuilder& b) { @@ -203,12 +204,13 @@ void trivialControlledGlobalPhase(QCProgramBuilder& b) { } void inverseGlobalPhase(QCProgramBuilder& b) { - b.inv([&]() { b.gphase(-0.123); }); + b.inv({}, [&](ValueRange /*qubits*/) { b.gphase(-0.123); }); } void inverseMultipleControlledGlobalPhase(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcgphase(-0.123, {q[0], q[1], q[2]}); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mcgphase(-0.123, qubits); }); } void identity(QCProgramBuilder& b) { @@ -228,7 +230,8 @@ void multipleControlledIdentity(QCProgramBuilder& b) { void nestedControlledIdentity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.ctrl(q[2], [&] { b.cid(q[1], q[0]); }); + b.ctrl(q[2], {q[0], q[1]}, + [&](ValueRange targets) { b.cid(targets[1], targets[0]); }); } void trivialControlledIdentity(QCProgramBuilder& b) { @@ -238,12 +241,13 @@ void trivialControlledIdentity(QCProgramBuilder& b) { void inverseIdentity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.id(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.id(qubits[0]); }); } void inverseMultipleControlledIdentity(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcid({q[2], q[1]}, q[0]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mcid({qubits[0], qubits[1]}, qubits[2]); }); } void x(QCProgramBuilder& b) { @@ -263,7 +267,8 @@ void multipleControlledX(QCProgramBuilder& b) { void nestedControlledX(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.cx(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.cx(targets[0], targets[1]); }); } void trivialControlledX(QCProgramBuilder& b) { @@ -282,12 +287,13 @@ void repeatedControlledX(QCProgramBuilder& b) { void inverseX(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.x(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.x(qubits[0]); }); } void inverseMultipleControlledX(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcx({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mcx({qubits[0], qubits[1]}, qubits[2]); }); } void y(QCProgramBuilder& b) { @@ -307,7 +313,8 @@ void multipleControlledY(QCProgramBuilder& b) { void nestedControlledY(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.cy(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.cy(targets[0], targets[1]); }); } void trivialControlledY(QCProgramBuilder& b) { @@ -317,12 +324,13 @@ void trivialControlledY(QCProgramBuilder& b) { void inverseY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.y(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.y(qubits[0]); }); } void inverseMultipleControlledY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcy({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mcy({qubits[0], qubits[1]}, qubits[2]); }); } void z(QCProgramBuilder& b) { @@ -342,7 +350,8 @@ void multipleControlledZ(QCProgramBuilder& b) { void nestedControlledZ(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.cz(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.cz(targets[0], targets[1]); }); } void trivialControlledZ(QCProgramBuilder& b) { @@ -352,12 +361,13 @@ void trivialControlledZ(QCProgramBuilder& b) { void inverseZ(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.z(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.z(qubits[0]); }); } void inverseMultipleControlledZ(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcz({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mcz({qubits[0], qubits[1]}, qubits[2]); }); } void h(QCProgramBuilder& b) { @@ -377,7 +387,8 @@ void multipleControlledH(QCProgramBuilder& b) { void nestedControlledH(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.ch(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.ch(targets[0], targets[1]); }); } void trivialControlledH(QCProgramBuilder& b) { @@ -387,12 +398,13 @@ void trivialControlledH(QCProgramBuilder& b) { void inverseH(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.h(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.h(qubits[0]); }); } void inverseMultipleControlledH(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mch({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mch({qubits[0], qubits[1]}, qubits[2]); }); } void hWithoutRegister(QCProgramBuilder& b) { @@ -417,7 +429,8 @@ void multipleControlledS(QCProgramBuilder& b) { void nestedControlledS(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.cs(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.cs(targets[0], targets[1]); }); } void trivialControlledS(QCProgramBuilder& b) { @@ -427,12 +440,13 @@ void trivialControlledS(QCProgramBuilder& b) { void inverseS(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.s(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.s(qubits[0]); }); } void inverseMultipleControlledS(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcs({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mcs({qubits[0], qubits[1]}, qubits[2]); }); } void sdg(QCProgramBuilder& b) { @@ -452,7 +466,8 @@ void multipleControlledSdg(QCProgramBuilder& b) { void nestedControlledSdg(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.csdg(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.csdg(targets[0], targets[1]); }); } void trivialControlledSdg(QCProgramBuilder& b) { @@ -462,12 +477,13 @@ void trivialControlledSdg(QCProgramBuilder& b) { void inverseSdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.sdg(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.sdg(qubits[0]); }); } void inverseMultipleControlledSdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcsdg({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mcsdg({qubits[0], qubits[1]}, qubits[2]); }); } void t_(QCProgramBuilder& b) { @@ -487,7 +503,8 @@ void multipleControlledT(QCProgramBuilder& b) { void nestedControlledT(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.ct(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.ct(targets[0], targets[1]); }); } void trivialControlledT(QCProgramBuilder& b) { @@ -497,12 +514,13 @@ void trivialControlledT(QCProgramBuilder& b) { void inverseT(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.t(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.t(qubits[0]); }); } void inverseMultipleControlledT(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mct({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mct({qubits[0], qubits[1]}, qubits[2]); }); } void tdg(QCProgramBuilder& b) { @@ -522,7 +540,8 @@ void multipleControlledTdg(QCProgramBuilder& b) { void nestedControlledTdg(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.ctdg(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.ctdg(targets[0], targets[1]); }); } void trivialControlledTdg(QCProgramBuilder& b) { @@ -532,12 +551,13 @@ void trivialControlledTdg(QCProgramBuilder& b) { void inverseTdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.tdg(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.tdg(qubits[0]); }); } void inverseMultipleControlledTdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mctdg({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mctdg({qubits[0], qubits[1]}, qubits[2]); }); } void sx(QCProgramBuilder& b) { @@ -557,7 +577,8 @@ void multipleControlledSx(QCProgramBuilder& b) { void nestedControlledSx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.csx(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.csx(targets[0], targets[1]); }); } void trivialControlledSx(QCProgramBuilder& b) { @@ -567,12 +588,13 @@ void trivialControlledSx(QCProgramBuilder& b) { void inverseSx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.sx(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.sx(qubits[0]); }); } void inverseMultipleControlledSx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcsx({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, + [&](ValueRange qubits) { b.mcsx({qubits[0], qubits[1]}, qubits[2]); }); } void sxdg(QCProgramBuilder& b) { @@ -592,7 +614,8 @@ void multipleControlledSxdg(QCProgramBuilder& b) { void nestedControlledSxdg(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.csxdg(reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.csxdg(targets[0], targets[1]); }); } void trivialControlledSxdg(QCProgramBuilder& b) { @@ -602,12 +625,14 @@ void trivialControlledSxdg(QCProgramBuilder& b) { void inverseSxdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.sxdg(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.sxdg(qubits[0]); }); } void inverseMultipleControlledSxdg(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcsxdg({q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.mcsxdg({qubits[0], qubits[1]}, qubits[2]); + }); } void rx(QCProgramBuilder& b) { @@ -627,7 +652,8 @@ void multipleControlledRx(QCProgramBuilder& b) { void nestedControlledRx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.crx(0.123, reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.crx(0.123, targets[0], targets[1]); }); } void trivialControlledRx(QCProgramBuilder& b) { @@ -637,12 +663,14 @@ void trivialControlledRx(QCProgramBuilder& b) { void inverseRx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.rx(-0.123, q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.rx(-0.123, qubits[0]); }); } void inverseMultipleControlledRx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcrx(-0.123, {q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.mcrx(-0.123, {qubits[0], qubits[1]}, qubits[2]); + }); } void ry(QCProgramBuilder& b) { @@ -662,7 +690,8 @@ void multipleControlledRy(QCProgramBuilder& b) { void nestedControlledRy(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.cry(0.456, reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.cry(0.456, targets[0], targets[1]); }); } void trivialControlledRy(QCProgramBuilder& b) { @@ -672,12 +701,14 @@ void trivialControlledRy(QCProgramBuilder& b) { void inverseRy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.ry(-0.456, q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.ry(-0.456, qubits[0]); }); } void inverseMultipleControlledRy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcry(-0.456, {q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.mcry(-0.456, {qubits[0], qubits[1]}, qubits[2]); + }); } void rz(QCProgramBuilder& b) { @@ -697,7 +728,8 @@ void multipleControlledRz(QCProgramBuilder& b) { void nestedControlledRz(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.crz(0.789, reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.crz(0.789, targets[0], targets[1]); }); } void trivialControlledRz(QCProgramBuilder& b) { @@ -707,12 +739,14 @@ void trivialControlledRz(QCProgramBuilder& b) { void inverseRz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.rz(-0.789, q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.rz(-0.789, qubits[0]); }); } void inverseMultipleControlledRz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcrz(-0.789, {q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.mcrz(-0.789, {qubits[0], qubits[1]}, qubits[2]); + }); } void p(QCProgramBuilder& b) { @@ -732,7 +766,8 @@ void multipleControlledP(QCProgramBuilder& b) { void nestedControlledP(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.cp(0.123, reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, + [&](ValueRange targets) { b.cp(0.123, targets[0], targets[1]); }); } void trivialControlledP(QCProgramBuilder& b) { @@ -742,12 +777,14 @@ void trivialControlledP(QCProgramBuilder& b) { void inverseP(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.p(-0.123, q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.p(-0.123, qubits[0]); }); } void inverseMultipleControlledP(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcp(-0.123, {q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.mcp(-0.123, {qubits[0], qubits[1]}, qubits[2]); + }); } void r(QCProgramBuilder& b) { @@ -767,7 +804,9 @@ void multipleControlledR(QCProgramBuilder& b) { void nestedControlledR(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.cr(0.123, 0.456, reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { + b.cr(0.123, 0.456, targets[0], targets[1]); + }); } void trivialControlledR(QCProgramBuilder& b) { @@ -777,12 +816,14 @@ void trivialControlledR(QCProgramBuilder& b) { void inverseR(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.r(-0.123, 0.456, q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.r(-0.123, 0.456, qubits[0]); }); } void inverseMultipleControlledR(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcr(-0.123, 0.456, {q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.mcr(-0.123, 0.456, {qubits[0], qubits[1]}, qubits[2]); + }); } void u2(QCProgramBuilder& b) { @@ -802,7 +843,9 @@ void multipleControlledU2(QCProgramBuilder& b) { void nestedControlledU2(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.cu2(0.234, 0.567, reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { + b.cu2(0.234, 0.567, targets[0], targets[1]); + }); } void trivialControlledU2(QCProgramBuilder& b) { @@ -813,13 +856,16 @@ void trivialControlledU2(QCProgramBuilder& b) { void inverseU2(QCProgramBuilder& b) { constexpr double pi = std::numbers::pi; auto q = b.allocQubitRegister(1); - b.inv([&]() { b.u2(-0.567 + pi, -0.234 - pi, q[0]); }); + b.inv(q[0], + [&](ValueRange qubits) { b.u2(-0.567 + pi, -0.234 - pi, qubits[0]); }); } void inverseMultipleControlledU2(QCProgramBuilder& b) { constexpr double pi = std::numbers::pi; auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcu2(-0.567 + pi, -0.234 - pi, {q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.mcu2(-0.567 + pi, -0.234 - pi, {qubits[0], qubits[1]}, qubits[2]); + }); } void u(QCProgramBuilder& b) { @@ -839,7 +885,9 @@ void multipleControlledU(QCProgramBuilder& b) { void nestedControlledU(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); - b.ctrl(reg[0], [&] { b.cu(0.1, 0.2, 0.3, reg[1], reg[2]); }); + b.ctrl(reg[0], {reg[1], reg[2]}, [&](ValueRange targets) { + b.cu(0.1, 0.2, 0.3, targets[0], targets[1]); + }); } void trivialControlledU(QCProgramBuilder& b) { @@ -849,12 +897,14 @@ void trivialControlledU(QCProgramBuilder& b) { void inverseU(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.u(-0.1, -0.3, -0.2, q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.u(-0.1, -0.3, -0.2, qubits[0]); }); } void inverseMultipleControlledU(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { b.mcu(-0.1, -0.3, -0.2, {q[0], q[1]}, q[2]); }); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.mcu(-0.1, -0.3, -0.2, {qubits[0], qubits[1]}, qubits[2]); + }); } void swap(QCProgramBuilder& b) { @@ -874,7 +924,9 @@ void multipleControlledSwap(QCProgramBuilder& b) { void nestedControlledSwap(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.cswap(reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.cswap(targets[0], targets[1], targets[2]); + }); } void trivialControlledSwap(QCProgramBuilder& b) { @@ -884,12 +936,14 @@ void trivialControlledSwap(QCProgramBuilder& b) { void inverseSwap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.swap(q[0], q[1]); }); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.swap(qubits[0], qubits[1]); }); } void inverseMultipleControlledSwap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mcswap({q[0], q[1]}, q[2], q[3]); }); + b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + b.mcswap({qubits[0], qubits[1]}, qubits[2], qubits[3]); + }); } void iswap(QCProgramBuilder& b) { @@ -909,7 +963,9 @@ void multipleControlledIswap(QCProgramBuilder& b) { void nestedControlledIswap(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.ciswap(reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.ciswap(targets[0], targets[1], targets[2]); + }); } void trivialControlledIswap(QCProgramBuilder& b) { @@ -919,12 +975,15 @@ void trivialControlledIswap(QCProgramBuilder& b) { void inverseIswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.iswap(q[0], q[1]); }); + b.inv({q[0], q[1]}, + [&](ValueRange qubits) { b.iswap(qubits[0], qubits[1]); }); } void inverseMultipleControlledIswap(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mciswap({q[0], q[1]}, q[2], q[3]); }); + b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + b.mciswap({qubits[0], qubits[1]}, qubits[2], qubits[3]); + }); } void dcx(QCProgramBuilder& b) { @@ -944,7 +1003,9 @@ void multipleControlledDcx(QCProgramBuilder& b) { void nestedControlledDcx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.cdcx(reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.cdcx(targets[0], targets[1], targets[2]); + }); } void trivialControlledDcx(QCProgramBuilder& b) { @@ -954,12 +1015,14 @@ void trivialControlledDcx(QCProgramBuilder& b) { void inverseDcx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.dcx(q[1], q[0]); }); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.dcx(qubits[1], qubits[0]); }); } void inverseMultipleControlledDcx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mcdcx({q[0], q[1]}, q[3], q[2]); }); + b.inv({q[0], q[1], q[3], q[2]}, [&](ValueRange qubits) { + b.mcdcx({qubits[0], qubits[1]}, qubits[2], qubits[3]); + }); } void ecr(QCProgramBuilder& b) { @@ -979,7 +1042,9 @@ void multipleControlledEcr(QCProgramBuilder& b) { void nestedControlledEcr(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.cecr(reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.cecr(targets[0], targets[1], targets[2]); + }); } void trivialControlledEcr(QCProgramBuilder& b) { @@ -989,12 +1054,14 @@ void trivialControlledEcr(QCProgramBuilder& b) { void inverseEcr(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.ecr(q[0], q[1]); }); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { b.ecr(qubits[0], qubits[1]); }); } void inverseMultipleControlledEcr(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mcecr({q[0], q[1]}, q[2], q[3]); }); + b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + b.mcecr({qubits[0], qubits[1]}, qubits[2], qubits[3]); + }); } void rxx(QCProgramBuilder& b) { @@ -1014,7 +1081,9 @@ void multipleControlledRxx(QCProgramBuilder& b) { void nestedControlledRxx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.crxx(0.123, reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.crxx(0.123, targets[0], targets[1], targets[2]); + }); } void trivialControlledRxx(QCProgramBuilder& b) { @@ -1024,18 +1093,22 @@ void trivialControlledRxx(QCProgramBuilder& b) { void inverseRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.rxx(-0.123, q[0], q[1]); }); + b.inv({q[0], q[1]}, + [&](ValueRange qubits) { b.rxx(-0.123, qubits[0], qubits[1]); }); } void inverseMultipleControlledRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mcrxx(-0.123, {q[0], q[1]}, q[2], q[3]); }); + b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + b.mcrxx(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); + }); } void tripleControlledRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(5); b.mcrxx(0.123, {q[0], q[1], q[2]}, q[3], q[4]); } + void fourControlledRxx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(6); b.mcrxx(0.123, {q[0], q[1], q[2], q[3]}, q[4], q[5]); @@ -1058,7 +1131,9 @@ void multipleControlledRyy(QCProgramBuilder& b) { void nestedControlledRyy(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.cryy(0.123, reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.cryy(0.123, targets[0], targets[1], targets[2]); + }); } void trivialControlledRyy(QCProgramBuilder& b) { @@ -1068,12 +1143,15 @@ void trivialControlledRyy(QCProgramBuilder& b) { void inverseRyy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.ryy(-0.123, q[0], q[1]); }); + b.inv({q[0], q[1]}, + [&](ValueRange qubits) { b.ryy(-0.123, qubits[0], qubits[1]); }); } void inverseMultipleControlledRyy(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mcryy(-0.123, {q[0], q[1]}, q[2], q[3]); }); + b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + b.mcryy(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); + }); } void rzx(QCProgramBuilder& b) { @@ -1093,7 +1171,9 @@ void multipleControlledRzx(QCProgramBuilder& b) { void nestedControlledRzx(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.crzx(0.123, reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.crzx(0.123, targets[0], targets[1], targets[2]); + }); } void trivialControlledRzx(QCProgramBuilder& b) { @@ -1103,12 +1183,15 @@ void trivialControlledRzx(QCProgramBuilder& b) { void inverseRzx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.rzx(-0.123, q[0], q[1]); }); + b.inv({q[0], q[1]}, + [&](ValueRange qubits) { b.rzx(-0.123, qubits[0], qubits[1]); }); } void inverseMultipleControlledRzx(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mcrzx(-0.123, {q[0], q[1]}, q[2], q[3]); }); + b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + b.mcrzx(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); + }); } void rzz(QCProgramBuilder& b) { @@ -1128,7 +1211,9 @@ void multipleControlledRzz(QCProgramBuilder& b) { void nestedControlledRzz(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.crzz(0.123, reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.crzz(0.123, targets[0], targets[1], targets[2]); + }); } void trivialControlledRzz(QCProgramBuilder& b) { @@ -1138,12 +1223,15 @@ void trivialControlledRzz(QCProgramBuilder& b) { void inverseRzz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.rzz(-0.123, q[0], q[1]); }); + b.inv({q[0], q[1]}, + [&](ValueRange qubits) { b.rzz(-0.123, qubits[0], qubits[1]); }); } void inverseMultipleControlledRzz(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mcrzz(-0.123, {q[0], q[1]}, q[2], q[3]); }); + b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + b.mcrzz(-0.123, {qubits[0], qubits[1]}, qubits[2], qubits[3]); + }); } void xxPlusYY(QCProgramBuilder& b) { @@ -1163,7 +1251,9 @@ void multipleControlledXxPlusYY(QCProgramBuilder& b) { void nestedControlledXxPlusYY(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.cxx_plus_yy(0.123, 0.456, reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.cxx_plus_yy(0.123, 0.456, targets[0], targets[1], targets[2]); + }); } void trivialControlledXxPlusYY(QCProgramBuilder& b) { @@ -1173,12 +1263,16 @@ void trivialControlledXxPlusYY(QCProgramBuilder& b) { void inverseXxPlusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.xx_plus_yy(-0.123, 0.456, q[0], q[1]); }); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { + b.xx_plus_yy(-0.123, 0.456, qubits[0], qubits[1]); + }); } void inverseMultipleControlledXxPlusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mcxx_plus_yy(-0.123, 0.456, {q[0], q[1]}, q[2], q[3]); }); + b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + b.mcxx_plus_yy(-0.123, 0.456, {qubits[0], qubits[1]}, qubits[2], qubits[3]); + }); } void xxMinusYY(QCProgramBuilder& b) { @@ -1198,7 +1292,9 @@ void multipleControlledXxMinusYY(QCProgramBuilder& b) { void nestedControlledXxMinusYY(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(4); - b.ctrl(reg[0], [&] { b.cxx_minus_yy(0.123, 0.456, reg[1], reg[2], reg[3]); }); + b.ctrl(reg[0], {reg[1], reg[2], reg[3]}, [&](ValueRange targets) { + b.cxx_minus_yy(0.123, 0.456, targets[0], targets[1], targets[2]); + }); } void trivialControlledXxMinusYY(QCProgramBuilder& b) { @@ -1208,12 +1304,17 @@ void trivialControlledXxMinusYY(QCProgramBuilder& b) { void inverseXxMinusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.xx_minus_yy(-0.123, 0.456, q[0], q[1]); }); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { + b.xx_minus_yy(-0.123, 0.456, qubits[0], qubits[1]); + }); } void inverseMultipleControlledXxMinusYY(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.inv([&]() { b.mcxx_minus_yy(-0.123, 0.456, {q[0], q[1]}, q[2], q[3]); }); + b.inv({q[0], q[1], q[2], q[3]}, [&](ValueRange qubits) { + b.mcxx_minus_yy(-0.123, 0.456, {qubits[0], qubits[1]}, qubits[2], + qubits[3]); + }); } void barrier(QCProgramBuilder& b) { @@ -1233,74 +1334,165 @@ void barrierMultipleQubits(QCProgramBuilder& b) { void singleControlledBarrier(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ctrl(q[1], [&] { b.barrier(q[0]); }); + b.ctrl(q[1], q[0], [&](ValueRange targets) { b.barrier(targets[0]); }); } void inverseBarrier(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); - b.inv([&]() { b.barrier(q[0]); }); + b.inv(q[0], [&](ValueRange qubits) { b.barrier(qubits[0]); }); } void trivialCtrl(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.ctrl({}, [&]() { b.rxx(0.123, q[0], q[1]); }); + b.ctrl({}, {q[0], q[1]}, + [&](ValueRange targets) { b.rxx(0.123, targets[0], targets[1]); }); +} + +void emptyCtrl(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.rxx(0.123, q[0], q[1]); + b.ctrl({q[0]}, {q[1]}, [&](ValueRange /*targets*/) {}); } void nestedCtrl(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.ctrl(q[0], [&]() { b.ctrl(q[1], [&]() { b.rxx(0.123, q[2], q[3]); }); }); + b.ctrl(q[0], {q[1], q[2], q[3]}, [&](ValueRange targets) { + b.ctrl(targets[0], {targets[1], targets[2]}, [&](ValueRange innerTargets) { + b.rxx(0.123, innerTargets[0], innerTargets[1]); + }); + }); } void tripleNestedCtrl(QCProgramBuilder& b) { auto q = b.allocQubitRegister(5); - b.ctrl(q[0], [&]() { - b.ctrl(q[1], [&]() { b.ctrl(q[2], [&]() { b.rxx(0.123, q[3], q[4]); }); }); + b.ctrl(q[0], {q[1], q[2], q[3], q[4]}, [&](ValueRange targets) { + b.ctrl(targets[0], {targets[1], targets[2], targets[3]}, + [&](ValueRange innerTargets) { + b.ctrl(innerTargets[0], {innerTargets[1], innerTargets[2]}, + [&](ValueRange innerInnerTargets) { + b.rxx(0.123, innerInnerTargets[0], innerInnerTargets[1]); + }); + }); }); } void doubleNestedCtrlTwoQubits(QCProgramBuilder& b) { auto q = b.allocQubitRegister(6); - b.ctrl({q[0], q[1]}, - [&]() { b.ctrl({q[2], q[3]}, [&]() { b.rxx(0.123, q[4], q[5]); }); }); + b.ctrl({q[0], q[1]}, {q[2], q[3], q[4], q[5]}, [&](ValueRange targets) { + b.ctrl({targets[0], targets[1]}, {targets[2], targets[3]}, + [&](ValueRange innerTargets) { + b.rxx(0.123, innerTargets[0], innerTargets[1]); + }); + }); } void ctrlInvSandwich(QCProgramBuilder& b) { auto q = b.allocQubitRegister(4); - b.ctrl(q[0], [&]() { - b.inv([&]() { b.ctrl(q[1], [&]() { b.rxx(-0.123, q[2], q[3]); }); }); + b.ctrl(q[0], {q[1], q[2], q[3]}, [&](ValueRange targets) { + b.inv(targets, [&](ValueRange qubits) { + b.ctrl(qubits[0], {qubits[1], qubits[2]}, [&](ValueRange innerTargets) { + b.rxx(-0.123, innerTargets[0], innerTargets[1]); + }); + }); + }); +} + +void ctrlTwo(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(4); + b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { + b.x(targets[0]); + b.rxx(0.123, targets[0], targets[1]); }); } +void ctrlTwoMixed(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(4); + b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { + b.cx(targets[0], targets[1]); + b.rxx(0.123, targets[0], targets[1]); + }); +} + +void nestedCtrlTwo(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(4); + b.ctrl(q[0], {q[1], q[2], q[3]}, [&](ValueRange targets) { + b.ctrl(targets[0], {targets[1], targets[2]}, [&](ValueRange innerTargets) { + b.x(innerTargets[0]); + b.rxx(0.123, innerTargets[0], innerTargets[1]); + }); + }); +} + +void ctrlInvTwo(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + b.ctrl(q[0], {q[1], q[2]}, [&](ValueRange targets) { + b.inv(targets, [&](ValueRange qubits) { + b.x(qubits[0]); + b.rxx(0.123, qubits[0], qubits[1]); + }); + }); +} + +void emptyInv(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.rxx(0.123, q[0], q[1]); + b.inv({q[0], q[1]}, [&](ValueRange /*targets*/) {}); +} + void nestedInv(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv([&]() { b.inv([&]() { b.rxx(0.123, q[0], q[1]); }); }); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { + b.inv(qubits, [&](ValueRange innerQubits) { + b.rxx(0.123, innerQubits[0], innerQubits[1]); + }); + }); } void tripleNestedInv(QCProgramBuilder& b) { auto q = b.allocQubitRegister(2); - b.inv( - [&]() { b.inv([&]() { b.inv([&]() { b.rxx(-0.123, q[0], q[1]); }); }); }); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { + b.inv(qubits, [&](ValueRange innerQubits) { + b.inv(innerQubits, [&](ValueRange innerInnerQubits) { + b.rxx(-0.123, innerInnerQubits[0], innerInnerQubits[1]); + }); + }); + }); } void invCtrlSandwich(QCProgramBuilder& b) { auto q = b.allocQubitRegister(3); - b.inv([&]() { - b.ctrl(q[0], [&]() { b.inv([&]() { b.rxx(0.123, q[1], q[2]); }); }); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.ctrl(qubits[0], {qubits[1], qubits[2]}, [&](ValueRange targets) { + b.inv({targets[0], targets[1]}, [&](ValueRange innerQubits) { + b.rxx(0.123, innerQubits[0], innerQubits[1]); + }); + }); }); } -void simpleIf(QCProgramBuilder& b) { - auto q = b.allocQubitRegister(1); - b.h(q[0]); - auto cond = b.measure(q[0]); - b.scfIf(cond, [&] { b.x(q[0]); }); +void invTwo(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { + b.x(qubits[0]); + b.rxx(0.123, qubits[0], qubits[1]); + }); } -void ifElse(QCProgramBuilder& b) { +void invCtrlTwo(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + b.ctrl(qubits[0], {qubits[1], qubits[2]}, [&](ValueRange targets) { + b.x(targets[0]); + b.rxx(0.123, targets[0], targets[1]); + }); + }); +} + +void simpleIf(QCProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.h(q[0]); auto cond = b.measure(q[0]); - b.scfIf(cond, [&] { b.x(q[0]); }, [&] { b.z(q[0]); }); + b.scfIf(cond, [&] { b.x(q[0]); }); } void ifTwoQubits(QCProgramBuilder& b) { @@ -1313,6 +1505,13 @@ void ifTwoQubits(QCProgramBuilder& b) { }); } +void ifElse(QCProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.h(q[0]); + auto cond = b.measure(q[0]); + b.scfIf(cond, [&] { b.x(q[0]); }, [&] { b.z(q[0]); }); +} + void nestedIfOpForLoop(QCProgramBuilder& b) { auto reg = b.allocQubitRegister(3); auto q0 = b.allocQubit(); @@ -1395,7 +1594,7 @@ void nestedForLoopCtrlOpWithSeparateQubit(QCProgramBuilder& b) { b.scfFor(0, 3, 1, [&](Value iv) { auto q0 = b.memrefLoad(reg.value, iv); b.h(q0); - b.ctrl(control, [&] { b.x(q0); }); + b.ctrl(control, q0, [&](ValueRange targets) { b.x(targets[0]); }); }); } @@ -1405,7 +1604,7 @@ void nestedForLoopCtrlOpWithExtractedQubit(QCProgramBuilder& b) { b.scfFor(1, 4, 1, [&](Value iv) { auto q0 = b.memrefLoad(reg.value, iv); b.h(q0); - b.ctrl(reg[0], [&] { b.x(q0); }); + b.ctrl(reg[0], q0, [&](ValueRange targets) { b.x(targets[0]); }); }); } diff --git a/mlir/unittests/programs/qc_programs.h b/mlir/unittests/programs/qc_programs.h index e6569f7648..dbf855b982 100644 --- a/mlir/unittests/programs/qc_programs.h +++ b/mlir/unittests/programs/qc_programs.h @@ -814,6 +814,9 @@ void inverseBarrier(QCProgramBuilder& b); /// Creates a circuit with a trivial ctrl modifier. void trivialCtrl(QCProgramBuilder& b); +/// Creates a circuit with an empty ctrl modifier. +void emptyCtrl(QCProgramBuilder& b); + /// Creates a circuit with nested ctrl modifiers. void nestedCtrl(QCProgramBuilder& b); @@ -826,8 +829,25 @@ void doubleNestedCtrlTwoQubits(QCProgramBuilder& b); /// Creates a circuit with control modifiers interleaved by an inverse modifier. void ctrlInvSandwich(QCProgramBuilder& b); +/// Creates a circuit with a control modifier applied to two gates. +void ctrlTwo(QCProgramBuilder& b); + +/// Creates a circuit with a control modifier applied to a controlled and a +/// non-controlled gate. +void ctrlTwoMixed(QCProgramBuilder& b); + +/// Creates a circuit with nested control modifiers applied to two gates. +void nestedCtrlTwo(QCProgramBuilder& b); + +/// Creates a circuit with a control modifier applied to a inverse modifier +/// applied to two gates. +void ctrlInvTwo(QCProgramBuilder& b); + // --- InvOp ---------------------------------------------------------------- // +/// Creates a circuit with an empty inverse modifier. +void emptyInv(QCProgramBuilder& b); + /// Creates a circuit with nested inverse modifiers. void nestedInv(QCProgramBuilder& b); @@ -837,17 +857,24 @@ void tripleNestedInv(QCProgramBuilder& b); /// Creates a circuit with inverse modifiers interleaved by a control modifier. void invCtrlSandwich(QCProgramBuilder& b); +/// Creates a circuit with an inverse modifier applied to two gates. +void invTwo(QCProgramBuilder& b); + +/// Creates a circuit with an inverse modifier applied to a control modifier +/// applied to two gates. +void invCtrlTwo(QCProgramBuilder& b); + // --- IfOp ----------------------------------------------------------------- // /// Creates a circuit with a simple if operation with one qubit. void simpleIf(QCProgramBuilder& b); -/// Creates a circuit with an if operation with an else branch. -void ifElse(QCProgramBuilder& b); - /// Creates a circuit with an if operation with two qubits. void ifTwoQubits(QCProgramBuilder& b); +/// Creates a circuit with an if operation with an else branch. +void ifElse(QCProgramBuilder& b); + /// Creates a circuit with an if operation with a nested for operation with /// a register. void nestedIfOpForLoop(QCProgramBuilder& b); diff --git a/mlir/unittests/programs/qco_programs.cpp b/mlir/unittests/programs/qco_programs.cpp index 0ad96fbb10..523f071f8a 100644 --- a/mlir/unittests/programs/qco_programs.cpp +++ b/mlir/unittests/programs/qco_programs.cpp @@ -301,6 +301,24 @@ void twoX(QCOProgramBuilder& b) { q[0] = b.x(q[0]); } +void controlledTwoX(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.ctrl(q[0], q[1], [&](ValueRange targets) { + auto q = b.x(targets[0]); + q = b.x(q); + return SmallVector{q}; + }); +} + +void inverseTwoX(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(1); + b.inv(q[0], [&](ValueRange qubits) { + auto q = b.x(qubits[0]); + q = b.x(q); + return SmallVector{q}; + }); +} + void y(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(1); b.y(q[0]); @@ -1936,6 +1954,12 @@ void trivialCtrl(QCOProgramBuilder& b) { }); } +void emptyCtrl(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + std::tie(q[0], q[1]) = b.rxx(0.123, q[0], q[1]); + b.ctrl(q[0], q[1], [&](ValueRange targets) { return targets; }); +} + void nestedCtrl(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(4); b.ctrl({q[0]}, {q[1], q[2], q[3]}, [&](ValueRange targets) { @@ -2003,6 +2027,63 @@ void ctrlInvSandwich(QCOProgramBuilder& b) { }); } +void ctrlTwo(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(4); + b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { + auto i0 = targets[0]; + auto i1 = targets[1]; + i0 = b.x(i0); + std::tie(i0, i1) = b.rxx(0.123, i0, i1); + return SmallVector{i0, i1}; + }); +} + +void ctrlTwoMixed(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(4); + b.ctrl({q[0], q[1]}, {q[2], q[3]}, [&](ValueRange targets) { + auto i0 = targets[0]; + auto i1 = targets[1]; + std::tie(i0, i1) = b.cx(i0, i1); + std::tie(i0, i1) = b.rxx(0.123, i0, i1); + return SmallVector{i0, i1}; + }); +} + +void nestedCtrlTwo(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(4); + b.ctrl(q[0], {q[1], q[2], q[3]}, [&](ValueRange targets) { + const auto& [controlsOut, targetsOut] = b.ctrl( + targets[0], {targets[1], targets[2]}, [&](ValueRange innerTargets) { + auto i0 = innerTargets[0]; + auto i1 = innerTargets[1]; + i0 = b.x(i0); + std::tie(i0, i1) = b.rxx(0.123, i0, i1); + return SmallVector{i0, i1}; + }); + return llvm::to_vector(llvm::concat(controlsOut, targetsOut)); + }); +} + +void ctrlInvTwo(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + b.ctrl(q[0], {q[1], q[2]}, [&](ValueRange targets) { + auto inner = b.inv(targets, [&](ValueRange qubits) { + auto i0 = qubits[0]; + auto i1 = qubits[1]; + i0 = b.x(i0); + std::tie(i0, i1) = b.rxx(0.123, i0, i1); + return SmallVector{i0, i1}; + }); + return llvm::to_vector(inner); + }); +} + +void emptyInv(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + std::tie(q[0], q[1]) = b.rxx(0.123, q[0], q[1]); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { return qubits; }); +} + void nestedInv(QCOProgramBuilder& b) { auto q = b.allocQubitRegister(2); b.inv({q[0], q[1]}, [&](ValueRange qubits) { @@ -2046,6 +2127,32 @@ void invCtrlSandwich(QCOProgramBuilder& b) { }); } +void invTwo(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(2); + b.inv({q[0], q[1]}, [&](ValueRange qubits) { + auto i0 = qubits[0]; + auto i1 = qubits[1]; + i0 = b.x(i0); + std::tie(i0, i1) = b.rxx(0.123, i0, i1); + return SmallVector{i0, i1}; + }); +} + +void invCtrlTwo(QCOProgramBuilder& b) { + auto q = b.allocQubitRegister(3); + b.inv({q[0], q[1], q[2]}, [&](ValueRange qubits) { + const auto& [controlsOut, targetsOut] = + b.ctrl({qubits[0]}, {qubits[1], qubits[2]}, [&](ValueRange targets) { + auto i0 = targets[0]; + auto i1 = targets[1]; + i0 = b.x(i0); + std::tie(i0, i1) = b.rxx(0.123, i0, i1); + return SmallVector{i0, i1}; + }); + return llvm::to_vector(llvm::concat(controlsOut, targetsOut)); + }); +} + 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..f562cfff8a 100644 --- a/mlir/unittests/programs/qco_programs.h +++ b/mlir/unittests/programs/qco_programs.h @@ -167,9 +167,16 @@ void inverseX(QCOProgramBuilder& b); /// Creates a circuit with an inverse modifier applied to a controlled X gate. void inverseMultipleControlledX(QCOProgramBuilder& b); -/// Creates a circuit with two X gates in a row. +/// Creates a circuit with two subsequent X gates. void twoX(QCOProgramBuilder& b); +/// Creates a circuit with a control modifier applied to two subsequent X gates. +void controlledTwoX(QCOProgramBuilder& b); + +/// Creates a circuit with an inverse modifier applied to two subsequent X +/// gates. +void inverseTwoX(QCOProgramBuilder& b); + // --- YOp ------------------------------------------------------------------ // /// Creates a circuit with just a Y gate. @@ -960,6 +967,9 @@ void twoBarrier(QCOProgramBuilder& b); /// Creates a circuit with a trivial ctrl modifier. void trivialCtrl(QCOProgramBuilder& b); +/// Creates a circuit with an empty ctrl modifier. +void emptyCtrl(QCOProgramBuilder& b); + /// Creates a circuit with nested ctrl modifiers. void nestedCtrl(QCOProgramBuilder& b); @@ -972,8 +982,25 @@ void doubleNestedCtrlTwoQubits(QCOProgramBuilder& b); /// Creates a circuit with control modifiers interleaved by an inverse modifier. void ctrlInvSandwich(QCOProgramBuilder& b); +/// Creates a circuit with a control modifier applied to two gates. +void ctrlTwo(QCOProgramBuilder& b); + +/// Creates a circuit with a control modifier applied to a controlled and a +/// non-controlled gate. +void ctrlTwoMixed(QCOProgramBuilder& b); + +/// Creates a circuit with nested control modifiers applied to two gates. +void nestedCtrlTwo(QCOProgramBuilder& b); + +/// Creates a circuit with a control modifier applied to an inverse modifier +/// applied to two gates. +void ctrlInvTwo(QCOProgramBuilder& b); + // --- InvOp ---------------------------------------------------------------- // +/// Creates a circuit with an empty inverse modifier. +void emptyInv(QCOProgramBuilder& b); + /// Creates a circuit with nested inverse modifiers. void nestedInv(QCOProgramBuilder& b); @@ -983,6 +1010,13 @@ void tripleNestedInv(QCOProgramBuilder& b); /// Creates a circuit with inverse modifiers interleaved by a control modifier. void invCtrlSandwich(QCOProgramBuilder& b); +/// Creates a circuit with an inverse modifier applied to two gates. +void invTwo(QCOProgramBuilder& b); + +/// Creates a circuit with an inverse modifier applied to a control modifier +/// applied to two gates. +void invCtrlTwo(QCOProgramBuilder& b); + // --- IfOp ---------------------------------------------------------------- // /// Creates a circuit with a simple if operation with one qubit. diff --git a/mlir/unittests/programs/qir_programs.cpp b/mlir/unittests/programs/qir_programs.cpp index 6ae5023a09..68f209943f 100644 --- a/mlir/unittests/programs/qir_programs.cpp +++ b/mlir/unittests/programs/qir_programs.cpp @@ -605,4 +605,10 @@ void multipleControlledXxMinusYY(QIRProgramBuilder& b) { b.mcxx_minus_yy(0.123, 0.456, {q[0], q[1]}, q[2], q[3]); } +void ctrlTwo(QIRProgramBuilder& b) { + auto q = b.allocQubitRegister(4); + b.mcx({q[0], q[1]}, q[2]); + b.mcrxx(0.123, {q[0], q[1]}, q[2], q[3]); +} + } // namespace mlir::qir diff --git a/mlir/unittests/programs/qir_programs.h b/mlir/unittests/programs/qir_programs.h index 92f6c54078..86a7f7c807 100644 --- a/mlir/unittests/programs/qir_programs.h +++ b/mlir/unittests/programs/qir_programs.h @@ -422,4 +422,9 @@ void singleControlledXxMinusYY(QIRProgramBuilder& b); /// Creates a circuit with a multi-controlled XXMinusYY gate. void multipleControlledXxMinusYY(QIRProgramBuilder& b); +// --- CtrlOp --------------------------------------------------------------- // + +/// Creates a circuit with a control modifier applied to two gates. +void ctrlTwo(QIRProgramBuilder& b); + } // namespace mlir::qir diff --git a/mlir/unittests/programs/quantum_computation_programs.cpp b/mlir/unittests/programs/quantum_computation_programs.cpp index 719fd50b17..f0b9b305cd 100644 --- a/mlir/unittests/programs/quantum_computation_programs.cpp +++ b/mlir/unittests/programs/quantum_computation_programs.cpp @@ -11,10 +11,14 @@ #include "quantum_computation_programs.h" #include "ir/QuantumComputation.hpp" +#include "ir/operations/CompoundOperation.hpp" +#include "ir/operations/IfElseOperation.hpp" #include "ir/operations/OpType.hpp" #include "ir/operations/StandardOperation.hpp" #include +#include +#include namespace qc { @@ -538,6 +542,28 @@ void barrierMultipleQubits(QuantumComputation& comp) { comp.barrier({0, 1, 2}); } +void ctrlTwo(QuantumComputation& comp) { + const auto& q = comp.addQubitRegister(4, "q"); + CompoundOperation compound; + compound.emplace_back(2, X); + compound.emplace_back(Targets{2, 3}, RXX, + std::vector{0.123}); + compound.addControl(0); + compound.addControl(1); + comp.emplace_back(std::move(compound)); +} + +void ctrlTwoMixed(QuantumComputation& comp) { + const auto& q = comp.addQubitRegister(4, "q"); + CompoundOperation compound; + compound.emplace_back(2, 3, X); + compound.emplace_back(Targets{2, 3}, RXX, + std::vector{0.123}); + compound.addControl(0); + compound.addControl(1); + comp.emplace_back(std::move(compound)); +} + void simpleIf(QuantumComputation& comp) { const auto& q = comp.addQubitRegister(1, "q"); const auto& c = comp.addClassicalRegister(1, "c"); @@ -546,6 +572,19 @@ void simpleIf(QuantumComputation& comp) { comp.if_(X, q[0], c[0]); } +void ifTwoQubits(QuantumComputation& comp) { + const auto& q = comp.addQubitRegister(2, "q"); + const auto& c = comp.addClassicalRegister(1, "c"); + comp.h(q[0]); + comp.measure(q[0], c[0]); + CompoundOperation compound; + compound.emplace_back(0, X); + compound.emplace_back(1, X); + IfElseOperation ifElse( + std::make_unique(std::move(compound)), nullptr, c[0]); + comp.emplace_back(std::move(ifElse)); +} + void ifElse(QuantumComputation& comp) { const auto& q = comp.addQubitRegister(1, "q"); const auto& c = comp.addClassicalRegister(1, "c"); diff --git a/mlir/unittests/programs/quantum_computation_programs.h b/mlir/unittests/programs/quantum_computation_programs.h index b21bba30d5..f6dab6e1c2 100644 --- a/mlir/unittests/programs/quantum_computation_programs.h +++ b/mlir/unittests/programs/quantum_computation_programs.h @@ -385,11 +385,23 @@ void barrierTwoQubits(QuantumComputation& comp); /// Creates a circuit with a barrier on multiple qubits. void barrierMultipleQubits(QuantumComputation& comp); +// --- CtrlOp --------------------------------------------------------------- // + +/// Creates a circuit with a control modifier applied to two gates. +void ctrlTwo(QuantumComputation& comp); + +/// Creates a circuit with a control modifier applied to a controlled and a +/// non-controlled gate. +void ctrlTwoMixed(QuantumComputation& comp); + // --- IfOp ----------------------------------------------------------------- // /// Creates a circuit with a simple if operation with one qubit. void simpleIf(QuantumComputation& comp); +/// Creates a circuit with an if operation with two qubits. +void ifTwoQubits(QuantumComputation& comp); + /// Creates a circuit with an if operation with an else branch. void ifElse(QuantumComputation& comp); diff --git a/test/circuits/bell.qasm b/test/circuits/bell.qasm deleted file mode 100644 index c8b3cff498..0000000000 --- a/test/circuits/bell.qasm +++ /dev/null @@ -1,5 +0,0 @@ -OPENQASM 2.0; -//include "qelib1.inc"; -qreg q[2]; -U(pi/2,0,pi) q[0]; -CX q[0],q[1];