diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 32b7de372d..59b4527d52 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -13,6 +13,10 @@
program outputs are typically probabilistic and statistics produced by measurement processes are
conditional on the selected decision tree path.
+* Two new verifiers were added to the `quantum.paulirot` operation. They verify that the Pauli word
+ length and the number of qubit operands are the same, and that all of the Pauli words are legal.
+ [(#2405)](https://github.com/PennyLaneAI/catalyst/pull/2405)
+
Breaking changes 💔
Deprecations 👋
@@ -47,7 +51,8 @@
Contributors ✍️
This release contains contributions from (in alphabetical order):
-Ali Asadi
+Ali Asadi,
Sengthai Heng,
Jeffrey Kam,
-Mudit Pandey.
+Mudit Pandey,
+Paul Haochen Wang.
diff --git a/mlir/include/Quantum/IR/QuantumAttrDefs.td b/mlir/include/Quantum/IR/QuantumAttrDefs.td
index 133462bae1..716c60b2aa 100644
--- a/mlir/include/Quantum/IR/QuantumAttrDefs.td
+++ b/mlir/include/Quantum/IR/QuantumAttrDefs.td
@@ -38,5 +38,6 @@ def NamedObservable : I32EnumAttr<"NamedObservable",
def NamedObservableAttr : EnumAttr;
+def PauliWord : TypedArrayAttrBase;
#endif // QUANTUM_ATTR_DEFS
diff --git a/mlir/include/Quantum/IR/QuantumOps.td b/mlir/include/Quantum/IR/QuantumOps.td
index 165aa880d1..8e3e4a9ad7 100644
--- a/mlir/include/Quantum/IR/QuantumOps.td
+++ b/mlir/include/Quantum/IR/QuantumOps.td
@@ -534,8 +534,6 @@ def CustomOp : UnitaryGate_Op<"custom", [DifferentiableGate, NoMemoryEffect,
let hasVerifier = 1;
}
-def PauliWord : TypedArrayAttrBase;
-
def PauliRotOp : UnitaryGate_Op<"paulirot", [DifferentiableGate, NoMemoryEffect,
AttrSizedOperandSegments, AttrSizedResultSegments]> {
let summary = "Apply a Pauli Product Rotation";
@@ -571,6 +569,8 @@ def PauliRotOp : UnitaryGate_Op<"paulirot", [DifferentiableGate, NoMemoryEffect,
return getODSOperands(getParamOperandIdx());
}
}];
+
+ let hasVerifier = 1;
}
def GlobalPhaseOp : UnitaryGate_Op<"gphase", [DifferentiableGate, AttrSizedOperandSegments]> {
diff --git a/mlir/lib/Quantum/IR/QuantumOps.cpp b/mlir/lib/Quantum/IR/QuantumOps.cpp
index 47686d99e2..13dacb38c2 100644
--- a/mlir/lib/Quantum/IR/QuantumOps.cpp
+++ b/mlir/lib/Quantum/IR/QuantumOps.cpp
@@ -262,6 +262,27 @@ static LogicalResult verifyTensorResult(Type ty, int64_t length0, int64_t length
// ----- gates
+static const mlir::StringSet<> validPauliWords = {"X", "Y", "Z", "I"};
+
+LogicalResult PauliRotOp::verify()
+{
+ size_t pauliWordLength = getPauliProduct().size();
+ size_t numQubits = getInQubits().size();
+ if (pauliWordLength != numQubits) {
+ return emitOpError() << "length of Pauli word (" << pauliWordLength
+ << ") and number of qubits (" << numQubits << ") must be the same";
+ }
+
+ if (!llvm::all_of(getPauliProduct(), [](mlir::Attribute attr) {
+ auto pauliStr = llvm::cast(attr);
+ return validPauliWords.contains(pauliStr.getValue());
+ })) {
+ return emitOpError() << "Only \"X\", \"Y\", \"Z\", and \"I\" are valid Pauli words.";
+ }
+
+ return success();
+}
+
LogicalResult QubitUnitaryOp::verify()
{
size_t dim = std::pow(2, getInQubits().size());
diff --git a/mlir/test/Quantum/VerifierTest.mlir b/mlir/test/Quantum/VerifierTest.mlir
index f6fbb90ac1..31686aa350 100644
--- a/mlir/test/Quantum/VerifierTest.mlir
+++ b/mlir/test/Quantum/VerifierTest.mlir
@@ -127,6 +127,23 @@ func.func @controlled2(%1 : !quantum.bit, %2 : !quantum.bit, %3 : !quantum.bit)
return
}
+// -----
+
+func.func @test_paulirot_length_mismatch(%1 : !quantum.bit, %angle: f64) {
+ // expected-error@+1 {{length of Pauli word (2) and number of qubits (1) must be the same}}
+ %q = quantum.paulirot ["Z", "X"](%angle) %1 : !quantum.bit
+ return
+}
+
+// -----
+
+func.func @test_paulirot_bad_pauli_word(%1 : !quantum.bit, %angle: f64) {
+ // expected-error@+1 {{Only "X", "Y", "Z", and "I" are valid Pauli words.}}
+ %q = quantum.paulirot ["bad"](%angle) %1 : !quantum.bit
+ return
+}
+
+// -----
//////////////////
// Measurements //