From 24cd67d647b2d066a617187935cbdffe14d96e7d Mon Sep 17 00:00:00 2001 From: Romain Moyard Date: Thu, 7 Nov 2024 15:44:08 -0500 Subject: [PATCH] Decomposition for trapped ions hw {RX, RY, MS} (#1226) **Context:** For the trapped ions quantum computer from OQD, the native gate set is RX, RY, MS. **Description of the Change:** Add decomposition pass in MLIR that converts gates to their decomposition in terms of RX, RY, MS. --- mlir/include/Quantum/Transforms/Passes.h | 1 + mlir/include/Quantum/Transforms/Passes.td | 6 + mlir/include/Quantum/Transforms/Patterns.h | 1 + .../Catalyst/Transforms/RegisterAllPasses.cpp | 1 + mlir/lib/Quantum/Transforms/CMakeLists.txt | 2 + .../Transforms/IonsDecompositionPatterns.cpp | 176 ++++++++++++++++++ .../Transforms/ions_decompositions.cpp | 67 +++++++ mlir/test/Quantum/IonsDecompositionTest.mlir | 124 ++++++++++++ 8 files changed, 378 insertions(+) create mode 100644 mlir/lib/Quantum/Transforms/IonsDecompositionPatterns.cpp create mode 100644 mlir/lib/Quantum/Transforms/ions_decompositions.cpp create mode 100644 mlir/test/Quantum/IonsDecompositionTest.mlir diff --git a/mlir/include/Quantum/Transforms/Passes.h b/mlir/include/Quantum/Transforms/Passes.h index 23f3426b89..65a930bb17 100644 --- a/mlir/include/Quantum/Transforms/Passes.h +++ b/mlir/include/Quantum/Transforms/Passes.h @@ -29,5 +29,6 @@ std::unique_ptr createRemoveChainedSelfInversePass(); std::unique_ptr createAnnotateFunctionPass(); std::unique_ptr createSplitMultipleTapesPass(); std::unique_ptr createMergeRotationsPass(); +std::unique_ptr createIonsDecompositionPass(); } // namespace catalyst diff --git a/mlir/include/Quantum/Transforms/Passes.td b/mlir/include/Quantum/Transforms/Passes.td index 501279a1e8..6e7798547b 100644 --- a/mlir/include/Quantum/Transforms/Passes.td +++ b/mlir/include/Quantum/Transforms/Passes.td @@ -88,6 +88,12 @@ def SplitMultipleTapesPass : Pass<"split-multiple-tapes"> { let constructor = "catalyst::createSplitMultipleTapesPass()"; } +def IonsDecompositionPass : Pass<"ions-decomposition"> { + let summary = "Decompose the gates to the set {RX, RY, MS}"; + + let constructor = "catalyst::createIonsDecompositionPass()"; +} + // ----- Quantum circuit transformation passes begin ----- // // For example, automatic compiler peephole opts, etc. diff --git a/mlir/include/Quantum/Transforms/Patterns.h b/mlir/include/Quantum/Transforms/Patterns.h index c023fd6f81..1daf201018 100644 --- a/mlir/include/Quantum/Transforms/Patterns.h +++ b/mlir/include/Quantum/Transforms/Patterns.h @@ -27,6 +27,7 @@ void populateQIRConversionPatterns(mlir::TypeConverter &, mlir::RewritePatternSe void populateAdjointPatterns(mlir::RewritePatternSet &); void populateSelfInversePatterns(mlir::RewritePatternSet &); void populateMergeRotationsPatterns(mlir::RewritePatternSet &); +void populateIonsDecompositionPatterns(mlir::RewritePatternSet &); } // namespace quantum } // namespace catalyst diff --git a/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp b/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp index d3c347fabf..2539d45f1c 100644 --- a/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp +++ b/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp @@ -51,4 +51,5 @@ void catalyst::registerAllCatalystPasses() mlir::registerPass(catalyst::createScatterLoweringPass); mlir::registerPass(catalyst::createSplitMultipleTapesPass); mlir::registerPass(catalyst::createTestPass); + mlir::registerPass(catalyst::createIonsDecompositionPass); } diff --git a/mlir/lib/Quantum/Transforms/CMakeLists.txt b/mlir/lib/Quantum/Transforms/CMakeLists.txt index 96ba30d23e..fcbff39b76 100644 --- a/mlir/lib/Quantum/Transforms/CMakeLists.txt +++ b/mlir/lib/Quantum/Transforms/CMakeLists.txt @@ -15,6 +15,8 @@ file(GLOB SRC SplitMultipleTapes.cpp merge_rotation.cpp MergeRotationsPatterns.cpp + ions_decompositions.cpp + IonsDecompositionPatterns.cpp ) get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) diff --git a/mlir/lib/Quantum/Transforms/IonsDecompositionPatterns.cpp b/mlir/lib/Quantum/Transforms/IonsDecompositionPatterns.cpp new file mode 100644 index 0000000000..ecf5a7b535 --- /dev/null +++ b/mlir/lib/Quantum/Transforms/IonsDecompositionPatterns.cpp @@ -0,0 +1,176 @@ +// Copyright 2024 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define DEBUG_TYPE "ions-decomposition" + +#include "Quantum/IR/QuantumOps.h" +#include "Quantum/Transforms/Patterns.h" +#include "mlir/Dialect/Arith/IR/Arith.h" + +using namespace mlir; +using namespace catalyst::quantum; + +constexpr double PI = llvm::numbers::pi; + +// This function creates the RX(phi) RY(theta) RX(lambda) decomposition +// Note that for parametric operations theta is a Value, for non parametric +// operation theta is an attribute, hence the use of std::variant. + +void oneQubitDecomp(catalyst::quantum::CustomOp op, mlir::PatternRewriter &rewriter, double phi, + std::variant theta, double lambda) +{ + TypeRange outQubitsTypes = op.getOutQubits().getTypes(); + + ValueRange inQubits = op.getInQubits(); + + TypedAttr phiAttr = rewriter.getF64FloatAttr(phi); + mlir::Value phiValue = rewriter.create(op.getLoc(), phiAttr); + + mlir::Value thetaValue; + if (std::holds_alternative(theta)) { + thetaValue = std::get(theta); + } + else if (std::holds_alternative(theta)) { + TypedAttr thetaAttr = rewriter.getF64FloatAttr(std::get(theta)); + thetaValue = rewriter.create(op.getLoc(), thetaAttr); + } + TypedAttr lambdaAttr = rewriter.getF64FloatAttr(lambda); + mlir::Value lambdaValue = rewriter.create(op.getLoc(), lambdaAttr); + + auto rxPhi = rewriter.create(op.getLoc(), outQubitsTypes, ValueRange{}, phiValue, + inQubits, "RX", nullptr, ValueRange{}, ValueRange{}); + auto ryTheta = rewriter.create(op.getLoc(), outQubitsTypes, ValueRange{}, thetaValue, + rxPhi.getOutQubits(), "RY", nullptr, + rxPhi.getInCtrlQubits(), rxPhi.getInCtrlValues()); + auto rxLambda = rewriter.create(op.getLoc(), outQubitsTypes, ValueRange{}, + lambdaValue, ryTheta.getOutQubits(), "RX", nullptr, + ValueRange{}, ValueRange{}); + op.replaceAllUsesWith(rxLambda); +} + +void tDecomp(catalyst::quantum::CustomOp op, mlir::PatternRewriter &rewriter) +{ + if (op.getAdjoint()) { + oneQubitDecomp(op, rewriter, -PI / 2, -PI / 4, PI / 2); + } + else { + oneQubitDecomp(op, rewriter, -PI / 2, PI / 4, PI / 2); + } +} + +void sDecomp(catalyst::quantum::CustomOp op, mlir::PatternRewriter &rewriter) +{ + if (op.getAdjoint()) { + oneQubitDecomp(op, rewriter, -PI / 2, -PI / 2, PI / 2); + } + else { + oneQubitDecomp(op, rewriter, -PI / 2, PI / 2, PI / 2); + } +} + +void zDecomp(catalyst::quantum::CustomOp op, mlir::PatternRewriter &rewriter) +{ + oneQubitDecomp(op, rewriter, -PI / 2, PI, PI / 2); +} + +void hDecomp(catalyst::quantum::CustomOp op, mlir::PatternRewriter &rewriter) +{ + oneQubitDecomp(op, rewriter, 0.0, PI / 2, PI); +} + +void psDecomp(catalyst::quantum::CustomOp op, mlir::PatternRewriter &rewriter) +{ + oneQubitDecomp(op, rewriter, -PI / 2, op.getParams().front(), PI / 2); +} + +void rzDecomp(catalyst::quantum::CustomOp op, mlir::PatternRewriter &rewriter) +{ + oneQubitDecomp(op, rewriter, -PI / 2, op.getParams().front(), PI / 2); +} + +void cnotDecomp(catalyst::quantum::CustomOp op, mlir::PatternRewriter &rewriter) +{ + TypeRange outQubitsTypes = op.getOutQubits().getTypes(); + + mlir::Value inQubit0 = op.getInQubits().front(); + mlir::Value inQubit1 = op.getInQubits().back(); + + TypedAttr piOver2Attr = rewriter.getF64FloatAttr(PI / 2); + mlir::Value piOver2 = rewriter.create(op.getLoc(), piOver2Attr); + auto ryPiOver2 = + rewriter.create(op.getLoc(), outQubitsTypes.front(), ValueRange{}, piOver2, + inQubit0, "RY", nullptr, ValueRange{}, ValueRange{}); + SmallVector qubitsAfterRy; + qubitsAfterRy.push_back(ryPiOver2.getOutQubits().front()); + qubitsAfterRy.push_back(inQubit1); + auto ms = rewriter.create(op.getLoc(), outQubitsTypes, ValueRange{}, piOver2, + qubitsAfterRy, "MS", nullptr, ValueRange{}, ValueRange{}); + mlir::Value qubit0AfterMs = ms.getOutQubits().front(); + mlir::Value qubit1AfterMs = ms.getOutQubits().back(); + + TypedAttr minusPiOver2Attr = rewriter.getF64FloatAttr(-PI / 2); + mlir::Value minusPiOver2 = rewriter.create(op.getLoc(), minusPiOver2Attr); + auto rxMinusPiOver2 = + rewriter.create(op.getLoc(), outQubitsTypes.front(), ValueRange{}, minusPiOver2, + qubit0AfterMs, "RX", nullptr, ValueRange{}, ValueRange{}); + auto firstRyMinusPiOver2 = + rewriter.create(op.getLoc(), outQubitsTypes.front(), ValueRange{}, minusPiOver2, + qubit1AfterMs, "RY", nullptr, ValueRange{}, ValueRange{}); + + mlir::Value qubit0AfterRY = rxMinusPiOver2.getOutQubits().front(); + auto secondRyMinusPiOver2 = + rewriter.create(op.getLoc(), outQubitsTypes.front(), ValueRange{}, minusPiOver2, + qubit0AfterRY, "RY", nullptr, ValueRange{}, ValueRange{}); + + SmallVector qubitsEnd; + qubitsEnd.push_back(firstRyMinusPiOver2.getOutQubits().front()); + qubitsEnd.push_back(secondRyMinusPiOver2.getOutQubits().front()); + op.replaceAllUsesWith(qubitsEnd); +} + +std::map> + funcMap = {{"T", &tDecomp}, {"S", &sDecomp}, {"Z", &zDecomp}, + {"Hadamard", &hDecomp}, {"RZ", &rzDecomp}, {"PhaseShift", &psDecomp}, + {"CNOT", &cnotDecomp}}; + +namespace { + +struct IonsDecompositionRewritePattern : public mlir::OpRewritePattern { + using mlir::OpRewritePattern::OpRewritePattern; + + mlir::LogicalResult matchAndRewrite(CustomOp op, mlir::PatternRewriter &rewriter) const override + { + auto it = funcMap.find(op.getGateName().str()); + if (it != funcMap.end()) { + auto decompFunc = it->second; + decompFunc(op, rewriter); + return success(); + } + else { + return failure(); + } + } +}; +} // namespace + +namespace catalyst { +namespace quantum { + +void populateIonsDecompositionPatterns(RewritePatternSet &patterns) +{ + patterns.add(patterns.getContext(), 1); +} + +} // namespace quantum +} // namespace catalyst diff --git a/mlir/lib/Quantum/Transforms/ions_decompositions.cpp b/mlir/lib/Quantum/Transforms/ions_decompositions.cpp new file mode 100644 index 0000000000..c4d833816e --- /dev/null +++ b/mlir/lib/Quantum/Transforms/ions_decompositions.cpp @@ -0,0 +1,67 @@ +// Copyright 2024 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define DEBUG_TYPE "ions-decomposition" + +#include "Catalyst/IR/CatalystDialect.h" +#include "Quantum/IR/QuantumOps.h" +#include "Quantum/Transforms/Patterns.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" +#include "llvm/Support/Debug.h" + +using namespace llvm; +using namespace mlir; +using namespace catalyst::quantum; + +namespace catalyst { +namespace quantum { + +#define GEN_PASS_DEF_IONSDECOMPOSITIONPASS +#define GEN_PASS_DECL_IONSDECOMPOSITIONPASS +#include "Quantum/Transforms/Passes.h.inc" + +struct IonsDecompositionPass : impl::IonsDecompositionPassBase { + using IonsDecompositionPassBase::IonsDecompositionPassBase; + + void runOnOperation() final + { + LLVM_DEBUG(dbgs() << "ions decomposition pass" + << "\n"); + + Operation *module = getOperation(); + + RewritePatternSet patternsCanonicalization(&getContext()); + catalyst::quantum::CustomOp::getCanonicalizationPatterns(patternsCanonicalization, + &getContext()); + if (failed(applyPatternsAndFoldGreedily(module, std::move(patternsCanonicalization)))) { + return signalPassFailure(); + } + RewritePatternSet patterns(&getContext()); + populateIonsDecompositionPatterns(patterns); + if (failed(applyPatternsAndFoldGreedily(module, std::move(patterns)))) { + return signalPassFailure(); + } + } +}; + +} // namespace quantum + +std::unique_ptr createIonsDecompositionPass() +{ + return std::make_unique(); +} + +} // namespace catalyst diff --git a/mlir/test/Quantum/IonsDecompositionTest.mlir b/mlir/test/Quantum/IonsDecompositionTest.mlir new file mode 100644 index 0000000000..52419d379b --- /dev/null +++ b/mlir/test/Quantum/IonsDecompositionTest.mlir @@ -0,0 +1,124 @@ +// Copyright 2024 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// RUN: quantum-opt --ions-decomposition --split-input-file -verify-diagnostics %s | FileCheck %s + +func.func @test_non_param_1q(%arg0: f64) -> !quantum.bit { + // CHECK: [[PIO2:%.+]] = arith.constant 1.5707963267948966 : f64 + // CHECK: [[PIO4:%.+]] = arith.constant 0.78539816339744828 : f64 + // CHECK: [[MPIO2:%.+]] = arith.constant -1.5707963267948966 : f64 + // CHECK: [[reg:%.+]] = quantum.alloc( 1) : !quantum.reg + // CHECK: [[qubit:%.+]] = quantum.extract [[reg]][ 0] : !quantum.reg -> !quantum.bit + // CHECK: [[qubit1:%.+]] = quantum.custom "RX"([[MPIO2]]) [[qubit]] : !quantum.bit + // CHECK: [[qubit2:%.+]] = quantum.custom "RY"([[PIO4]]) [[qubit1]] : !quantum.bit + // CHECK: [[qubit3:%.+]] = quantum.custom "RX"([[PIO2]]) [[qubit2]] : !quantum.bit + // CHECK: [[qubit4:%.+]] = quantum.custom "RX"(%arg0) [[qubit3]] : !quantum.bit + // CHECK: [[qubit5:%.+]] = quantum.custom "RX"([[MPIO2]]) [[qubit4]] : !quantum.bit + // CHECK: [[qubit6:%.+]] = quantum.custom "RY"([[PIO4]]) [[qubit5]] : !quantum.bit + // CHECK: [[qubit7:%.+]] = quantum.custom "RX"([[PIO2]]) [[qubit6]] : !quantum.bit + // CHECK: return [[qubit7]] + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit + %2 = quantum.custom "T"() %1 : !quantum.bit + %3 = quantum.custom "RX"(%arg0) %2 : !quantum.bit + %4 = quantum.custom "T"() %3 : !quantum.bit + return %4 : !quantum.bit +} + +// ----- + +func.func @test_s_dagger(%arg0: f64) -> !quantum.bit { + // CHECK: [[PIO2:%.+]] = arith.constant 1.5707963267948966 : f64 + // CHECK: [[MPIO2:%.+]] = arith.constant -1.5707963267948966 : f64 + // CHECK: [[reg:%.+]] = quantum.alloc( 1) : !quantum.reg + // CHECK: [[qubit:%.+]] = quantum.extract [[reg]][ 0] : !quantum.reg -> !quantum.bit + // CHECK: [[qubit1:%.+]] = quantum.custom "RX"([[MPIO2]]) [[qubit]] : !quantum.bit + // CHECK: [[qubit2:%.+]] = quantum.custom "RY"([[MPIO2]]) [[qubit1]] : !quantum.bit + // CHECK: [[qubit3:%.+]] = quantum.custom "RX"([[PIO2]]) [[qubit2]] : !quantum.bit + // CHECK: [[qubit4:%.+]] = quantum.custom "RX"([[MPIO2]]) [[qubit3]] : !quantum.bit + // CHECK: [[qubit5:%.+]] = quantum.custom "RY"([[PIO2]]) [[qubit4]] : !quantum.bit + // CHECK: [[qubit6:%.+]] = quantum.custom "RX"([[PIO2]]) [[qubit5]] : !quantum.bit + // CHECK: return [[qubit6]] + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit + %2 = quantum.custom "S"() %1 {adjoint}: !quantum.bit + %3 = quantum.custom "S"() %2 : !quantum.bit + return %3 : !quantum.bit +} + +// ----- + +func.func @test_t_dagger(%arg0: f64) -> !quantum.bit { + // CHECK: [[PIO4:%.+]] = arith.constant 0.78539816339744828 : f64 + // CHECK: [[PIO2:%.+]] = arith.constant 1.5707963267948966 : f64 + // CHECK: [[MPIO4:%.+]] = arith.constant -0.78539816339744828 : f64 + // CHECK: [[MPIO2:%.+]] = arith.constant -1.5707963267948966 : f64 + // CHECK: [[reg:%.+]] = quantum.alloc( 1) : !quantum.reg + // CHECK: [[qubit:%.+]] = quantum.extract [[reg]][ 0] : !quantum.reg -> !quantum.bit + // CHECK: [[qubit1:%.+]] = quantum.custom "RX"([[MPIO2]]) [[qubit]] : !quantum.bit + // CHECK: [[qubit2:%.+]] = quantum.custom "RY"([[MPIO4]]) [[qubit1]] : !quantum.bit + // CHECK: [[qubit3:%.+]] = quantum.custom "RX"([[PIO2]]) [[qubit2]] : !quantum.bit + // CHECK: [[qubit4:%.+]] = quantum.custom "RX"([[MPIO2]]) [[qubit3]] : !quantum.bit + // CHECK: [[qubit5:%.+]] = quantum.custom "RY"([[PIO4]]) [[qubit4]] : !quantum.bit + // CHECK: [[qubit6:%.+]] = quantum.custom "RX"([[PIO2]]) [[qubit5]] : !quantum.bit + // CHECK: return [[qubit6]] + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit + %2 = quantum.custom "T"() %1 {adjoint}: !quantum.bit + %3 = quantum.custom "T"() %2 : !quantum.bit + return %3 : !quantum.bit +} + +// ----- +func.func @test_param_1q(%arg0: f64, %arg1: f64, %arg2: f64) -> !quantum.bit { + // CHECK: [[PIO2:%.+]] = arith.constant 1.5707963267948966 : f64 + // CHECK: [[MPIO2:%.+]] = arith.constant -1.5707963267948966 : f64 + // CHECK: [[reg:%.+]] = quantum.alloc( 1) : !quantum.reg + // CHECK: [[qubit:%.+]] = quantum.extract [[reg]][ 0] : !quantum.reg -> !quantum.bit + // CHECK: [[qubit1:%.+]] = quantum.custom "RX"([[MPIO2]]) [[qubit]] : !quantum.bit + // CHECK: [[qubit2:%.+]] = quantum.custom "RY"(%arg0) [[qubit1]] : !quantum.bit + // CHECK: [[qubit3:%.+]] = quantum.custom "RX"([[PIO2]]) [[qubit2]] : !quantum.bit + // CHECK: [[qubit4:%.+]] = quantum.custom "RX"(%arg1) [[qubit3]] : !quantum.bit + // CHECK: [[qubit5:%.+]] = quantum.custom "RX"([[MPIO2]]) [[qubit4]] : !quantum.bit + // CHECK: [[qubit6:%.+]] = quantum.custom "RY"(%arg2) [[qubit5]] : !quantum.bit + // CHECK: [[qubit7:%.+]] = quantum.custom "RX"([[PIO2]]) [[qubit6]] : !quantum.bit + // CHECK: return [[qubit7]] + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit + %2 = quantum.custom "RZ"(%arg0) %1 : !quantum.bit + %3 = quantum.custom "RX"(%arg1) %2 : !quantum.bit + %4 = quantum.custom "RZ"(%arg2) %3 : !quantum.bit + return %4 : !quantum.bit +} + +// ----- + +func.func @test_CNOT(%arg0: f64, %arg1: f64, %arg2: f64) -> (!quantum.bit, !quantum.bit) { + // CHECK: [[MPIO2:%.+]] = arith.constant -1.5707963267948966 : f64 + // CHECK: [[PIO2:%.+]] = arith.constant 1.5707963267948966 : f64 + // CHECK: [[reg:%.+]] = quantum.alloc( 2) : !quantum.reg + // CHECK: [[qubit0:%.+]] = quantum.extract [[reg]][ 0] : !quantum.reg -> !quantum.bit + // CHECK: [[qubit1:%.+]] = quantum.extract [[reg]][ 1] : !quantum.reg -> !quantum.bit + // CHECK: [[qubit2:%.+]] = quantum.custom "RY"([[PIO2]]) [[qubit0]] : !quantum.bit + // CHECK: [[qubits3:%.+]]:2 = quantum.custom "MS"([[PIO2]]) [[qubit2]], [[qubit1]] : !quantum.bit, !quantum.bit + // CHECK: [[qubit4:%.+]] = quantum.custom "RX"([[MPIO2]]) [[qubits3]]#0 : !quantum.bit + // CHECK: [[qubit5:%.+]] = quantum.custom "RY"([[MPIO2]]) [[qubits3]]#1 : !quantum.bit + // CHECK: [[qubit6:%.+]] = quantum.custom "RY"([[MPIO2]]) [[qubit4]] : !quantum.bit + // CHECK: return [[qubit5]], [[qubit6]] : !quantum.bit, !quantum.bit + %0 = quantum.alloc( 2) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit + %2 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit + %3:2 = quantum.custom "CNOT"() %1, %2 : !quantum.bit, !quantum.bit + return %3#0, %3#1 : !quantum.bit, !quantum.bit +}