diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index d034ca6faf..d47c1db9dd 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -55,6 +55,24 @@ [(#1357)](https://github.com/PennyLaneAI/catalyst/pull/1357) [(#1385)](https://github.com/PennyLaneAI/catalyst/pull/1385) +* A new circuit optimization pass, `--disentangle-CNOT`, is available. + [(#1154)](https://github.com/PennyLaneAI/catalyst/pull/1154) + + The pass disentangles CNOT gates whenever possible, e.g. when the control bit + is known to be in |0>, the pass removes the CNOT. The pass uses a finite state + machine to propagate simple one-qubit states, in order to determine + the input states to the CNOT. + + The algorithm is taken from [Relaxed Peephole Optimization: A Novel Compiler Optimization for Quantum Circuits, by Ji Liu, Luciano Bello, and Huiyang Zhou](https://arxiv.org/abs/2012.07711). + +* A new circuit optimization pass, `--disentangle-SWAP`, is available. + [(#1297)](https://github.com/PennyLaneAI/catalyst/pull/1297) + + The pass disentangles SWAP gates whenever possible by using a finite state + machine to propagate simple one-qubit states, similar to the `--disentangle-CNOT` pass. + + The algorithm is taken from [Relaxed Peephole Optimization: A Novel Compiler Optimization for Quantum Circuits, by Ji Liu, Luciano Bello, and Huiyang Zhou](https://arxiv.org/abs/2012.07711). +

Breaking changes 💔

* The `sample` and `counts` measurement primitives now support dynamic shot values across catalyst, although at the PennyLane side, the device shots still is constrained to a static integer literal. @@ -193,5 +211,6 @@ Mehrdad Malekmohammadi, William Maxwell Romain Moyard, Shuli Shu, +Ritu Thombre, Raul Torres, Paul Haochen Wang. diff --git a/mlir/include/Quantum/Transforms/Passes.h b/mlir/include/Quantum/Transforms/Passes.h index d43490d7cd..d733a108d7 100644 --- a/mlir/include/Quantum/Transforms/Passes.h +++ b/mlir/include/Quantum/Transforms/Passes.h @@ -29,6 +29,8 @@ std::unique_ptr createRemoveChainedSelfInversePass(); std::unique_ptr createAnnotateFunctionPass(); std::unique_ptr createSplitMultipleTapesPass(); std::unique_ptr createMergeRotationsPass(); +std::unique_ptr createDisentangleCNOTPass(); +std::unique_ptr createDisentangleSWAPPass(); std::unique_ptr createIonsDecompositionPass(); std::unique_ptr createStaticCustomLoweringPass(); diff --git a/mlir/include/Quantum/Transforms/Passes.td b/mlir/include/Quantum/Transforms/Passes.td index 5a5325adb3..168aed6a00 100644 --- a/mlir/include/Quantum/Transforms/Passes.td +++ b/mlir/include/Quantum/Transforms/Passes.td @@ -116,6 +116,28 @@ def MergeRotationsPass : Pass<"merge-rotations"> { let constructor = "catalyst::createMergeRotationsPass()"; } +def DisentangleCNOTPass : Pass<"disentangle-CNOT"> { + let summary = "Replace a CNOT gate with two single qubit gates whenever possible."; + + let constructor = "catalyst::createDisentangleCNOTPass()"; + let options = [ + Option<"EmitFSMStateRemark", "emit-FSM-state-remark", + "bool", /*default=*/"false", + "Whether to emit the state analysis result from the simple states propagation FSM onto the gate operations.">, + ]; +} + +def DisentangleSWAPPass : Pass<"disentangle-SWAP"> { + let summary = "Replace a SWAP gate with single qubit gates and a shorter SWAPZ gates whenever possible."; + + let constructor = "catalyst::createDisentangleSWAPPass()"; + let options = [ + Option<"EmitFSMStateRemark", "emit-FSM-state-remark", + "bool", /*default=*/"false", + "Whether to emit the state analysis result from the simple states propagation FSM onto the gate operations.">, + ]; +} + // ----- Quantum circuit transformation passes end ----- // #endif // QUANTUM_PASSES diff --git a/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp b/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp index eb5a79feae..442a4a6656 100644 --- a/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp +++ b/mlir/lib/Catalyst/Transforms/RegisterAllPasses.cpp @@ -31,6 +31,8 @@ void catalyst::registerAllCatalystPasses() mlir::registerPass(catalyst::createCopyGlobalMemRefPass); mlir::registerPass(catalyst::createDetensorizeSCFPass); mlir::registerPass(catalyst::createDisableAssertionPass); + mlir::registerPass(catalyst::createDisentangleCNOTPass); + mlir::registerPass(catalyst::createDisentangleSWAPPass); mlir::registerPass(catalyst::createEmitCatalystPyInterfacePass); mlir::registerPass(catalyst::createGEPInboundsPass); mlir::registerPass(catalyst::createGradientBufferizationPass); diff --git a/mlir/lib/Quantum/Transforms/CMakeLists.txt b/mlir/lib/Quantum/Transforms/CMakeLists.txt index 2483d4618d..1281b5bba3 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 + DisentangleSWAP.cpp + DisentangleCNOT.cpp ions_decompositions.cpp IonsDecompositionPatterns.cpp static_custom_lowering.cpp diff --git a/mlir/lib/Quantum/Transforms/DisentangleCNOT.cpp b/mlir/lib/Quantum/Transforms/DisentangleCNOT.cpp new file mode 100644 index 0000000000..bb5086f11f --- /dev/null +++ b/mlir/lib/Quantum/Transforms/DisentangleCNOT.cpp @@ -0,0 +1,143 @@ +// 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. + +// This algorithm is taken from https://arxiv.org/pdf/2012.07711, table 1 + +#define DEBUG_TYPE "disentanglecnot" + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/Pass.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/Support/Debug.h" + +#include "Catalyst/IR/CatalystDialect.h" +#include "Quantum/IR/QuantumOps.h" + +#include "PropagateSimpleStatesAnalysis.hpp" + +using namespace mlir; +using namespace catalyst; + +namespace catalyst { +#define GEN_PASS_DEF_DISENTANGLECNOTPASS +#define GEN_PASS_DECL_DISENTANGLECNOTPASS +#include "Quantum/Transforms/Passes.h.inc" + +struct DisentangleCNOTPass : public impl::DisentangleCNOTPassBase { + using impl::DisentangleCNOTPassBase::DisentangleCNOTPassBase; + + bool canScheduleOn(RegisteredOperationName opInfo) const override + { + return opInfo.hasInterface(); + } + + void runOnOperation() override + { + FunctionOpInterface func = cast(getOperation()); + mlir::IRRewriter builder(func->getContext()); + Location loc = func->getLoc(); + + PropagateSimpleStatesAnalysis &pssa = getAnalysis(); + llvm::DenseMap qubitValues = pssa.getQubitValues(); + + if (EmitFSMStateRemark) { + for (auto it = qubitValues.begin(); it != qubitValues.end(); ++it) { + it->first.getDefiningOp()->emitRemark(pssa.QubitState2String(it->second)); + } + } + + func->walk([&](quantum::CustomOp op) { + StringRef gate = op.getGateName(); + if (gate != "CNOT") { + return; + } + + Value controlIn = op->getOperand(0); + Value targetIn = op->getOperand(1); + Value controlOut = op->getResult(0); + Value targetOut = op->getResult(1); + + // Do nothing if the inputs states are not tracked + if (!qubitValues.contains(controlIn) || !qubitValues.contains(targetIn)) { + return; + } + + // |0> control, always do nothing + if (pssa.isZero(qubitValues[controlIn])) { + controlOut.replaceAllUsesWith(controlIn); + targetOut.replaceAllUsesWith(targetIn); + op->erase(); + return; + } + + // |1> control, insert PauliX gate on target + if (pssa.isOne(qubitValues[controlIn])) { + controlOut.replaceAllUsesWith(controlIn); + + // PauliX on |+-> is unnecessary: they are eigenstates! + if ((pssa.isPlus(qubitValues[targetIn])) || (pssa.isMinus(qubitValues[targetIn]))) { + targetOut.replaceAllUsesWith(targetIn); + op->erase(); + return; + } + else { + builder.setInsertionPoint(op); + quantum::CustomOp xgate = builder.create( + loc, /*gate_name=*/"PauliX", + /*in_qubits=*/mlir::ValueRange({targetIn})); + targetOut.replaceAllUsesWith(xgate->getResult(0)); + op->erase(); + return; + } + } + + // |+> target, always do nothing + if (pssa.isPlus(qubitValues[targetIn])) { + controlOut.replaceAllUsesWith(controlIn); + targetOut.replaceAllUsesWith(targetIn); + op->erase(); + return; + } + + // |-> target, insert PauliZ on control + if (pssa.isMinus(qubitValues[targetIn])) { + targetOut.replaceAllUsesWith(targetIn); + + // PauliZ on |01> is unnecessary: they are eigenstates! + if ((pssa.isZero(qubitValues[controlIn])) || (pssa.isOne(qubitValues[controlIn]))) { + controlOut.replaceAllUsesWith(controlIn); + op->erase(); + return; + } + else { + builder.setInsertionPoint(op); + quantum::CustomOp zgate = builder.create( + loc, /*gate_name=*/"PauliZ", + /*in_qubits=*/mlir::ValueRange({controlIn})); + controlOut.replaceAllUsesWith(zgate->getResult(0)); + op->erase(); + return; + } + } + }); + } +}; + +std::unique_ptr createDisentangleCNOTPass() +{ + return std::make_unique(); +} + +} // namespace catalyst diff --git a/mlir/lib/Quantum/Transforms/DisentangleSWAP.cpp b/mlir/lib/Quantum/Transforms/DisentangleSWAP.cpp new file mode 100644 index 0000000000..fe10e3abc4 --- /dev/null +++ b/mlir/lib/Quantum/Transforms/DisentangleSWAP.cpp @@ -0,0 +1,507 @@ +// 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. + +// This algorithm is taken from https://arxiv.org/pdf/2012.07711, table 6 (Equivalences for +// basis-states in SWAP gate) + +#define DEBUG_TYPE "disentangleswap" + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/Pass.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/Support/Debug.h" + +#include "Catalyst/IR/CatalystDialect.h" +#include "Quantum/IR/QuantumOps.h" + +#include "PropagateSimpleStatesAnalysis.hpp" + +using namespace mlir; +using namespace catalyst; + +namespace catalyst { +#define GEN_PASS_DEF_DISENTANGLESWAPPASS +#define GEN_PASS_DECL_DISENTANGLESWAPPASS +#include "Quantum/Transforms/Passes.h.inc" + +struct DisentangleSWAPPass : public impl::DisentangleSWAPPassBase { + using impl::DisentangleSWAPPassBase::DisentangleSWAPPassBase; + + // function to create a single qubit gate with a given name + // right after the SWAP is to be erased + quantum::CustomOp createSimpleOneBitGate(StringRef gateName, const Value &inQubit, + const Value &outQubit, mlir::IRRewriter &builder, + Location &loc, + const quantum::CustomOp &insert_after_gate) + { + OpBuilder::InsertionGuard insertionGuard(builder); + builder.setInsertionPointAfter(insert_after_gate); + quantum::CustomOp newGate = + builder.create(loc, + /*out_qubits=*/mlir::TypeRange({outQubit.getType()}), + /*out_ctrl_qubits=*/mlir::TypeRange(), + /*params=*/mlir::ValueRange(), + /*in_qubits=*/mlir::ValueRange({inQubit}), + /*gate_name=*/gateName, + /*adjoint=*/nullptr, + /*in_ctrl_qubits=*/mlir::ValueRange(), + /*in_ctrl_values=*/mlir::ValueRange()); + + return newGate; + } + + // above function overloaded to create a single qubit gate with a given name + // if multiple gates are to be inserted after SWAP transformation + quantum::CustomOp createSimpleOneBitGate(StringRef gateName, const Value &inQubit, + mlir::IRRewriter &builder, Location &loc, + const quantum::CustomOp &insert_after_gate) + { + OpBuilder::InsertionGuard insertionGuard(builder); + builder.setInsertionPointAfter(insert_after_gate); + quantum::CustomOp newGate = + builder.create(loc, + /*out_qubits=*/mlir::TypeRange({inQubit.getType()}), + /*out_ctrl_qubits=*/mlir::TypeRange(), + /*params=*/mlir::ValueRange(), + /*in_qubits=*/mlir::ValueRange({inQubit}), + /*gate_name=*/gateName, + /*adjoint=*/nullptr, + /*in_ctrl_qubits=*/mlir::ValueRange(), + /*in_ctrl_values=*/mlir::ValueRange()); + + return newGate; + } + + // function to create a two qubit gate with a given name + // right after the SWAP is to be erased + quantum::CustomOp createSimpleTwoBitGate(StringRef gateName, const Value &controlIn, + const Value &targetIn, const Value &controlOut, + const Value &targetOut, mlir::IRRewriter &builder, + Location &loc, + const quantum::CustomOp &insert_after_gate) + { + OpBuilder::InsertionGuard insertionGuard(builder); + builder.setInsertionPointAfter(insert_after_gate); + quantum::CustomOp newGate = builder.create( + loc, + /*out_qubits=*/mlir::TypeRange({controlOut.getType(), targetOut.getType()}), + /*out_ctrl_qubits=*/mlir::TypeRange({}), + /*params=*/mlir::ValueRange(), + /*in_qubits=*/mlir::ValueRange({controlIn, targetIn}), + /*gate_name=*/gateName, + /*adjoint=*/nullptr, + /*in_ctrl_qubits=*/mlir::ValueRange({}), + /*in_ctrl_values=*/mlir::ValueRange()); + + return newGate; + } + + // above function overloaded to create a two qubit gate with a given name + // if multiple gates are to be inserted after SWAP transformation + quantum::CustomOp createSimpleTwoBitGate(StringRef gateName, const Value &controlIn, + const Value &targetIn, mlir::IRRewriter &builder, + Location &loc, + const quantum::CustomOp &insert_after_gate) + { + OpBuilder::InsertionGuard insertionGuard(builder); + builder.setInsertionPointAfter(insert_after_gate); + quantum::CustomOp newGate = builder.create( + loc, + /*out_qubits=*/mlir::TypeRange({controlIn.getType(), targetIn.getType()}), + /*out_ctrl_qubits=*/mlir::TypeRange({}), + /*params=*/mlir::ValueRange(), + /*in_qubits=*/mlir::ValueRange({controlIn, targetIn}), + /*gate_name=*/gateName, + /*adjoint=*/nullptr, + /*in_ctrl_qubits=*/mlir::ValueRange({}), + /*in_ctrl_values=*/mlir::ValueRange()); + + return newGate; + } + + bool canScheduleOn(RegisteredOperationName opInfo) const override + { + return opInfo.hasInterface(); + } + + void runOnOperation() override + { + FunctionOpInterface func = cast(getOperation()); + mlir::IRRewriter builder(func->getContext()); + Location loc = func->getLoc(); + + PropagateSimpleStatesAnalysis &pssa = getAnalysis(); + llvm::DenseMap qubitValues = pssa.getQubitValues(); + + func->walk([&](quantum::CustomOp op) { + StringRef gate = op.getGateName(); + if (gate != "SWAP") { + return; + } + + Value SwapQubit_0_In = op->getOperand(0); + Value SwapQubit_1_In = op->getOperand(1); + Value SwapQubit_0_Out = op->getResult(0); + Value SwapQubit_1_Out = op->getResult(1); + + // first qubit in |0> + if (pssa.isZero(qubitValues[SwapQubit_0_In])) { + // second qubit in |0>: SWAP(|0>,|0>) + if (pssa.isZero(qubitValues[SwapQubit_1_In])) { + SwapQubit_0_Out.replaceAllUsesWith(SwapQubit_0_In); + SwapQubit_1_Out.replaceAllUsesWith(SwapQubit_1_In); + op->erase(); + return; + } + // second qubit in |1>: SWAP(|0>,|1>) + else if (pssa.isOne(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp xgate_on_0 = createSimpleOneBitGate( + "PauliX", SwapQubit_0_In, SwapQubit_0_Out, builder, loc, op); + SwapQubit_0_Out.replaceAllUsesWith(xgate_on_0->getResult(0)); + + quantum::CustomOp xgate_on_1 = createSimpleOneBitGate( + "PauliX", SwapQubit_1_In, SwapQubit_1_Out, builder, loc, xgate_on_0); + SwapQubit_1_Out.replaceAllUsesWith(xgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |+>: SWAP(|0>,|+>) + else if (pssa.isPlus(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp hgate_on_0 = createSimpleOneBitGate( + "Hadamard", SwapQubit_0_In, SwapQubit_0_Out, builder, loc, op); + SwapQubit_0_Out.replaceAllUsesWith(hgate_on_0->getResult(0)); + + quantum::CustomOp hgate_on_1 = createSimpleOneBitGate( + "Hadamard", SwapQubit_1_In, SwapQubit_1_Out, builder, loc, hgate_on_0); + SwapQubit_1_Out.replaceAllUsesWith(hgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |->: SWAP(|0>,|->) + else if (pssa.isMinus(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp xgate_on_0 = + createSimpleOneBitGate("PauliX", SwapQubit_0_In, builder, loc, op); + + quantum::CustomOp hgate_on_0 = createSimpleOneBitGate( + "Hadamard", xgate_on_0->getResult(0), builder, loc, xgate_on_0); + SwapQubit_0_Out.replaceAllUsesWith(hgate_on_0->getResult(0)); + + quantum::CustomOp hgate_on_1 = createSimpleOneBitGate( + "Hadamard", SwapQubit_1_In, builder, loc, hgate_on_0); + + quantum::CustomOp xgate_on_1 = createSimpleOneBitGate( + "PauliX", hgate_on_1->getResult(0), builder, loc, hgate_on_1); + SwapQubit_1_Out.replaceAllUsesWith(xgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in NON_BASIS: SWAP(|0>,|NON_BASIS>) + else if (pssa.isOther(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp cnot_on_1_0 = createSimpleTwoBitGate( + "CNOT", SwapQubit_1_In, SwapQubit_0_In, builder, loc, op); + + quantum::CustomOp cnot_on_0_1 = createSimpleTwoBitGate( + "CNOT", cnot_on_1_0->getResult(1), cnot_on_1_0->getResult(0), builder, loc, + cnot_on_1_0); + + SwapQubit_0_Out.replaceAllUsesWith(cnot_on_0_1->getResult(0)); + SwapQubit_1_Out.replaceAllUsesWith(cnot_on_0_1->getResult(1)); + op->erase(); + return; + } + } + + // first qubit in |1> + else if (pssa.isOne(qubitValues[SwapQubit_0_In])) { + // second qubit in |0>: SWAP(|1>,|0>) + if (pssa.isZero(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp xgate_on_0 = createSimpleOneBitGate( + "PauliX", SwapQubit_0_In, SwapQubit_0_Out, builder, loc, op); + SwapQubit_0_Out.replaceAllUsesWith(xgate_on_0->getResult(0)); + + quantum::CustomOp xgate_on_1 = createSimpleOneBitGate( + "PauliX", SwapQubit_1_In, SwapQubit_1_Out, builder, loc, xgate_on_0); + SwapQubit_1_Out.replaceAllUsesWith(xgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |1>: SWAP(|1>,|1>) + else if (pssa.isOne(qubitValues[SwapQubit_1_In])) { + SwapQubit_0_Out.replaceAllUsesWith(SwapQubit_0_In); + SwapQubit_1_Out.replaceAllUsesWith(SwapQubit_1_In); + op->erase(); + return; + } + // second qubit in |+>: SWAP(|1>,|+>) + else if (pssa.isPlus(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp xgate_on_0 = + createSimpleOneBitGate("PauliX", SwapQubit_0_In, builder, loc, op); + + quantum::CustomOp hgate_on_0 = createSimpleOneBitGate( + "Hadamard", xgate_on_0->getResult(0), builder, loc, xgate_on_0); + SwapQubit_0_Out.replaceAllUsesWith(hgate_on_0->getResult(0)); + + quantum::CustomOp hgate_on_1 = createSimpleOneBitGate( + "Hadamard", SwapQubit_1_In, builder, loc, hgate_on_0); + + quantum::CustomOp xgate_on_1 = createSimpleOneBitGate( + "PauliX", hgate_on_1->getResult(0), builder, loc, hgate_on_1); + SwapQubit_1_Out.replaceAllUsesWith(xgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |->: SWAP(|1>,|->) + else if (pssa.isMinus(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp hgate_on_0 = createSimpleOneBitGate( + "Hadamard", SwapQubit_0_In, SwapQubit_0_Out, builder, loc, op); + SwapQubit_0_Out.replaceAllUsesWith(hgate_on_0->getResult(0)); + + quantum::CustomOp hgate_on_1 = createSimpleOneBitGate( + "Hadamard", SwapQubit_1_In, SwapQubit_1_Out, builder, loc, hgate_on_0); + SwapQubit_1_Out.replaceAllUsesWith(hgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |NON_BASIS>: SWAP(|1>,|NON_BASIS>) + else if (pssa.isOther(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp xgate_on_1 = + createSimpleOneBitGate("PauliX", SwapQubit_1_In, builder, loc, op); + + quantum::CustomOp cnot_on_1_0 = createSimpleTwoBitGate( + "CNOT", xgate_on_1->getResult(0), SwapQubit_0_In, builder, loc, xgate_on_1); + + quantum::CustomOp cnot_on_0_1 = createSimpleTwoBitGate( + "CNOT", cnot_on_1_0->getResult(1), cnot_on_1_0->getResult(0), builder, loc, + cnot_on_1_0); + + SwapQubit_0_Out.replaceAllUsesWith(cnot_on_0_1->getResult(0)); + SwapQubit_1_Out.replaceAllUsesWith(cnot_on_0_1->getResult(1)); + op->erase(); + return; + } + } + + // first qubit in |+> + else if (pssa.isPlus(qubitValues[SwapQubit_0_In])) { + // second qubit in |0>: SWAP(|+>,|0>) + if (pssa.isZero(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp hgate_on_0 = createSimpleOneBitGate( + "Hadamard", SwapQubit_0_In, SwapQubit_0_Out, builder, loc, op); + SwapQubit_0_Out.replaceAllUsesWith(hgate_on_0->getResult(0)); + + quantum::CustomOp hgate_on_1 = createSimpleOneBitGate( + "Hadamard", SwapQubit_1_In, SwapQubit_1_Out, builder, loc, hgate_on_0); + SwapQubit_1_Out.replaceAllUsesWith(hgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |01>: SWAP(|+>,|1>) + else if (pssa.isOne(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp hgate_on_0 = + createSimpleOneBitGate("Hadamard", SwapQubit_0_In, builder, loc, op); + + quantum::CustomOp xgate_on_0 = createSimpleOneBitGate( + "PauliX", hgate_on_0->getResult(0), builder, loc, hgate_on_0); + SwapQubit_0_Out.replaceAllUsesWith(xgate_on_0->getResult(0)); + + quantum::CustomOp xgate_on_1 = + createSimpleOneBitGate("PauliX", SwapQubit_1_In, builder, loc, xgate_on_0); + + quantum::CustomOp hgate_on_1 = createSimpleOneBitGate( + "Hadamard", xgate_on_1->getResult(0), builder, loc, xgate_on_1); + SwapQubit_1_Out.replaceAllUsesWith(hgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |+>: SWAP(|+>,|+>) + else if (pssa.isPlus(qubitValues[SwapQubit_1_In])) { + SwapQubit_0_Out.replaceAllUsesWith(SwapQubit_0_In); + SwapQubit_1_Out.replaceAllUsesWith(SwapQubit_1_In); + op->erase(); + return; + } + // second qubit in |->: SWAP(|+>,|->) + else if (pssa.isMinus(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp zgate_on_0 = createSimpleOneBitGate( + "PauliZ", SwapQubit_0_In, SwapQubit_0_Out, builder, loc, op); + SwapQubit_0_Out.replaceAllUsesWith(zgate_on_0->getResult(0)); + + quantum::CustomOp zgate_on_1 = createSimpleOneBitGate( + "PauliZ", SwapQubit_1_In, SwapQubit_1_Out, builder, loc, zgate_on_0); + SwapQubit_1_Out.replaceAllUsesWith(zgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |NON_BASIS>: SWAP(|+>,|NON_BASIS>) + else if (pssa.isOther(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp cnot_on_0_1 = createSimpleTwoBitGate( + "CNOT", SwapQubit_0_In, SwapQubit_1_In, builder, loc, op); + + quantum::CustomOp cnot_on_1_0 = createSimpleTwoBitGate( + "CNOT", cnot_on_0_1->getResult(1), cnot_on_0_1->getResult(0), builder, loc, + cnot_on_0_1); + + SwapQubit_0_Out.replaceAllUsesWith(cnot_on_1_0->getResult(1)); + SwapQubit_1_Out.replaceAllUsesWith(cnot_on_1_0->getResult(0)); + op->erase(); + return; + } + } + + // first qubit in |-> + else if (pssa.isMinus(qubitValues[SwapQubit_0_In])) { + // second qubit in |0>: SWAP(|->,|0>) + if (pssa.isZero(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp hgate_on_0 = + createSimpleOneBitGate("Hadamard", SwapQubit_0_In, builder, loc, op); + + quantum::CustomOp xgate_on_0 = createSimpleOneBitGate( + "PauliX", hgate_on_0->getResult(0), builder, loc, hgate_on_0); + SwapQubit_0_Out.replaceAllUsesWith(xgate_on_0->getResult(0)); + + quantum::CustomOp xgate_on_1 = + createSimpleOneBitGate("PauliX", SwapQubit_1_In, builder, loc, xgate_on_0); + + quantum::CustomOp hgate_on_1 = createSimpleOneBitGate( + "Hadamard", xgate_on_1->getResult(0), builder, loc, xgate_on_1); + SwapQubit_1_Out.replaceAllUsesWith(hgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |1>: SWAP(|->,|1>) + else if (pssa.isOne(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp hgate_on_0 = createSimpleOneBitGate( + "Hadamard", SwapQubit_0_In, SwapQubit_0_Out, builder, loc, op); + SwapQubit_0_Out.replaceAllUsesWith(hgate_on_0->getResult(0)); + + quantum::CustomOp hgate_on_1 = createSimpleOneBitGate( + "Hadamard", SwapQubit_1_In, SwapQubit_1_Out, builder, loc, hgate_on_0); + SwapQubit_1_Out.replaceAllUsesWith(hgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |+>: SWAP(|->,|+>) + else if (pssa.isPlus(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp zgate_on_0 = createSimpleOneBitGate( + "PauliZ", SwapQubit_0_In, SwapQubit_0_Out, builder, loc, op); + SwapQubit_0_Out.replaceAllUsesWith(zgate_on_0->getResult(0)); + + quantum::CustomOp zgate_on_1 = createSimpleOneBitGate( + "PauliZ", SwapQubit_1_In, SwapQubit_1_Out, builder, loc, zgate_on_0); + SwapQubit_1_Out.replaceAllUsesWith(zgate_on_1->getResult(0)); + op->erase(); + return; + } + // second qubit in |->: SWAP(|->,|->) + else if (pssa.isMinus(qubitValues[SwapQubit_1_In])) { + SwapQubit_0_Out.replaceAllUsesWith(SwapQubit_0_In); + SwapQubit_1_Out.replaceAllUsesWith(SwapQubit_1_In); + op->erase(); + return; + } + // second qubit in |NON_BASIS>: SWAP(|->,|NON_BASIS>) + else if (pssa.isOther(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp zgate_on_1 = + createSimpleOneBitGate("PauliZ", SwapQubit_1_In, builder, loc, op); + + quantum::CustomOp cnot_on_0_1 = createSimpleTwoBitGate( + "CNOT", SwapQubit_0_In, zgate_on_1->getResult(0), builder, loc, zgate_on_1); + + quantum::CustomOp cnot_on_1_0 = createSimpleTwoBitGate( + "CNOT", cnot_on_0_1->getResult(1), cnot_on_0_1->getResult(0), builder, loc, + cnot_on_0_1); + + SwapQubit_0_Out.replaceAllUsesWith(cnot_on_1_0->getResult(1)); + SwapQubit_1_Out.replaceAllUsesWith(cnot_on_1_0->getResult(0)); + op->erase(); + return; + } + } + + // first qubit in |NON_BASIS> + else if (pssa.isOther(qubitValues[SwapQubit_0_In])) { + // second qubit in |0>: SWAP(|NON_BASIS>,|0>) + if (pssa.isZero(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp cnot_on_0_1 = createSimpleTwoBitGate( + "CNOT", SwapQubit_0_In, SwapQubit_1_In, builder, loc, op); + + quantum::CustomOp cnot_on_1_0 = createSimpleTwoBitGate( + "CNOT", cnot_on_0_1->getResult(1), cnot_on_0_1->getResult(0), builder, loc, + cnot_on_0_1); + + SwapQubit_0_Out.replaceAllUsesWith(cnot_on_1_0->getResult(1)); + SwapQubit_1_Out.replaceAllUsesWith(cnot_on_1_0->getResult(0)); + op->erase(); + return; + } + // second qubit in |1>: SWAP(|NON_BASIS>,|1>) + else if (pssa.isOne(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp xgate_on_0 = + createSimpleOneBitGate("PauliX", SwapQubit_0_In, builder, loc, op); + + quantum::CustomOp cnot_on_0_1 = createSimpleTwoBitGate( + "CNOT", xgate_on_0->getResult(0), SwapQubit_1_In, builder, loc, xgate_on_0); + + quantum::CustomOp cnot_on_1_0 = createSimpleTwoBitGate( + "CNOT", cnot_on_0_1->getResult(1), cnot_on_0_1->getResult(0), builder, loc, + cnot_on_0_1); + + SwapQubit_0_Out.replaceAllUsesWith(cnot_on_1_0->getResult(1)); + SwapQubit_1_Out.replaceAllUsesWith(cnot_on_1_0->getResult(0)); + op->erase(); + return; + } + // second qubit in |+>: SWAP(|NON_BASIS>,|+>) + else if (pssa.isPlus(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp cnot_on_1_0 = createSimpleTwoBitGate( + "CNOT", SwapQubit_1_In, SwapQubit_0_In, builder, loc, op); + + quantum::CustomOp cnot_on_0_1 = createSimpleTwoBitGate( + "CNOT", cnot_on_1_0->getResult(1), cnot_on_1_0->getResult(0), builder, loc, + cnot_on_1_0); + + SwapQubit_0_Out.replaceAllUsesWith(cnot_on_0_1->getResult(0)); + SwapQubit_1_Out.replaceAllUsesWith(cnot_on_0_1->getResult(1)); + op->erase(); + return; + } + // second qubit in |->: SWAP(|NON_BASIS>,|->) + else if (pssa.isMinus(qubitValues[SwapQubit_1_In])) { + quantum::CustomOp zgate_on_0 = + createSimpleOneBitGate("PauliZ", SwapQubit_0_In, builder, loc, op); + + quantum::CustomOp cnot_on_1_0 = createSimpleTwoBitGate( + "CNOT", SwapQubit_1_In, zgate_on_0->getResult(0), builder, loc, zgate_on_0); + + quantum::CustomOp cnot_on_0_1 = createSimpleTwoBitGate( + "CNOT", cnot_on_1_0->getResult(1), cnot_on_1_0->getResult(0), builder, loc, + cnot_on_1_0); + + SwapQubit_0_Out.replaceAllUsesWith(cnot_on_0_1->getResult(0)); + SwapQubit_1_Out.replaceAllUsesWith(cnot_on_0_1->getResult(1)); + op->erase(); + return; + } + } + }); + } +}; + +std::unique_ptr createDisentangleSWAPPass() +{ + return std::make_unique(); +} + +} // namespace catalyst diff --git a/mlir/lib/Quantum/Transforms/PropagateSimpleStatesAnalysis.hpp b/mlir/lib/Quantum/Transforms/PropagateSimpleStatesAnalysis.hpp new file mode 100644 index 0000000000..02efa45246 --- /dev/null +++ b/mlir/lib/Quantum/Transforms/PropagateSimpleStatesAnalysis.hpp @@ -0,0 +1,241 @@ +// 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. + +// This algorithm is taken from https://arxiv.org/pdf/2012.07711, figure 5 + +#pragma once + +#include + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/Pass.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/Support/Debug.h" + +#include "Catalyst/IR/CatalystDialect.h" +#include "Quantum/IR/QuantumOps.h" + +using namespace mlir; +using namespace catalyst; + +namespace catalyst { + +// The six Pauli eigenstates +enum class QubitState { + ZERO, + ONE, + PLUS, + MINUS, + LEFT, + RIGHT, + NOT_A_BASIS, +}; + +// {input state : {gate, output state}} +static std::map> QubitTransitions = { + {QubitState::ZERO, + { + {"Hadamard", QubitState::PLUS}, + {"PauliX", QubitState::ONE}, + {"PauliY", QubitState::ONE}, + {"PauliZ", QubitState::ZERO}, + }}, + + {QubitState::ONE, + { + {"Hadamard", QubitState::MINUS}, + {"PauliX", QubitState::ZERO}, + {"PauliY", QubitState::ZERO}, + {"PauliZ", QubitState::ONE}, + }}, + + {QubitState::PLUS, + { + {"Hadamard", QubitState::ZERO}, + {"PauliX", QubitState::PLUS}, + {"PauliY", QubitState::MINUS}, + {"PauliZ", QubitState::MINUS}, + {"S", QubitState::LEFT}, + }}, + + {QubitState::MINUS, + { + {"Hadamard", QubitState::ONE}, + {"PauliX", QubitState::MINUS}, + {"PauliY", QubitState::PLUS}, + {"PauliZ", QubitState::PLUS}, + {"S", QubitState::RIGHT}, + }}, + + {QubitState::LEFT, + { + {"Hadamard", QubitState::RIGHT}, + {"PauliX", QubitState::RIGHT}, + {"PauliY", QubitState::LEFT}, + {"PauliZ", QubitState::RIGHT}, + {"S+", QubitState::PLUS}, + }}, + + {QubitState::RIGHT, + { + {"Hadamard", QubitState::LEFT}, + {"PauliX", QubitState::LEFT}, + {"PauliY", QubitState::RIGHT}, + {"PauliZ", QubitState::LEFT}, + {"S+", QubitState::MINUS}, + }}, +}; + +class PropagateSimpleStatesAnalysis { + public: + PropagateSimpleStatesAnalysis(Operation *target) + { + // `target` is a qnode function + // We restrict the analysis to gates at the top-level body of the function + // This is so that gates inside nested regions, like control flows, are not valid targets + // e.g. + // func.func circuit() { + // %0 = |0> + // %1 = scf.if (condition) { + // true: Hadamard + // false: PauliZ + // } + // } + // since depending on the control flow path, %1 can be in multiple different states + + // Two kinds of operations produce qubit values: extract ops and custom ops + // For extract ops, if its operand is a alloc op directly, then it's a starting qubit and is + // in |0>. Then the FSM propagates the states through the custom op gates + + target->walk([&](quantum::ExtractOp op) { + // Do not analyze any operations in invalid ops or regions. + // The only valid ops are the custom/extract ops whose immediate parent is the qnode + // function. With this, we skip over regions like control flow. + if (!isImmediateChild(op, target)) { + return; + } + + // starting qubits are in |0> + // We restrict "starting qubits" to those who are extracted immediately from alloc ops + if (isa(op.getQreg().getDefiningOp())) { + qubitValues[op.getQubit()] = QubitState::ZERO; + return; + } + }); + + target->walk([&](quantum::CustomOp op) { + if (!isImmediateChild(op, target)) { + return; + } + + if (op->getNumResults() != 1) { + // restrict to single-qubit gates + return; + } + + Value res = op->getResult(0); + + // takes in parameters other than the parent qubit + // e.g. the rotation angle + // must be NOT_A_BASIS! + if (op->getNumOperands() != 1) { + qubitValues[res] = QubitState::NOT_A_BASIS; + return; + } + + // get state from parent and gate + StringRef gate = op.getGateName(); + Value parent = op.getInQubits()[0]; + + // Unknown parent state, child state is thus also unknown + if (!qubitValues.contains(parent) || isOther(qubitValues[parent])) { + qubitValues[res] = QubitState::NOT_A_BASIS; + return; + } + + // Identity preserves parent state + if (gate == "Identity") { + qubitValues[res] = qubitValues[parent]; + return; + } + + // A valid FSM transition gate + // Special treatment for S+ gate from |L> and |R> + if (gate == "S" && op->hasAttr("adjoint")) { + gate = "S+"; + } + if (QubitTransitions[qubitValues[parent]].count(gate) == 1) { + qubitValues[res] = QubitTransitions[qubitValues[parent]][gate]; + } + // Not a valid FSM transition gate + else { + qubitValues[res] = QubitState::NOT_A_BASIS; + } + return; + }); + } + + llvm::DenseMap getQubitValues() { return qubitValues; } + + // Function to convert enum values to strings + static std::string QubitState2String(QubitState state) + { + switch (state) { + case QubitState::ZERO: + return "ZERO"; + case QubitState::ONE: + return "ONE"; + case QubitState::PLUS: + return "PLUS"; + case QubitState::MINUS: + return "MINUS"; + case QubitState::LEFT: + return "LEFT"; + case QubitState::RIGHT: + return "RIGHT"; + case QubitState::NOT_A_BASIS: + return "NOT_A_BASIS"; + } + } + + bool isZero(QubitState qs) { return qs == QubitState::ZERO; } + + bool isOne(QubitState qs) { return qs == QubitState::ONE; } + + bool isPlus(QubitState qs) { return qs == QubitState::PLUS; } + + bool isMinus(QubitState qs) { return qs == QubitState::MINUS; } + + bool isLeft(QubitState qs) { return qs == QubitState::LEFT; } + + bool isRight(QubitState qs) { return qs == QubitState::RIGHT; } + + bool isOther(QubitState qs) { return qs == QubitState::NOT_A_BASIS; } + + private: + // The object `qubitValues` contains all the analysis results + // It is a map of the form + // + llvm::DenseMap qubitValues; + + bool isImmediateChild(Operation *op, Operation *ancestor) + { + // returns true if op is an immediate child of ancestor, + // with no extra operations in between + return op->getParentOp() == ancestor; + } +}; + +} // namespace catalyst diff --git a/mlir/test/Quantum/DisentangleCNOTTest.mlir b/mlir/test/Quantum/DisentangleCNOTTest.mlir new file mode 100644 index 0000000000..7d94abb697 --- /dev/null +++ b/mlir/test/Quantum/DisentangleCNOTTest.mlir @@ -0,0 +1,217 @@ +// 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 %s --pass-pipeline="builtin.module(func.func(disentangle-CNOT))" --split-input-file --verify-diagnostics | FileCheck %s + + +// Explicit unit tests for all CNOT disentangling table entries +// Note that these tests reuse qubits for conciseness. + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %cst = arith.constant 1.230000e+00 : f64 + %_ = quantum.alloc( 2) : !quantum.reg + %ZERO_0 = quantum.extract %_[ 0] : !quantum.reg -> !quantum.bit + %ZERO_1 = quantum.extract %_[ 1] : !quantum.reg -> !quantum.bit + %ONE_0 = quantum.custom "PauliX"() %ZERO_0 : !quantum.bit + %ONE_1 = quantum.custom "PauliX"() %ZERO_1 : !quantum.bit + %PLUS_0 = quantum.custom "Hadamard"() %ZERO_0 : !quantum.bit + %PLUS_1 = quantum.custom "Hadamard"() %ZERO_1 : !quantum.bit + %MINUS_0 = quantum.custom "Hadamard"() %ONE_0 : !quantum.bit + %MINUS_1 = quantum.custom "Hadamard"() %ONE_1 : !quantum.bit + %OTHERS_0 = quantum.custom "RX"(%cst) %ZERO_0 : !quantum.bit + %OTHERS_1 = quantum.custom "RX"(%cst) %ZERO_1 : !quantum.bit + // CHECK: [[ZERO_0:%.+]] = quantum.extract {{%.+}}[ 0] : !quantum.reg -> !quantum.bit + // CHECK: [[ZERO_1:%.+]] = quantum.extract {{%.+}}[ 1] : !quantum.reg -> !quantum.bit + // CHECK: [[ONE_0:%.+]] = quantum.custom "PauliX"() [[ZERO_0]] : !quantum.bit + // CHECK: [[ONE_1:%.+]] = quantum.custom "PauliX"() [[ZERO_1]] : !quantum.bit + // CHECK: [[PLUS_0:%.+]] = quantum.custom "Hadamard"() [[ZERO_0]] : !quantum.bit + // CHECK: [[PLUS_1:%.+]] = quantum.custom "Hadamard"() [[ZERO_1]] : !quantum.bit + // CHECK: [[MINUS_0:%.+]] = quantum.custom "Hadamard"() [[ONE_0]] : !quantum.bit + // CHECK: [[MINUS_1:%.+]] = quantum.custom "Hadamard"() [[ONE_1]] : !quantum.bit + // CHECK: [[OTHERS_0:%.+]] = quantum.custom "RX"({{%.+}}) [[ZERO_0]] : !quantum.bit + // CHECK: [[OTHERS_1:%.+]] = quantum.custom "RX"({{%.+}}) [[ZERO_1]] : !quantum.bit + + + %0:2 = quantum.custom "CNOT"() %ZERO_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_0:2 = quantum.custom "CRZ"(%cst) %0#0, %0#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ZERO_0]], [[OTHERS_1]] : !quantum.bit, !quantum.bit + + + %1:2 = quantum.custom "CNOT"() %ZERO_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_1:2 = quantum.custom "CRZ"(%cst) %1#0, %1#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ZERO_0]], [[PLUS_1]] : !quantum.bit, !quantum.bit + + + %2:2 = quantum.custom "CNOT"() %ZERO_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_2:2 = quantum.custom "CRZ"(%cst) %2#0, %2#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ZERO_0]], [[MINUS_1]] : !quantum.bit, !quantum.bit + + + %3:2 = quantum.custom "CNOT"() %ZERO_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_3:2 = quantum.custom "CRZ"(%cst) %3#0, %3#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ZERO_0]], [[ZERO_1]] : !quantum.bit, !quantum.bit + + + %4:2 = quantum.custom "CNOT"() %ZERO_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_4:2 = quantum.custom "CRZ"(%cst) %4#0, %4#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ZERO_0]], [[ONE_1]] : !quantum.bit, !quantum.bit + + + %5:2 = quantum.custom "CNOT"() %ONE_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_5:2 = quantum.custom "CRZ"(%cst) %5#0, %5#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: [[mid_5:%.+]] = quantum.custom "PauliX"() [[OTHERS_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ONE_0]], [[mid_5]] : !quantum.bit, !quantum.bit + + + %6:2 = quantum.custom "CNOT"() %ONE_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_6:2 = quantum.custom "CRZ"(%cst) %6#0, %6#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ONE_0]], [[PLUS_1]] : !quantum.bit, !quantum.bit + + + %7:2 = quantum.custom "CNOT"() %ONE_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_7:2 = quantum.custom "CRZ"(%cst) %7#0, %7#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ONE_0]], [[MINUS_1]] : !quantum.bit, !quantum.bit + + + %8:2 = quantum.custom "CNOT"() %ONE_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_8:2 = quantum.custom "CRZ"(%cst) %8#0, %8#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: [[mid_8:%.+]] = quantum.custom "PauliX"() [[ZERO_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ONE_0]], [[mid_8]] : !quantum.bit, !quantum.bit + + + %9:2 = quantum.custom "CNOT"() %ONE_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_9:2 = quantum.custom "CRZ"(%cst) %9#0, %9#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: [[mid_9:%.+]] = quantum.custom "PauliX"() [[ONE_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ONE_0]], [[mid_9]] : !quantum.bit, !quantum.bit + + + %10:2 = quantum.custom "CNOT"() %PLUS_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_10:2 = quantum.custom "CRZ"(%cst) %10#0, %10#1 : !quantum.bit, !quantum.bit + // CHECK: [[_10:%.+]]:2 = quantum.custom "CNOT"() [[PLUS_0]], [[OTHERS_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[_10]]#0, [[_10]]#1 : !quantum.bit, !quantum.bit + + + %11:2 = quantum.custom "CNOT"() %PLUS_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_11:2 = quantum.custom "CRZ"(%cst) %11#0, %11#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[PLUS_0]], [[PLUS_1]] : !quantum.bit, !quantum.bit + + + %12:2 = quantum.custom "CNOT"() %PLUS_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_12:2 = quantum.custom "CRZ"(%cst) %12#0, %12#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: [[mid_12:%.+]] = quantum.custom "PauliZ"() [[PLUS_0]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[mid_12]], [[MINUS_1]] : !quantum.bit, !quantum.bit + + + %13:2 = quantum.custom "CNOT"() %PLUS_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_13:2 = quantum.custom "CRZ"(%cst) %13#0, %13#1 : !quantum.bit, !quantum.bit + // CHECK: [[_13:%.+]]:2 = quantum.custom "CNOT"() [[PLUS_0]], [[ZERO_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[_13]]#0, [[_13]]#1 : !quantum.bit, !quantum.bit + + + %14:2 = quantum.custom "CNOT"() %PLUS_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_14:2 = quantum.custom "CRZ"(%cst) %14#0, %14#1 : !quantum.bit, !quantum.bit + // CHECK: [[_14:%.+]]:2 = quantum.custom "CNOT"() [[PLUS_0]], [[ONE_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[_14]]#0, [[_14]]#1 : !quantum.bit, !quantum.bit + + + %15:2 = quantum.custom "CNOT"() %MINUS_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_15:2 = quantum.custom "CRZ"(%cst) %15#0, %15#1 : !quantum.bit, !quantum.bit + // CHECK: [[_15:%.+]]:2 = quantum.custom "CNOT"() [[MINUS_0]], [[OTHERS_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[_15]]#0, [[_15]]#1 : !quantum.bit, !quantum.bit + + + %16:2 = quantum.custom "CNOT"() %MINUS_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_16:2 = quantum.custom "CRZ"(%cst) %16#0, %16#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[MINUS_0]], [[PLUS_1]] : !quantum.bit, !quantum.bit + + + %17:2 = quantum.custom "CNOT"() %MINUS_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_17:2 = quantum.custom "CRZ"(%cst) %17#0, %17#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: [[mid_17:%.+]] = quantum.custom "PauliZ"() [[MINUS_0]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[mid_17]], [[MINUS_1]] : !quantum.bit, !quantum.bit + + + %18:2 = quantum.custom "CNOT"() %MINUS_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_18:2 = quantum.custom "CRZ"(%cst) %18#0, %18#1 : !quantum.bit, !quantum.bit + // CHECK: [[_18:%.+]]:2 = quantum.custom "CNOT"() [[MINUS_0]], [[ZERO_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[_18]]#0, [[_18]]#1 : !quantum.bit, !quantum.bit + + + %19:2 = quantum.custom "CNOT"() %MINUS_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_19:2 = quantum.custom "CRZ"(%cst) %19#0, %19#1 : !quantum.bit, !quantum.bit + // CHECK: [[_19:%.+]]:2 = quantum.custom "CNOT"() [[MINUS_0]], [[ONE_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[_19]]#0, [[_19]]#1 : !quantum.bit, !quantum.bit + + + %20:2 = quantum.custom "CNOT"() %OTHERS_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_20:2 = quantum.custom "CRZ"(%cst) %20#0, %20#1 : !quantum.bit, !quantum.bit + // CHECK: [[_20:%.+]]:2 = quantum.custom "CNOT"() [[OTHERS_0]], [[OTHERS_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[_20]]#0, [[_20]]#1 : !quantum.bit, !quantum.bit + + + %21:2 = quantum.custom "CNOT"() %OTHERS_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_21:2 = quantum.custom "CRZ"(%cst) %21#0, %21#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[OTHERS_0]], [[PLUS_1]] : !quantum.bit, !quantum.bit + + + %22:2 = quantum.custom "CNOT"() %OTHERS_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_22:2 = quantum.custom "CRZ"(%cst) %22#0, %22#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "CNOT" + // CHECK: [[mid_22:%.+]] = quantum.custom "PauliZ"() [[OTHERS_0]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[mid_22]], [[MINUS_1]] : !quantum.bit, !quantum.bit + + + %23:2 = quantum.custom "CNOT"() %OTHERS_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_23:2 = quantum.custom "CRZ"(%cst) %23#0, %23#1 : !quantum.bit, !quantum.bit + // CHECK: [[_23:%.+]]:2 = quantum.custom "CNOT"() [[OTHERS_0]], [[ZERO_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[_23]]#0, [[_23]]#1 : !quantum.bit, !quantum.bit + + + %24:2 = quantum.custom "CNOT"() %OTHERS_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_24:2 = quantum.custom "CRZ"(%cst) %24#0, %24#1 : !quantum.bit, !quantum.bit + // CHECK: [[_24:%.+]]:2 = quantum.custom "CNOT"() [[OTHERS_0]], [[ONE_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[_24]]#0, [[_24]]#1 : !quantum.bit, !quantum.bit + + + %true = arith.constant true + // CHECK: scf.if + %scf_res = scf.if %true -> !quantum.bit { + %ZERO_0_in_if = quantum.extract %_[ 0] : !quantum.reg -> !quantum.bit + + // CHECK: quantum.custom "CNOT" + %25:2 = quantum.custom "CNOT"() %ZERO_0_in_if, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_25:2 = quantum.custom "CRZ"(%cst) %25#0, %25#1 : !quantum.bit, !quantum.bit + scf.yield %user_25#0 : !quantum.bit + } else { + scf.yield %ZERO_0 : !quantum.bit + } + + return %cst : f64 + } diff --git a/mlir/test/Quantum/DisentangleSWAPTest.mlir b/mlir/test/Quantum/DisentangleSWAPTest.mlir new file mode 100644 index 0000000000..d8f6de2924 --- /dev/null +++ b/mlir/test/Quantum/DisentangleSWAPTest.mlir @@ -0,0 +1,226 @@ +// 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 %s --pass-pipeline="builtin.module(func.func(disentangle-SWAP))" --split-input-file --verify-diagnostics | FileCheck %s + + +// Explicit unit tests for all SWAP disentangling table entries +// Note that these tests reuse qubits for conciseness. + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %cst = arith.constant 3.140000e+00 : f64 + %_ = quantum.alloc( 2) : !quantum.reg + %ZERO_0 = quantum.extract %_[ 0] : !quantum.reg -> !quantum.bit + %ZERO_1 = quantum.extract %_[ 1] : !quantum.reg -> !quantum.bit + %ONE_0 = quantum.custom "PauliX"() %ZERO_0 : !quantum.bit + %ONE_1 = quantum.custom "PauliX"() %ZERO_1 : !quantum.bit + %PLUS_0 = quantum.custom "Hadamard"() %ZERO_0 : !quantum.bit + %PLUS_1 = quantum.custom "Hadamard"() %ZERO_1 : !quantum.bit + %MINUS_0 = quantum.custom "Hadamard"() %ONE_0 : !quantum.bit + %MINUS_1 = quantum.custom "Hadamard"() %ONE_1 : !quantum.bit + %OTHERS_0 = quantum.custom "RX"(%cst) %ZERO_0 : !quantum.bit + %OTHERS_1 = quantum.custom "RX"(%cst) %ZERO_1 : !quantum.bit + // CHECK: [[ZERO_0:%.+]] = quantum.extract {{%.+}}[ 0] : !quantum.reg -> !quantum.bit + // CHECK: [[ZERO_1:%.+]] = quantum.extract {{%.+}}[ 1] : !quantum.reg -> !quantum.bit + // CHECK: [[ONE_0:%.+]] = quantum.custom "PauliX"() [[ZERO_0]] : !quantum.bit + // CHECK: [[ONE_1:%.+]] = quantum.custom "PauliX"() [[ZERO_1]] : !quantum.bit + // CHECK: [[PLUS_0:%.+]] = quantum.custom "Hadamard"() [[ZERO_0]] : !quantum.bit + // CHECK: [[PLUS_1:%.+]] = quantum.custom "Hadamard"() [[ZERO_1]] : !quantum.bit + // CHECK: [[MINUS_0:%.+]] = quantum.custom "Hadamard"() [[ONE_0]] : !quantum.bit + // CHECK: [[MINUS_1:%.+]] = quantum.custom "Hadamard"() [[ONE_1]] : !quantum.bit + // CHECK: [[OTHERS_0:%.+]] = quantum.custom "RX"({{%.+}}) [[ZERO_0]] : !quantum.bit + // CHECK: [[OTHERS_1:%.+]] = quantum.custom "RX"({{%.+}}) [[ZERO_1]] : !quantum.bit + + + %0:2 = quantum.custom "SWAP"() %ZERO_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_0:2 = quantum.custom "CRZ"(%cst) %0#0, %0#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]]:2 = quantum.custom "CNOT"() [[OTHERS_1]], [[ZERO_0]] : !quantum.bit, !quantum.bit + // CHECK: [[b:%.+]]:2 = quantum.custom "CNOT"() [[a]]#1, [[a]]#0 : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[b]]#0, [[b]]#1 : !quantum.bit, !quantum.bit + + %1:2 = quantum.custom "SWAP"() %ZERO_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_1:2 = quantum.custom "CRZ"(%cst) %1#0, %1#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ZERO_0]], [[ZERO_1]] : !quantum.bit, !quantum.bit + + %2:2 = quantum.custom "SWAP"() %ZERO_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_2:2 = quantum.custom "CRZ"(%cst) %2#0, %2#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "PauliX"() [[ZERO_0]] : !quantum.bit + // CHECK: [[b:%.+]] = quantum.custom "PauliX"() [[ONE_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a]], [[b]] : !quantum.bit, !quantum.bit + + %3:2 = quantum.custom "SWAP"() %ZERO_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_3:2 = quantum.custom "CRZ"(%cst) %3#0, %3#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "Hadamard"() [[ZERO_0]] : !quantum.bit + // CHECK: [[b:%.+]] = quantum.custom "Hadamard"() [[PLUS_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a]], [[b]] : !quantum.bit, !quantum.bit + + %4:2 = quantum.custom "SWAP"() %ZERO_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_4:2 = quantum.custom "CRZ"(%cst) %4#0, %4#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a0:%.+]] = quantum.custom "PauliX"() [[ZERO_0]] : !quantum.bit + // CHECK: [[a1:%.+]] = quantum.custom "Hadamard"() [[a0]] : !quantum.bit + // CHECK: [[b0:%.+]] = quantum.custom "Hadamard"() [[MINUS_1]] : !quantum.bit + // CHECK: [[b1:%.+]] = quantum.custom "PauliX"() [[b0]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a1]], [[b1]] : !quantum.bit, !quantum.bit + + %5:2 = quantum.custom "SWAP"() %ONE_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_5:2 = quantum.custom "CRZ"(%cst) %5#0, %5#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "PauliX"() [[OTHERS_1]] : !quantum.bit + // CHECK: [[b:%.+]]:2 = quantum.custom "CNOT"() [[a]], [[ONE_0]] : !quantum.bit, !quantum.bit + // CHECK: [[c:%.+]]:2 = quantum.custom "CNOT"() [[b]]#1, [[b]]#0 : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[c]]#0, [[c]]#1 : !quantum.bit, !quantum.bit + + %6:2 = quantum.custom "SWAP"() %ONE_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_6:2 = quantum.custom "CRZ"(%cst) %6#0, %6#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "PauliX"() [[ONE_0]] : !quantum.bit + // CHECK: [[b:%.+]] = quantum.custom "PauliX"() [[ZERO_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a]], [[b]] : !quantum.bit, !quantum.bit + + %7:2 = quantum.custom "SWAP"() %ONE_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_7:2 = quantum.custom "CRZ"(%cst) %7#0, %7#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[ONE_0]], [[ONE_1]] : !quantum.bit, !quantum.bit + + %8:2 = quantum.custom "SWAP"() %ONE_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_8:2 = quantum.custom "CRZ"(%cst) %8#0, %8#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a0:%.+]] = quantum.custom "PauliX"() [[ONE_0]] : !quantum.bit + // CHECK: [[a1:%.+]] = quantum.custom "Hadamard"() [[a0]] : !quantum.bit + // CHECK: [[b0:%.+]] = quantum.custom "Hadamard"() [[PLUS_1]] : !quantum.bit + // CHECK: [[b1:%.+]] = quantum.custom "PauliX"() [[b0]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a1]], [[b1]] : !quantum.bit, !quantum.bit + + + %9:2 = quantum.custom "SWAP"() %ONE_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_9:2 = quantum.custom "CRZ"(%cst) %9#0, %9#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "Hadamard"() [[ONE_0]] : !quantum.bit + // CHECK: [[b:%.+]] = quantum.custom "Hadamard"() [[MINUS_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a]], [[b]] : !quantum.bit, !quantum.bit + + %10:2 = quantum.custom "SWAP"() %PLUS_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_10:2 = quantum.custom "CRZ"(%cst) %10#0, %10#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]]:2 = quantum.custom "CNOT"() [[PLUS_0]], [[OTHERS_1]] : !quantum.bit, !quantum.bit + // CHECK: [[b:%.+]]:2 = quantum.custom "CNOT"() [[a]]#1, [[a]]#0 : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[b]]#1, [[b]]#0 : !quantum.bit, !quantum.bit + + %11:2 = quantum.custom "SWAP"() %PLUS_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_11:2 = quantum.custom "CRZ"(%cst) %11#0, %11#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "Hadamard"() [[PLUS_0]] : !quantum.bit + // CHECK: [[b:%.+]] = quantum.custom "Hadamard"() [[ZERO_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a]], [[b]] : !quantum.bit, !quantum.bit + + %12:2 = quantum.custom "SWAP"() %PLUS_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_12:2 = quantum.custom "CRZ"(%cst) %12#0, %12#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a0:%.+]] = quantum.custom "Hadamard"() [[PLUS_0]] : !quantum.bit + // CHECK: [[a1:%.+]] = quantum.custom "PauliX"() [[a0]] : !quantum.bit + // CHECK: [[b0:%.+]] = quantum.custom "PauliX"() [[ONE_1]] : !quantum.bit + // CHECK: [[b1:%.+]] = quantum.custom "Hadamard"() [[b0]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a1]], [[b1]] : !quantum.bit, !quantum.bit + + %13:2 = quantum.custom "SWAP"() %PLUS_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_13:2 = quantum.custom "CRZ"(%cst) %13#0, %13#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[PLUS_0]], [[PLUS_1]] : !quantum.bit, !quantum.bit + + %14:2 = quantum.custom "SWAP"() %PLUS_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_14:2 = quantum.custom "CRZ"(%cst) %14#0, %14#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "PauliZ"() [[PLUS_0]] : !quantum.bit + // CHECK: [[b:%.+]] = quantum.custom "PauliZ"() [[MINUS_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a]], [[b]] : !quantum.bit, !quantum.bit + + %15:2 = quantum.custom "SWAP"() %MINUS_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_15:2 = quantum.custom "CRZ"(%cst) %15#0, %15#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "PauliZ"() [[OTHERS_1]] : !quantum.bit + // CHECK: [[b:%.+]]:2 = quantum.custom "CNOT"() [[MINUS_0]], [[a]] : !quantum.bit, !quantum.bit + // CHECK: [[c:%.+]]:2 = quantum.custom "CNOT"() [[b]]#1, [[b]]#0 : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[c]]#1, [[c]]#0 : !quantum.bit, !quantum.bit + + %16:2 = quantum.custom "SWAP"() %MINUS_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_16:2 = quantum.custom "CRZ"(%cst) %16#0, %16#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a0:%.+]] = quantum.custom "Hadamard"() [[MINUS_0]] : !quantum.bit + // CHECK: [[a1:%.+]] = quantum.custom "PauliX"() [[a0]] : !quantum.bit + // CHECK: [[b0:%.+]] = quantum.custom "PauliX"() [[ZERO_1]] : !quantum.bit + // CHECK: [[b1:%.+]] = quantum.custom "Hadamard"() [[b0]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a1]], [[b1]] : !quantum.bit, !quantum.bit + + %17:2 = quantum.custom "SWAP"() %MINUS_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_17:2 = quantum.custom "CRZ"(%cst) %17#0, %17#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "Hadamard"() [[MINUS_0]] : !quantum.bit + // CHECK: [[b:%.+]] = quantum.custom "Hadamard"() [[ONE_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a]], [[b]] : !quantum.bit, !quantum.bit + + %18:2 = quantum.custom "SWAP"() %MINUS_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_18:2 = quantum.custom "CRZ"(%cst) %18#0, %18#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "PauliZ"() [[MINUS_0]] : !quantum.bit + // CHECK: [[b:%.+]] = quantum.custom "PauliZ"() [[PLUS_1]] : !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a]], [[b]] : !quantum.bit, !quantum.bit + + %19:2 = quantum.custom "SWAP"() %MINUS_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_19:2 = quantum.custom "CRZ"(%cst) %19#0, %19#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[MINUS_0]], [[MINUS_1]] : !quantum.bit, !quantum.bit + + %20:2 = quantum.custom "SWAP"() %OTHERS_0, %OTHERS_1 : !quantum.bit, !quantum.bit + %user_20:2 = quantum.custom "CRZ"(%cst) %20#0, %20#1 : !quantum.bit, !quantum.bit + // CHECK: [[a:%.+]]:2 = quantum.custom "SWAP"() [[OTHERS_0]], [[OTHERS_1]] : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[a]]#0, [[a]]#1 : !quantum.bit, !quantum.bit + + %21:2 = quantum.custom "SWAP"() %OTHERS_0, %ZERO_1 : !quantum.bit, !quantum.bit + %user_21:2 = quantum.custom "CRZ"(%cst) %21#0, %21#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]]:2 = quantum.custom "CNOT"() [[OTHERS_0]], [[ZERO_1]] : !quantum.bit, !quantum.bit + // CHECK: [[b:%.+]]:2 = quantum.custom "CNOT"() [[a]]#1, [[a]]#0 : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[b]]#1, [[b]]#0 : !quantum.bit, !quantum.bit + + %22:2 = quantum.custom "SWAP"() %OTHERS_0, %ONE_1 : !quantum.bit, !quantum.bit + %user_22:2 = quantum.custom "CRZ"(%cst) %22#0, %22#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "PauliX"() [[OTHERS_0]] : !quantum.bit + // CHECK: [[b:%.+]]:2 = quantum.custom "CNOT"() [[a]], [[ONE_1]] : !quantum.bit, !quantum.bit + // CHECK: [[c:%.+]]:2 = quantum.custom "CNOT"() [[b]]#1, [[b]]#0 : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[c]]#1, [[c]]#0 : !quantum.bit, !quantum.bit + + %23:2 = quantum.custom "SWAP"() %OTHERS_0, %PLUS_1 : !quantum.bit, !quantum.bit + %user_23:2 = quantum.custom "CRZ"(%cst) %23#0, %23#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]]:2 = quantum.custom "CNOT"() [[PLUS_1]], [[OTHERS_0]] : !quantum.bit, !quantum.bit + // CHECK: [[b:%.+]]:2 = quantum.custom "CNOT"() [[a]]#1, [[a]]#0 : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[b]]#0, [[b]]#1 : !quantum.bit, !quantum.bit + + %24:2 = quantum.custom "SWAP"() %OTHERS_0, %MINUS_1 : !quantum.bit, !quantum.bit + %user_24:2 = quantum.custom "CRZ"(%cst) %24#0, %24#1 : !quantum.bit, !quantum.bit + // CHECK-NOT: quantum.custom "SWAP" + // CHECK: [[a:%.+]] = quantum.custom "PauliZ"() [[OTHERS_0]] : !quantum.bit + // CHECK: [[b:%.+]]:2 = quantum.custom "CNOT"() [[MINUS_1]], [[a]] : !quantum.bit, !quantum.bit + // CHECK: [[c:%.+]]:2 = quantum.custom "CNOT"() [[b]]#1, [[b]]#0 : !quantum.bit, !quantum.bit + // CHECK: {{%.+}} = quantum.custom "CRZ"({{%.+}}) [[c]]#0, [[c]]#1 : !quantum.bit, !quantum.bit + + return %cst : f64 + } diff --git a/mlir/test/Quantum/PropagateSimpleStatesTest.mlir b/mlir/test/Quantum/PropagateSimpleStatesTest.mlir new file mode 100644 index 0000000000..64915b3a67 --- /dev/null +++ b/mlir/test/Quantum/PropagateSimpleStatesTest.mlir @@ -0,0 +1,294 @@ +// 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 %s --pass-pipeline="builtin.module(func.func(disentangle-CNOT{emit-FSM-state-remark=true}))" --split-input-file --verify-diagnostics | FileCheck %s + + +// Basic test with an actual circuit that has every state in the standard FSM. + +// CHECK: func.func private @circuit() + func.func private @circuit() -> tensor<8xcomplex> { + %0 = quantum.alloc( 3) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %out_qubits = quantum.custom "Hadamard"() %1 : !quantum.bit +// expected-remark@above {{PLUS}} + %out_qubits_0 = quantum.custom "PauliZ"() %out_qubits : !quantum.bit +// expected-remark@above {{MINUS}} + %identity = quantum.custom "Identity"() %out_qubits_0 : !quantum.bit +// expected-remark@above {{MINUS}} + %out_qubits_1 = quantum.custom "S"() %identity : !quantum.bit +// expected-remark@above {{RIGHT}} + %out_qubits_2 = quantum.custom "PauliY"() %out_qubits_1 : !quantum.bit +// expected-remark@above {{RIGHT}} + %out_qubits_3 = quantum.custom "PauliX"() %out_qubits_2 : !quantum.bit +// expected-remark@above {{LEFT}} + %out_qubits_4 = quantum.custom "S"() %out_qubits_3 {adjoint} : !quantum.bit +// expected-remark@above {{PLUS}} + %2 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %out_qubits_5 = quantum.custom "Hadamard"() %2 : !quantum.bit +// expected-remark@above {{PLUS}} + %out_qubits_6 = quantum.custom "PauliY"() %out_qubits_5 : !quantum.bit +// expected-remark@above {{MINUS}} + %out_qubits_7:2 = quantum.custom "CNOT"() %out_qubits_4, %out_qubits_6 : !quantum.bit, !quantum.bit + %3 = quantum.extract %0[ 2] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %out_qubits_8 = quantum.custom "PauliX"() %3 : !quantum.bit +// expected-remark@above {{ONE}} + %out_qubits_10:2 = quantum.custom "CNOT"() %out_qubits_7#1, %out_qubits_8 : !quantum.bit, !quantum.bit + %4 = quantum.compbasis %out_qubits_7#0, %out_qubits_10#0, %out_qubits_10#1 : !quantum.obs + %5 = quantum.state %4 : tensor<8xcomplex> + return %5 : tensor<8xcomplex> + } + + +// ----- + + +// Test for gates outside the FSM: T + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %cst = arith.constant 3.140000e+00 : f64 + %0 = quantum.alloc( 1) : !quantum.reg + %T = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %T_out = quantum.custom "T"() %T : !quantum.bit +// expected-remark@above {{NOT_A_BASIS}} + return %cst : f64 + } + + + +// ----- + + +// Test for gates outside the FSM: RX, RY, RZ + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %cst = arith.constant 3.140000e+00 : f64 + %0 = quantum.alloc( 3) : !quantum.reg + %RX = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %RX_out = quantum.custom "RX"(%cst) %RX : !quantum.bit +// expected-remark@above {{NOT_A_BASIS}} + %RY = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %RY_out = quantum.custom "RY"(%cst) %RY : !quantum.bit +// expected-remark@above {{NOT_A_BASIS}} + %RZ = quantum.extract %0[ 2] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %RZ_out = quantum.custom "RZ"(%cst) %RZ : !quantum.bit +// expected-remark@above {{NOT_A_BASIS}} + %failure_ahead = quantum.custom "Hadamard"() %RZ_out : !quantum.bit +// expected-remark@above {{NOT_A_BASIS}} + return %cst : f64 + } + + +// ----- + + +// Test when known states enter unknown gates + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %cst = arith.constant 3.140000e+00 : f64 + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %2 = quantum.custom "PauliX"() %1 : !quantum.bit +// expected-remark@above {{ONE}} + %T_out = quantum.custom "RY"(%cst) %2 : !quantum.bit +// expected-remark@above {{NOT_A_BASIS}} + return %cst : f64 + } + + +// ----- + + +// Explicit unit tests for all the FSM transition edges from |0> +// Note that these explicit edge tests reuse qubits for conciseness. + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + + %2 = quantum.custom "PauliX"() %1 : !quantum.bit +// expected-remark@above {{ONE}} + %3 = quantum.custom "PauliY"() %1 : !quantum.bit +// expected-remark@above {{ONE}} + %4 = quantum.custom "PauliZ"() %1 : !quantum.bit +// expected-remark@above {{ZERO}} + %5 = quantum.custom "Hadamard"() %1 : !quantum.bit +// expected-remark@above {{PLUS}} + %cst = arith.constant 3.140000e+00 : f64 + return %cst : f64 + } + + +// ----- + + +// Explicit unit tests for all the FSM transition edges from |1> +// Note that these explicit edge tests reuse qubits for conciseness. + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %2 = quantum.custom "PauliX"() %1 : !quantum.bit +// expected-remark@above {{ONE}} + + %3 = quantum.custom "PauliX"() %2 : !quantum.bit +// expected-remark@above {{ZERO}} + %4 = quantum.custom "PauliY"() %2 : !quantum.bit +// expected-remark@above {{ZERO}} + %5 = quantum.custom "PauliZ"() %2 : !quantum.bit +// expected-remark@above {{ONE}} + %6 = quantum.custom "Hadamard"() %2 : !quantum.bit +// expected-remark@above {{MINUS}} + %cst = arith.constant 3.140000e+00 : f64 + return %cst : f64 + } + + +// ----- + + +// Explicit unit tests for all the FSM transition edges from |+> +// Note that these explicit edge tests reuse qubits for conciseness. + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %2 = quantum.custom "Hadamard"() %1 : !quantum.bit +// expected-remark@above {{PLUS}} + + %3 = quantum.custom "PauliX"() %2 : !quantum.bit +// expected-remark@above {{PLUS}} + %4 = quantum.custom "PauliY"() %2 : !quantum.bit +// expected-remark@above {{MINUS}} + %5 = quantum.custom "PauliZ"() %2 : !quantum.bit +// expected-remark@above {{MINUS}} + %6 = quantum.custom "Hadamard"() %2 : !quantum.bit +// expected-remark@above {{ZERO}} + %7 = quantum.custom "S"() %2 : !quantum.bit +// expected-remark@above {{LEFT}} + %cst = arith.constant 3.140000e+00 : f64 + return %cst : f64 + } + + +// ----- + + +// Explicit unit tests for all the FSM transition edges from |-> +// Note that these explicit edge tests reuse qubits for conciseness. + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %2 = quantum.custom "Hadamard"() %1 : !quantum.bit +// expected-remark@above {{PLUS}} + %3 = quantum.custom "PauliZ"() %2 : !quantum.bit +// expected-remark@above {{MINUS}} + + %4 = quantum.custom "PauliX"() %3 : !quantum.bit +// expected-remark@above {{MINUS}} + %5 = quantum.custom "PauliY"() %3 : !quantum.bit +// expected-remark@above {{PLUS}} + %6 = quantum.custom "PauliZ"() %3 : !quantum.bit +// expected-remark@above {{PLUS}} + %7 = quantum.custom "Hadamard"() %3 : !quantum.bit +// expected-remark@above {{ONE}} + %8 = quantum.custom "S"() %3 : !quantum.bit +// expected-remark@above {{RIGHT}} + %cst = arith.constant 3.140000e+00 : f64 + return %cst : f64 + } + + +// ----- + + +// Explicit unit tests for all the FSM transition edges from |L> +// Note that these explicit edge tests reuse qubits for conciseness. + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %2 = quantum.custom "Hadamard"() %1 : !quantum.bit +// expected-remark@above {{PLUS}} + %3 = quantum.custom "S"() %2 : !quantum.bit +// expected-remark@above {{LEFT}} + + %4 = quantum.custom "PauliX"() %3 : !quantum.bit +// expected-remark@above {{RIGHT}} + %5 = quantum.custom "PauliY"() %3 : !quantum.bit +// expected-remark@above {{LEFT}} + %6 = quantum.custom "PauliZ"() %3 : !quantum.bit +// expected-remark@above {{RIGHT}} + %7 = quantum.custom "Hadamard"() %3 : !quantum.bit +// expected-remark@above {{RIGHT}} + %8 = quantum.custom "S"() %3 {adjoint} : !quantum.bit +// expected-remark@above {{PLUS}} + %cst = arith.constant 3.140000e+00 : f64 + return %cst : f64 + } + + +// ----- + + +// Explicit unit tests for all the FSM transition edges from |R> +// Note that these explicit edge tests reuse qubits for conciseness. + +// CHECK: func.func private @circuit() + func.func private @circuit() -> f64 { + %0 = quantum.alloc( 1) : !quantum.reg + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit +// expected-remark@above {{ZERO}} + %2 = quantum.custom "Hadamard"() %1 : !quantum.bit +// expected-remark@above {{PLUS}} + %3 = quantum.custom "S"() %2 : !quantum.bit +// expected-remark@above {{LEFT}} + %4 = quantum.custom "PauliX"() %3 : !quantum.bit +// expected-remark@above {{RIGHT}} + + %5 = quantum.custom "PauliX"() %4 : !quantum.bit +// expected-remark@above {{LEFT}} + %6 = quantum.custom "PauliY"() %4 : !quantum.bit +// expected-remark@above {{RIGHT}} + %7 = quantum.custom "PauliZ"() %4 : !quantum.bit +// expected-remark@above {{LEFT}} + %8 = quantum.custom "Hadamard"() %4 : !quantum.bit +// expected-remark@above {{LEFT}} + %9 = quantum.custom "S"() %4 {adjoint} : !quantum.bit +// expected-remark@above {{MINUS}} + %cst = arith.constant 3.140000e+00 : f64 + return %cst : f64 + }