Repository: PennyLaneAI/pennylane
Pull request #6842 (2025-01-16 00:13:35)

`PhaseShift` optimizations for `qml.clifford_t_decomposition`
**Context:** Compilation transform converts adjoints of some gates to rotations that may result in complex decomposition due to the internal handling of the global phase. 

**Description of the Change:** We try to catch `PhaseShift`-based simplification when we process rotation gates. 

**Benefits:** Some simple cases like Adjoint(S) and Adjoint(T) will no longer give unexpectedly deep decompositions. 

**Possible Drawbacks:** There still are edge cases that remain, mostly due to approximate-set building for the solovay-kitaev decomposition. Ironing them out would still require some extra effort and a future PR. 

**Related GitHub Issues:** [sc-82010], #6131 


github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6842?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.60%. Comparing base [(`0bed5e8`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/0bed5e81a8a8a15dbb74ce2051e54e6ba19e8602?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`7ca9d78`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/7ca9d78eb453a470a06b77a1e5327d3c54454162?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> Report is 3 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #6842   +/-   ##
=======================================
  Coverage   99.60%   99.60%           
=======================================
  Files         476      476           
  Lines       45178    45189   +11     
=======================================
+ Hits        44999    45010   +11     
  Misses        179      179           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6842?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

obliviateandsurrender: > This looks good for the cases that are being considered. Do you think there is any need to extend this to gates other than S and T? If so we may want to refactor to make the code more extendable. If not, it is probably ok as it is.

This PR's focus is limited to simplifying adjoints of `S` and `T` gates. I think the rest of the workflow can catch most of the other nuanced cases if not all. Moreover, I think doing an extension wouldn't be required with the improved SK for now. 

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index cba18c600..9200442bb 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -118,6 +118,9 @@
 * The requested `diff_method` is now validated when program capture is enabled.
   [(#6852)](https://github.com/PennyLaneAI/pennylane/pull/6852)
 
+* The `qml.clifford_t_decomposition` has been improved to use less gates when decomposing `qml.PhaseShift`.
+  [(#6842)](https://github.com/PennyLaneAI/pennylane/pull/6842)
+
 <h3>Breaking changes 💔</h3>
 
 * `MultiControlledX` no longer accepts strings as control values.
@@ -229,6 +232,7 @@
 
 This release contains contributions from (in alphabetical order):
 
+Utkarsh Azad,
 Yushao Chen,
 Isaac De Vlugt,
 Diksha Dhawan,
diff --git a/pennylane/transforms/decompositions/clifford_t_transform.py b/pennylane/transforms/decompositions/clifford_t_transform.py
index 8e90abe2d..4c28d8cd4 100644
--- a/pennylane/transforms/decompositions/clifford_t_transform.py
+++ b/pennylane/transforms/decompositions/clifford_t_transform.py
@@ -213,6 +213,21 @@ def _rot_decompose(op):
         ops_ = _simplify_param(theta, qml.Z(wires))
         if ops_ is None:
             ops_ = [qml.RZ(theta, wires=wires), qml.GlobalPhase(-theta / 2)]
+            if not qml.math.is_abstract(theta):
+                # The following loop simplifies the two cases where for all odd intergers `k`,
+                # `PhaseShift(k * pi / 2)` is S / S* and `PhaseShift(k * pi / 4)` is T / T*.
+                for val_ in [2, 4]:
+                    div_ = qml.math.divide(theta, math.pi / val_)
+                    mod_ = qml.math.mod(theta, math.pi / val_)
+                    if qml.math.allclose(mod_, 0.0, atol=1e-6) and qml.math.allclose(
+                        qml.math.mod(div_, 2), 1.0, atol=1e-6
+                    ):
+                        vop_ = qml.S(wires) if val_ == 2 else qml.T(wires)
+                        sign = qml.math.mod(qml.math.floor_divide(div_, 2), 2)
+                        ops_ = [
+                            vop_ if qml.math.allclose(sign, 0.0, atol=1e-6) else qml.adjoint(vop_)
+                        ]
+                        break
         else:
             ops_.append(qml.GlobalPhase(-theta / 2))
 


Test patch:
diff --git a/tests/transforms/test_cliffordt_transform.py b/tests/transforms/test_cliffordt_transform.py
index 759f44684..eb6fcb5f3 100644
--- a/tests/transforms/test_cliffordt_transform.py
+++ b/tests/transforms/test_cliffordt_transform.py
@@ -99,6 +99,15 @@ def circuit_5():
     return qml.expval(qml.PauliZ(0))
 
 
+def circuit_6():
+    """Circuit 6 with adjoint S and T"""
+    qml.adjoint(qml.S(wires=0))
+    qml.PhaseShift(5 * math.pi / 2, wires=1)
+    qml.adjoint(qml.T(wires=2))
+    qml.PhaseShift(-3 * math.pi / 4, wires=3)
+    return qml.expval(qml.PauliZ(0))
+
+
 class TestCliffordCompile:
     """Unit tests for clifford compilation function."""
 
@@ -145,7 +154,7 @@ class TestCliffordCompile:
         qml.math.isclose(res1, tape_fn([res2]), atol=1e-2)
 
     def test_qnode_decomposition(self):
-        """Test decomposition for the Clifford transform."""
+        """Test decomposition for the Clifford transform applied to a QNode."""
 
         dev = qml.device("default.qubit")
 
@@ -171,6 +180,19 @@ class TestCliffordCompile:
             for op in tape.operations
         )
 
+    def test_phase_shift_decomposition(self):
+        """Test decomposition for the Clifford transform applied to the circuits with phase shifts."""
+        old_tape = qml.tape.make_qscript(circuit_6)()
+
+        [new_tape], _ = clifford_t_decomposition(old_tape, max_depth=3)
+
+        compiled_ops = new_tape.operations
+
+        assert qml.equal(compiled_ops[0], qml.adjoint(qml.S(0)))
+        assert qml.equal(compiled_ops[1], qml.S(1))
+        assert qml.equal(compiled_ops[2], qml.adjoint(qml.T(2)))
+        assert qml.equal(compiled_ops[3], qml.T(3))
+
     @pytest.mark.parametrize("epsilon", [2e-2, 5e-2, 9e-2])
     @pytest.mark.parametrize("circuit", [circuit_3, circuit_4, circuit_5])
     def test_total_error(self, epsilon, circuit):
@@ -411,7 +433,7 @@ class TestCliffordCompile:
 
         [tape], _ = qml.clifford_t_decomposition(tape)
 
-        assert not sum([isinstance(op, qml.GlobalPhase) for op in tape.operations])
+        assert not sum(isinstance(op, qml.GlobalPhase) for op in tape.operations)
 
     def test_raise_with_decomposition_method(self):
         """Test that exception is correctly raise when using incorrect decomposing method"""