Repository: PennyLaneAI/pennylane
Pull request #4657 (2023-10-06 15:24:47)

Adds global phase to the `PhaseShift` decomposition
**Context:** Adds `GlobalPhase` to `PhaseShift` decomposition, which is right now missing and simply expands to `RZ`.

**Description of the Change:** Changes the `compute_decomposition` for `PhaseShift` and a few tests using the previous decomposition

**Benefits:** We wouldn't miss out on the phase information when decomposing this gate. 

**Possible Drawbacks:** N/A

**Related GitHub Issues:** N/A

**Note for reviewers:**
If a device does not support `PhaseShift` and _also_ doesn't support `GlobalPhase`, this should not cause any trouble because `GlobalPhase` decomposes to nothing. It won't have the phase information, but it didn't in the past either (just the `RZ`, so it will remain correct up to a phase)

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/4657?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified lines are covered by tests :white_check_mark:
> Comparison is base [(`da79370`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/da79370db01834671bcd4d1edce408131f920ab2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.63% compared to head [(`69ea019`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4657?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.63%.

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


```diff
@@           Coverage Diff           @@
##           master    #4657   +/-   ##
=======================================
  Coverage   99.63%   99.63%           
=======================================
  Files         376      376           
  Lines       33666    33675    +9     
=======================================
+ Hits        33542    33551    +9     
  Misses        124      124           
```


| [Files](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4657?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/ops/qubit/parametric\_ops\_single\_qubit.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4657?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC9wYXJhbWV0cmljX29wc19zaW5nbGVfcXViaXQucHk=) | `100.00% <100.00%> (ø)` | |
| [...lane/transforms/optimization/optimization\_utils.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4657?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvb3B0aW1pemF0aW9uL29wdGltaXphdGlvbl91dGlscy5weQ==) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4657?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).

Jaybsoni: Just a few small comments, I think it would also be useful to look for decompositions of PhaseShift that don't use the GlobalPhase operator until we have a decomposition for it
trbromley: > I'm not sure what would change after 0.33 is released, but we might need to work out a better plan for general GlobalPhase support before doing this.

Agreed, I think we can just give a decomposition to the identity. This is wrong, but only as wrong as it was before.
timmysilv: works for me... but what wire will we give to the decomposition 😅 
trbromley: > works for me... but what wire will we give to the decomposition 😅

I want to say that it should be the wires of `GlobalPhase`, but I'm still confused by the [docstrings](https://docs.pennylane.ai/en/latest/code/api/pennylane.GlobalPhase.html) about wires being an "unused argument".
timmysilv: the op has no wires. the argument is only there to not break constructors - Lillian tried to remove it and it made a huge mess for some reason. If you give them and they match all device wires, nice! If they don't match, your circuit will fail. But if you don't give them at all, the op wires are an empty list. So, if you try to decompose to Identity in that case (a common one, who would give wires to a global phase??), we won't know what wires to use.

idea: remove the need for wires from Identity as well.

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index ef71f1242..77e1e0344 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -434,6 +434,9 @@
   done with `qml.ExpvalCost`, but this is the preferred method because `ExpvalCost` is deprecated.
   [(#4896)](https://github.com/PennyLaneAI/pennylane/pull/4896)
 
+* Decomposition of `qml.PhaseShift` now uses `qml.GlobalPhase` for retaining the global phase information. 
+  [(#4657)](https://github.com/PennyLaneAI/pennylane/pull/4657)
+
 * `qml.equal` for `Controlled` operators no longer returns `False` when equivalent but 
   differently-ordered sets of control wires and control values are compared.
   [(#4944)](https://github.com/PennyLaneAI/pennylane/pull/4944)
@@ -608,6 +611,7 @@ This release contains contributions from (in alphabetical order):
 
 Guillermo Alonso,
 Ali Asadi,
+Utkarsh Azad,
 Gabriel Bottrill,
 Thomas Bromley,
 Astral Cai,
@@ -628,4 +632,4 @@ Mudit Pandey,
 Matthew Silverman,
 Jay Soni,
 David Wierichs,
-Justin Woodring.
\ No newline at end of file
+Justin Woodring.
diff --git a/pennylane/ops/qubit/parametric_ops_single_qubit.py b/pennylane/ops/qubit/parametric_ops_single_qubit.py
index c9b19e8d2..29767e482 100644
--- a/pennylane/ops/qubit/parametric_ops_single_qubit.py
+++ b/pennylane/ops/qubit/parametric_ops_single_qubit.py
@@ -499,7 +499,7 @@ class PhaseShift(Operation):
         [RZ(1.234, wires=[0])]
 
         """
-        return [RZ(phi, wires=wires)]
+        return [RZ(phi, wires=wires), qml.GlobalPhase(phi / 2)]
 
     def adjoint(self):
         return PhaseShift(-self.data[0], wires=self.wires)
diff --git a/pennylane/transforms/optimization/optimization_utils.py b/pennylane/transforms/optimization/optimization_utils.py
index b6727b507..85ab917de 100644
--- a/pennylane/transforms/optimization/optimization_utils.py
+++ b/pennylane/transforms/optimization/optimization_utils.py
@@ -190,7 +190,7 @@ def fuse_rot_angles(angles_1, angles_2):
 
 
 def _fuse_global_phases(operations):
-    """Fuse all the global phase operations into single one
+    """Fuse all the global phase operations into single one.
 
     Args:
         operations (list[Operation]): list of operations to be iterated over


Test patch:
diff --git a/tests/ops/qubit/test_parametric_ops.py b/tests/ops/qubit/test_parametric_ops.py
index 34ab79df3..c80aaf170 100644
--- a/tests/ops/qubit/test_parametric_ops.py
+++ b/tests/ops/qubit/test_parametric_ops.py
@@ -264,7 +264,7 @@ class TestDecompositions:
         op = qml.PhaseShift(phi, wires=0)
         res = op.decomposition()
 
-        assert len(res) == 1
+        assert len(res) == 2
 
         assert res[0].name == "RZ"
 
@@ -273,6 +273,10 @@ class TestDecompositions:
 
         decomposed_matrix = res[0].matrix()
         global_phase = np.exp(-1j * phi / 2)[..., np.newaxis, np.newaxis]
+
+        assert res[1].name == "GlobalPhase"
+        assert np.allclose(qml.matrix(res[1]), np.exp(-1j * phi / 2))
+
         assert np.allclose(decomposed_matrix, global_phase * op.matrix(), atol=tol, rtol=0)
 
     def test_phase_decomposition_broadcasted(self, tol):
@@ -281,7 +285,7 @@ class TestDecompositions:
         op = qml.PhaseShift(phi, wires=0)
         res = op.decomposition()
 
-        assert len(res) == 1
+        assert len(res) == 2
 
         assert res[0].name == "RZ"
 
@@ -291,6 +295,9 @@ class TestDecompositions:
         decomposed_matrix = res[0].matrix()
         global_phase = np.exp(-1j * phi / 2)[..., np.newaxis, np.newaxis]
 
+        assert res[1].name == "GlobalPhase"
+        assert np.allclose(qml.matrix(res[1]), np.exp(-1j * phi / 2))
+
         assert np.allclose(decomposed_matrix, global_phase * op.matrix(), atol=tol, rtol=0)
 
     def test_Rot_decomposition(self):
diff --git a/tests/transforms/test_batch_transform.py b/tests/transforms/test_batch_transform.py
index bf6edf8f2..cbd91c302 100644
--- a/tests/transforms/test_batch_transform.py
+++ b/tests/transforms/test_batch_transform.py
@@ -214,9 +214,11 @@ class TestBatchTransform:
         spy_expand.assert_called()
 
         input_tape = spy_transform.call_args[0][1]
-        assert len(input_tape.operations) == 1
+        assert len(input_tape.operations) == 2
         assert input_tape.operations[0].name == "RZ"
+        assert input_tape.operations[1].name == "GlobalPhase"
         assert input_tape.operations[0].parameters == [0.5]
+        assert input_tape.operations[1].parameters == [0.25]
 
     @pytest.mark.parametrize("perform_expansion", [True, False])
     def test_expand_fn_with_kwarg(self, mocker, perform_expansion):
@@ -249,9 +251,17 @@ class TestBatchTransform:
         spy_expand.assert_called()  # The expand_fn of transform_fn always is called
 
         input_tape = spy_transform.call_args[0][1]
-        assert len(input_tape.operations) == 1
-        assert input_tape.operations[0].name == ("RZ" if perform_expansion else "PhaseShift")
-        assert input_tape.operations[0].parameters == [0.5]
+
+        if perform_expansion:
+            assert len(input_tape.operations) == 2
+            assert input_tape.operations[0].name == "RZ"
+            assert input_tape.operations[0].parameters == [0.5]
+            assert input_tape.operations[1].name == "GlobalPhase"
+            assert input_tape.operations[1].parameters == [0.25]
+        else:
+            assert len(input_tape.operations) == 1
+            assert input_tape.operations[0].name == "PhaseShift"
+            assert input_tape.operations[0].parameters == [0.5]
 
     @pytest.mark.parametrize("perform_expansion", [True, False])
     def test_expand_qnode_with_kwarg(self, mocker, perform_expansion):
@@ -287,9 +297,17 @@ class TestBatchTransform:
         spy_transform.assert_called()
         spy_expand.assert_called()  # The expand_fn of transform_fn always is called
         input_tape = spy_transform.call_args[0][1]
-        assert len(input_tape.operations) == 1
-        assert input_tape.operations[0].name == ("RZ" if perform_expansion else "PhaseShift")
-        assert input_tape.operations[0].parameters == [0.5]
+
+        if perform_expansion:
+            assert len(input_tape.operations) == 2
+            assert input_tape.operations[0].name == "RZ"
+            assert input_tape.operations[0].parameters == [0.5]
+            assert input_tape.operations[1].name == "GlobalPhase"
+            assert input_tape.operations[1].parameters == [0.25]
+        else:
+            assert len(input_tape.operations) == 1
+            assert input_tape.operations[0].name == "PhaseShift"
+            assert input_tape.operations[0].parameters == [0.5]
 
     def test_parametrized_transform_tape(self):
         """Test that a parametrized transform can be applied
diff --git a/tests/transforms/test_compile.py b/tests/transforms/test_compile.py
index 8de375628..aec234aef 100644
--- a/tests/transforms/test_compile.py
+++ b/tests/transforms/test_compile.py
@@ -30,6 +30,7 @@ from pennylane.transforms.optimization import (
     merge_rotations,
     single_qubit_fusion,
 )
+from pennylane.transforms.optimization.optimization_utils import _fuse_global_phases
 
 
 def build_qfunc(wires):
@@ -250,7 +251,7 @@ class TestCompileIntegration:
 
         pipeline = [partial(commute_controlled, direction="left"), cancel_inverses, merge_rotations]
 
-        basis_set = ["CNOT", "RX", "RY", "RZ"]
+        basis_set = ["CNOT", "RX", "RY", "RZ", "GlobalPhase"]
 
         transformed_qfunc = compile(qfunc, pipeline=pipeline, basis_set=basis_set)
         transformed_qnode = qml.QNode(transformed_qfunc, dev)
@@ -273,6 +274,7 @@ class TestCompileIntegration:
             "CNOT",
             "RY",
             "CNOT",
+            "GlobalPhase",
         ]
 
         wires_expected = [
@@ -289,9 +291,11 @@ class TestCompileIntegration:
             Wires([wires[1], wires[2]]),
             Wires(wires[2]),
             Wires([wires[1], wires[2]]),
+            Wires([]),
         ]
 
-        compare_operation_lists(transformed_qnode.qtape.operations, names_expected, wires_expected)
+        tansformed_ops = _fuse_global_phases(transformed_qnode.qtape.operations)
+        compare_operation_lists(tansformed_ops, names_expected, wires_expected)
 
     def test_compile_template(self):
         """Test that functions with templates are correctly expanded and compiled."""
diff --git a/tests/transforms/test_tape_expand.py b/tests/transforms/test_tape_expand.py
index e8eceb892..e57d06e78 100644
--- a/tests/transforms/test_tape_expand.py
+++ b/tests/transforms/test_tape_expand.py
@@ -228,9 +228,10 @@ class TestExpandNonunitaryGen:
         tape = qml.tape.QuantumScript.from_queue(q)
         new_tape = qml.transforms.expand_nonunitary_gen(tape)
         assert tape.operations[:2] == new_tape.operations[:2]
-        exp_op = new_tape.operations[2]
+        exp_op, gph_op = new_tape.operations[2:4]
         assert exp_op.name == "RZ" and exp_op.data == (2.1,) and exp_op.wires == qml.wires.Wires(1)
-        assert tape.operations[3:] == new_tape.operations[3:]
+        assert gph_op.name == "GlobalPhase" and gph_op.data == (2.1 * 0.5,)
+        assert tape.operations[3:] == new_tape.operations[4:]
 
     def test_expand_nonunitary_generator(self):
         """Test that a tape with single-parameter operations with
@@ -246,9 +247,10 @@ class TestExpandNonunitaryGen:
         new_tape = qml.transforms.expand_nonunitary_gen(tape)
 
         assert tape.operations[:2] == new_tape.operations[:2]
-        exp_op = new_tape.operations[2]
+        exp_op, gph_op = new_tape.operations[2:4]
         assert exp_op.name == "RZ" and exp_op.data == (2.1,) and exp_op.wires == qml.wires.Wires(1)
-        assert tape.operations[3:] == new_tape.operations[3:]
+        assert gph_op.name == "GlobalPhase" and gph_op.data == (2.1 * 0.5,)
+        assert tape.operations[3:] == new_tape.operations[4:]
 
     def test_decompose_all_nonunitary_generator(self):
         """Test that decompositions actually only contain unitarily
@@ -332,8 +334,9 @@ class TestExpandInvalidTrainable:
 
         assert new_tape.operations[0].name == "RZ"
         assert new_tape.operations[0].grad_method == "A"
-        assert new_tape.operations[1].name == "RY"
-        assert new_tape.operations[2].name == "CNOT"
+        assert new_tape.operations[1].name == "GlobalPhase"
+        assert new_tape.operations[2].name == "RY"
+        assert new_tape.operations[3].name == "CNOT"
 
     def test_nontrainable_nondiff(self, mocker):
         """Test that a circuit with non-differentiable