Skip to content

Commit 30e553e

Browse files
rturradosengthai
andauthored
Support commuting P(pi/2) past non-Clifford PPR (#1966)
**Context:** At the moment, only pi/4 Cliffords can be commuted past non-Clifford. With this PR, pi/2 Cliffords should also be able to be commuted. **Description of the Change:** The main change is implemented in `PauliStringWrapper::computeCommutationRulesWith`. Here, we keep returning `PP'i` for a pi/4 Clifford, but we now return `-P'` for a pi/2 Clifford. The other main change needed is to update the definition of `isClifford` in QECDialect.td. Now, a Clifford can also have a rotation of pi/2. **Benefits:** pi/2 Cliffords can now be commuted past non-Clifford. **Possible Drawbacks:** I have only added 2 anti-conmute tests. More tests may be needed. **Related GitHub Issues:** close #1713 --------- Co-authored-by: Sengthai Heng <[email protected]>
1 parent 63aca2f commit 30e553e

File tree

9 files changed

+146
-45
lines changed

9 files changed

+146
-45
lines changed

doc/releases/changelog-dev.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
%0 = transform.apply_registered_pass "some-pass" with options = {"an-option" = true, "maxValue" = 1 : i64, "multi-word-option" = 1 : i64}
3232
```
3333

34+
* `Commuting Clifford Pauli Product Rotation (PPR) operations, past non-Clifford PPRs, now supports P(π/2) Cliffords in addition to P(π/4)`
35+
[(#1966)](https://github.com/PennyLaneAI/catalyst/pull/1966)
36+
3437
<h3>Breaking changes 💔</h3>
3538

3639
* The JAX version used by Catalyst is updated to 0.6.2.

frontend/catalyst/passes/builtin_passes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ def to_ppr(qnode):
530530
531531
.. note::
532532
533-
The circuit that generated from this pass are currently
533+
The circuits that generated from this pass are currently
534534
only not executable in any backend. This pass is only for analysis
535535
and potential future execution when a suitable backend is available.
536536

mlir/include/QEC/IR/QECDialect.td

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,24 @@ def PPRotationOp : QEC_Op<"ppr", [QECOpInterface, AttrSizedOperandSegments]> {
323323
let hasVerifier = 1;
324324

325325
code extraBaseClassDeclaration = [{
326-
bool isNonClifford(){
326+
bool hasPiOverTwoRotation(){
327327
int16_t rotationKind = static_cast<int16_t>(getRotationKind());
328-
return rotationKind == 8 || rotationKind == -8; // π/8 rotations are non-Clifford
328+
return rotationKind == 2 || rotationKind == -2;
329329
};
330-
bool isClifford(){
330+
bool hasPiOverFourRotation(){
331331
int16_t rotationKind = static_cast<int16_t>(getRotationKind());
332-
return rotationKind == 4 || rotationKind == -4; // π/4 rotations are Clifford
332+
return rotationKind == 4 || rotationKind == -4;
333+
};
334+
bool hasPiOverEightRotation(){
335+
int16_t rotationKind = static_cast<int16_t>(getRotationKind());
336+
return rotationKind == 8 || rotationKind == -8;
337+
};
338+
339+
bool isNonClifford(){
340+
return hasPiOverEightRotation(); // π/8 rotations are non-Clifford
341+
};
342+
bool isClifford(){
343+
return hasPiOverTwoRotation() || hasPiOverFourRotation(); // π/2 and π/4 rotations are Clifford
333344
};
334345
}];
335346
let extraClassDeclaration = extraBaseClassDeclaration;

mlir/include/QEC/Utils/PauliStringWrapper.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414

1515
#pragma once
1616

17+
#include "llvm/ADT/SetVector.h"
18+
1719
#include "QEC/IR/QECDialect.h"
1820
#include "QEC/Transforms/Patterns.h"
19-
#include "llvm/ADT/SetVector.h"
2021

2122
using namespace mlir;
2223

@@ -82,8 +83,11 @@ struct PauliStringWrapper {
8283

8384
// Commutation Rules
8485
// P is Clifford, P' is non-Clifford
86+
// P can have a π/2 or π/4 rotation
8587
// if P commutes with P' then PP' = P'P
86-
// if P anti-commutes with P' then PP' = -iPP' P
88+
// if P anti-commutes with P' then
89+
// if P has a π/2 rotation then PP' = -P'P
90+
// if P has a π/4 rotation then PP' = -iPP'P
8791
// In here, P and P' are lhs and rhs, respectively.
8892
PauliStringWrapper computeCommutationRulesWith(const PauliStringWrapper &rhs) const;
8993
};
@@ -95,15 +99,15 @@ struct PauliStringWrapper {
9599
using PauliWordPair = std::pair<PauliStringWrapper, PauliStringWrapper>;
96100

97101
/**
98-
* @brief Expland the op's operands to the set of operands.
102+
* @brief Expand the op's operands to the set of operands.
99103
* - Initialize the new pauliWord with "I" for each qubit.
100104
* - Find location of inOutOperands in set of operands
101105
* - Assign the inOutOperands' pauli word to new pauliWord in that location
102106
* e.g: qubits = {q0, q1, q2}, inQubitOut = [q1, q2], op.getPauliProduct() = ["X", "Y"]
103107
* -> ["I", "X", "Y"]
104108
*
105109
* @tparam T either mlir::Operation::operand_range or mlir::Operation::result_range
106-
* @param qubits set of combination of qubit operands
110+
* @param operands set of combination of qubit operands
107111
* @param inOutOperands either value from.inQubits or .outQubits from op
108112
* @param op QECOpInterface
109113
* @return PauliWord of the expanded pauliWord
@@ -151,7 +155,7 @@ SmallVector<Value> replaceValueWithOperands(const PauliStringWrapper &lhsPauliWr
151155
/**
152156
* @brief Update the pauliWord of the right hand side operation.
153157
*
154-
* @param rhsOp QECOpInterface of the right hand side
158+
* @param op QECOpInterface of the right hand side
155159
* @param newPauliWord PauliWord of the new pauliWord
156160
* @param rewriter PatternRewriter
157161
*/

mlir/lib/QEC/Transforms/CommutePPR.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ void moveCliffordPastNonClifford(const PauliStringWrapper &lhsPauli,
111111
updatePauliWordSign(rhs, rhsPauli.isNegative(), rewriter);
112112
}
113113

114-
// Fullfill Operands of RHS
114+
// Fulfill Operands of RHS
115115
SmallVector<Value> newRHSOperands = replaceValueWithOperands(lhsPauli, rhsPauli);
116116

117117
// Remove the Identity gate in the Pauli product

mlir/lib/QEC/Transforms/DecomposeCliffordPPR.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
#include <mlir/IR/Builders.h>
1919
#include <mlir/IR/Value.h>
2020

21-
#include "Quantum/IR/QuantumOps.h"
22-
2321
#include "QEC/IR/QECDialect.h"
2422
#include "QEC/Transforms/PPRDecomposeUtils.h"
2523
#include "QEC/Transforms/Patterns.h"
2624
#include "QEC/Utils/PauliStringWrapper.h"
25+
#include "Quantum/IR/QuantumOps.h"
2726

2827
using namespace mlir;
2928
using namespace catalyst::qec;
@@ -105,7 +104,7 @@ struct DecomposeCliffordPPR : public OpRewritePattern<PPRotationOp> {
105104

106105
LogicalResult matchAndRewrite(PPRotationOp op, PatternRewriter &rewriter) const override
107106
{
108-
if (op.isClifford()) {
107+
if (op.hasPiOverFourRotation()) {
109108
decompose_pi_over_four_flattening(avoidPauliYMeasure, op, op.getCondition(), rewriter);
110109
return success();
111110
}

mlir/lib/QEC/Utils/PauliStringWrapper.cpp

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616
#include <stim/stabilizers/flex_pauli_string.h>
1717
#include <stim/stabilizers/pauli_string.h>
1818

19-
#include "Quantum/IR/QuantumOps.h" // for quantum::AllocQubitOp
20-
2119
#include "QEC/Utils/PauliStringWrapper.h"
20+
#include "Quantum/IR/QuantumOps.h" // for quantum::AllocQubitOp
2221

2322
namespace catalyst {
2423
namespace qec {
@@ -79,12 +78,21 @@ bool PauliStringWrapper::commutes(const PauliStringWrapper &other) const
7978
PauliStringWrapper
8079
PauliStringWrapper::computeCommutationRulesWith(const PauliStringWrapper &rhs) const
8180
{
82-
// P * P' * i
83-
stim::FlexPauliString result =
84-
(*this->pauliString) * (*rhs.pauliString) * stim::FlexPauliString::from_text("i");
85-
81+
stim::FlexPauliString result = *rhs.pauliString;
82+
assert(llvm::isa<PPRotationOp>(this->op) && "Clifford Operation is not PPRotationOp");
83+
auto this_op = llvm::cast<PPRotationOp>(this->op);
84+
if (this_op.hasPiOverTwoRotation()) {
85+
// -P'
86+
result.value.sign = !result.value.sign;
87+
}
88+
else if (this_op.hasPiOverFourRotation()) {
89+
// P * P' * i
90+
result = (*this->pauliString) * result * stim::FlexPauliString::from_text("i");
91+
}
92+
else {
93+
llvm_unreachable("Clifford rotation should be π/2 or π/4");
94+
}
8695
assert(!result.imag && "Resulting Pauli string should be real");
87-
8896
return PauliStringWrapper(std::move(result));
8997
}
9098

mlir/test/QEC/CommutePPR.mlir

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func.func @test_anticommute_5(%q1 : !quantum.bit, %q2 : !quantum.bit){
120120

121121
func.func @test_anticommute_6(%q1 : !quantum.bit, %q2 : !quantum.bit){
122122

123-
// XY commuts with ZZ
123+
// XY commutes with ZZ
124124

125125
// XY(4) * ZZ(8) * ZY(8)
126126
// -> ZZ(8) * XY(4) * ZY(8)
@@ -188,6 +188,54 @@ func.func @test_anticommute_9(%q1 : !quantum.bit, %q2 : !quantum.bit, %q3 : !qua
188188

189189
// -----
190190

191+
func.func @test_anticommute_10(%q1 : !quantum.bit){
192+
193+
// Pauli rule:
194+
// YZ = -ZY
195+
196+
// Y(2) * Z(8) * Y(2) * Z(8)
197+
// -> Z(-8) * Y(2) * Y(2) * Z(8)
198+
// -> Z(-8) * Y(2) * Z(-8) * Y(2)
199+
// -> Z(-8) * Z(8) * Y(2) * Y(2)
200+
201+
// CHECK: [[q1_0:%.+]] = qec.ppr ["Z"](-8) %arg0
202+
// CHECK: [[q1_1:%.+]] = qec.ppr ["Z"](8) [[q1_0]]
203+
// CHECK: [[q1_2:%.+]] = qec.ppr ["Y"](2) [[q1_1]]
204+
// CHECK: [[q1_3:%.+]] = qec.ppr ["Y"](2) [[q1_2]]
205+
%0 = qec.ppr ["Y"](2) %q1 : !quantum.bit
206+
%1 = qec.ppr ["Z"](8) %0 : !quantum.bit
207+
%2 = qec.ppr ["Y"](2) %1 : !quantum.bit
208+
%3 = qec.ppr ["Z"](8) %2 : !quantum.bit
209+
func.return
210+
}
211+
212+
// -----
213+
214+
func.func @test_anticommute_11(%q1 : !quantum.bit, %q2 : !quantum.bit, %q3 : !quantum.bit){
215+
216+
// Pauli rules:
217+
//
218+
// XZ = -ZX
219+
//
220+
// P and P' commute if there is an even number of anti-commuting single-qubit pairs.
221+
// Otherwise, they anti-commute.
222+
223+
224+
// XYZ(2) * XZY(8) * ZYZ(8)
225+
// -> XZY(8) * XYZ(2) * ZYZ(8) // XYZ(2) and XZY(8) commute (XX commute, YZ and ZY anti-commute)
226+
// -> XZY(8) * ZYZ(-8) * XYZ(2) // XYZ(2) and ZYZ(8) anti-commute (XZ anti-commutes, YY and ZZ commute)
227+
228+
// CHECK: [[q1_0:%.+]]:3 = qec.ppr ["X", "Z", "Y"](8) %arg0, %arg1, %arg2
229+
// CHECK: [[q1_1:%.+]]:3 = qec.ppr ["Z", "Y", "Z"](-8) [[q1_0]]#0, [[q1_0]]#1, [[q1_0]]#2
230+
// CHECK: [[q1_2:%.+]]:3 = qec.ppr ["X", "Y", "Z"](2) [[q1_1]]#0, [[q1_1]]#1, [[q1_1]]#2
231+
%0:3 = qec.ppr ["X", "Y", "Z"](2) %q1, %q2, %q3 : !quantum.bit, !quantum.bit, !quantum.bit
232+
%1:3 = qec.ppr ["X", "Z", "Y"](8) %0#0, %0#1, %0#2 : !quantum.bit, !quantum.bit, !quantum.bit
233+
%2:3 = qec.ppr ["Z", "Y", "Z"](8) %1#0, %1#1, %1#2 : !quantum.bit, !quantum.bit, !quantum.bit
234+
func.return
235+
}
236+
237+
// -----
238+
191239
func.func public @circuit_first_minus(%q0: !quantum.bit, %q1: !quantum.bit) {
192240

193241
// CHECK: qec.ppr ["Y"](-8)

mlir/test/QEC/MergePPRIntoPPM.mlir

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717

1818
func.func public @merge_ppr_ppm_test_1(%q1: !quantum.bit) -> tensor<i1> {
1919

20-
// CHECK-NOT: qec.ppr["X"](4)
21-
// CHECK: qec.ppm ["X"] %
22-
// CHECK-NOT: qec.ppr["X"](4)
20+
// CHECK-NOT: qec.ppr ["X"]
21+
// CHECK: qec.ppm ["X"] %
22+
// CHECK-NOT: qec.ppr ["X"]
2323
%0 = qec.ppr ["X"](4) %q1: !quantum.bit
2424
%m, %out_qubits = qec.ppm ["X"] %0 : !quantum.bit
2525
%from_elements = tensor.from_elements %m : tensor<i1>
@@ -28,9 +28,8 @@ func.func public @merge_ppr_ppm_test_1(%q1: !quantum.bit) -> tensor<i1> {
2828

2929
func.func public @merge_ppr_ppm_test_2(%q1: !quantum.bit) -> (tensor<i1>, !quantum.bit) {
3030

31-
// CHECK-NOT: qec.ppr["X"](4)
32-
// CHECK: qec.ppm ["Z"](-1) %
33-
// CHECK-NOT: qec.ppr["X"](4)
31+
// CHECK: qec.ppm ["Z"](-1) %
32+
// CHECK: qec.ppr ["X"](4)
3433
%0 = qec.ppr ["X"](4) %q1: !quantum.bit
3534
%m, %out_qubits = qec.ppm ["Y"] %0 : !quantum.bit
3635
%from_elements = tensor.from_elements %m : tensor<i1>
@@ -39,9 +38,8 @@ func.func public @merge_ppr_ppm_test_2(%q1: !quantum.bit) -> (tensor<i1>, !quant
3938

4039
func.func public @merge_ppr_ppm_test_3(%q1: !quantum.bit) -> (tensor<i1>, !quantum.bit) {
4140

42-
// CHECK-NOT: qec.ppr["X"](4)
43-
// CHECK: qec.ppm ["Y"] %
44-
// CHECK-NOT: qec.ppr["X"](4)
41+
// CHECK: qec.ppm ["Y"] %
42+
// CHECK: qec.ppr ["X"](-4)
4543
%0 = qec.ppr ["X"](-4) %q1: !quantum.bit
4644
%m, %out_qubits = qec.ppm ["Z"](-1) %0 : !quantum.bit
4745
%from_elements = tensor.from_elements %m : tensor<i1>
@@ -50,12 +48,12 @@ func.func public @merge_ppr_ppm_test_3(%q1: !quantum.bit) -> (tensor<i1>, !quant
5048

5149
func.func public @merge_ppr_ppm_test_4(%q1: !quantum.bit, %q2: !quantum.bit) -> (tensor<i1>, !quantum.bit) {
5250

53-
// CHECK: [[q0:%.+]] = qec.ppr ["X"](8) %arg0 : !quantum.bit
51+
// CHECK: [[q0:%.+]] = qec.ppr ["X"](8) %arg0 : !quantum.bit
5452
// CHECK-NOT: ["X"](4)
55-
// CHECK: [[m1:%.+]], [[o1:%.+]]:2 = qec.ppm ["X", "X"] %arg1, [[q0]]
53+
// CHECK: [[m1:%.+]], [[o1:%.+]]:2 = qec.ppm ["X", "X"] %arg1, [[q0]]
5654
// CHECK-NOT: ["X"](4)
57-
// CHECK: %from_elements = tensor.from_elements [[m1]] : tensor<i1>
58-
// CHECK: return %from_elements, [[o1]]#1 : tensor<i1>, !quantum.bit
55+
// CHECK: %from_elements = tensor.from_elements [[m1]] : tensor<i1>
56+
// CHECK: return %from_elements, [[o1]]#1 : tensor<i1>, !quantum.bit
5957
%0 = qec.ppr ["X"](8) %q1: !quantum.bit
6058
%1 = qec.ppr ["X"](4) %q2: !quantum.bit
6159
%m, %out_qubits:2 = qec.ppm ["X", "X"] %0, %1 : !quantum.bit, !quantum.bit
@@ -65,12 +63,12 @@ func.func public @merge_ppr_ppm_test_4(%q1: !quantum.bit, %q2: !quantum.bit) ->
6563

6664
func.func public @merge_ppr_ppm_test_5(%q1: !quantum.bit, %q2: !quantum.bit) -> (tensor<i1>, !quantum.bit) {
6765

68-
// CHECK: [[q0:%.+]] = qec.ppr ["X"](8) %arg0 : !quantum.bit
69-
// CHECK-NOT: qec.ppr ["Y"](4)
70-
// CHECK: [[m1:%.+]], [[o1:%.+]]:2 = qec.ppm ["Z", "X"] %arg1, [[q0]]
71-
// CHECK-NOT: qec.ppr ["Y"](4)
72-
// CHECK: %from_elements = tensor.from_elements [[m1]] : tensor<i1>
73-
// CHECK: return %from_elements, [[o1]]#1 : tensor<i1>, !quantum.bit
66+
// CHECK: [[q0:%.+]] = qec.ppr ["X"](8) %arg0 : !quantum.bit
67+
// CHECK-NOT: qec.ppr ["Y"](4)
68+
// CHECK: [[m1:%.+]], [[o1:%.+]]:2 = qec.ppm ["Z", "X"] %arg1, [[q0]]
69+
// CHECK-NOT: qec.ppr ["Y"](4)
70+
// CHECK: %from_elements = tensor.from_elements [[m1]] : tensor<i1>
71+
// CHECK: return %from_elements, [[o1]]#1 : tensor<i1>, !quantum.bit
7472
%0 = qec.ppr ["X"](8) %q1: !quantum.bit
7573
%1 = qec.ppr ["Y"](4) %q2: !quantum.bit
7674
%m, %out_qubits:2 = qec.ppm ["X", "X"] %0, %1 : !quantum.bit, !quantum.bit
@@ -92,6 +90,37 @@ func.func public @merge_ppr_ppm_test_6(%q1: !quantum.bit, %q2: !quantum.bit) ->
9290
return %from_elements, %out_qubits#0 : tensor<i1>, !quantum.bit
9391
}
9492

93+
func.func public @merge_ppr_ppm_test_7(%q1: !quantum.bit) -> tensor<i1> {
94+
95+
// CHECK-NOT: qec.ppr ["X"]
96+
// CHECK: qec.ppm ["X"] %
97+
// CHECK-NOT: qec.ppr ["X"]
98+
%0 = qec.ppr ["X"](2) %q1: !quantum.bit
99+
%m, %out_qubits = qec.ppm ["X"] %0 : !quantum.bit
100+
%from_elements = tensor.from_elements %m : tensor<i1>
101+
return %from_elements : tensor<i1>
102+
}
103+
104+
func.func public @merge_ppr_ppm_test_8(%q1: !quantum.bit) -> (tensor<i1>, !quantum.bit) {
105+
106+
// CHECK: qec.ppm ["Y"](-1) %
107+
// CHECK: qec.ppr ["X"](2)
108+
%0 = qec.ppr ["X"](2) %q1: !quantum.bit
109+
%m, %out_qubits = qec.ppm ["Y"] %0 : !quantum.bit
110+
%from_elements = tensor.from_elements %m : tensor<i1>
111+
return %from_elements, %out_qubits : tensor<i1>, !quantum.bit
112+
}
113+
114+
func.func public @merge_ppr_ppm_test_9(%q1: !quantum.bit) -> (tensor<i1>, !quantum.bit) {
115+
116+
// CHECK: qec.ppm ["Z"] %
117+
// CHECK: qec.ppr ["X"](-2)
118+
%0 = qec.ppr ["X"](-2) %q1: !quantum.bit
119+
%m, %out_qubits = qec.ppm ["Z"](-1) %0 : !quantum.bit
120+
%from_elements = tensor.from_elements %m : tensor<i1>
121+
return %from_elements, %out_qubits : tensor<i1>, !quantum.bit
122+
}
123+
95124
func.func public @game_of_surface_code(%arg0: !quantum.bit, %arg1: !quantum.bit, %arg2: !quantum.bit, %arg3: !quantum.bit) {
96125

97126
// q1
@@ -102,10 +131,10 @@ func.func public @game_of_surface_code(%arg0: !quantum.bit, %arg1: !quantum.bit,
102131
// CHECK: [[q2:%.+]]:2 = qec.ppr ["Y", "X"](8) %arg2, %arg1
103132

104133
// q3, q2, q4, q1
105-
// CHECK: [[q3:%.+]]:4 = qec.ppr ["Z", "Z", "Y", "Z"](-8) [[q2]]#0, [[q2]]#1, [[q1]], [[q0]]
106-
134+
// CHECK: [[q3:%.+]]:4 = qec.ppr ["Z", "Z", "Y", "Z"](-8) [[q2]]#0, [[q2]]#1, [[q1]], [[q0]]
135+
107136
// q3, q2, q1, q4
108-
// CHECK: [[m1:%.+]], [[o1:%.+]]:4 = qec.ppm ["Z", "Z", "Y", "Y"] [[q3]]#0, [[q3]]#1, [[q3]]#3, [[q3]]#2
137+
// CHECK: [[m1:%.+]], [[o1:%.+]]:4 = qec.ppm ["Z", "Z", "Y", "Y"] [[q3]]#0, [[q3]]#1, [[q3]]#3, [[q3]]#2
109138

110139
// q2, q1
111140
// CHECK: [[m2:%.+]], [[o2:%.+]]:2 = qec.ppm ["X", "X"] [[o1]]#1, [[o1]]#2
@@ -149,7 +178,6 @@ func.func public @game_of_surface_code(%arg0: !quantum.bit, %arg1: !quantum.bit,
149178
return
150179
}
151180

152-
153181
func.func public @circuit_transformed_0() -> (tensor<i1>, tensor<i1>) {
154182

155183
// CHECK: [[qreg:%.+]] = quantum.alloc( 2) : !quantum.reg

0 commit comments

Comments
 (0)