diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index ef71f12428e..77e1e034465 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 c9b19e8d216..29767e482d1 100644 --- a/pennylane/ops/qubit/parametric_ops_single_qubit.py +++ b/pennylane/ops/qubit/parametric_ops_single_qubit.py @@ -499,7 +499,7 @@ def compute_decomposition(phi, wires): [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 b6727b50794..85ab917de75 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 diff --git a/tests/ops/qubit/test_parametric_ops.py b/tests/ops/qubit/test_parametric_ops.py index 34ab79df33a..c80aaf170c8 100644 --- a/tests/ops/qubit/test_parametric_ops.py +++ b/tests/ops/qubit/test_parametric_ops.py @@ -264,7 +264,7 @@ def test_phase_decomposition(self, phi, tol): 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 @@ def test_phase_decomposition(self, phi, tol): 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 @@ def test_phase_decomposition_broadcasted(self, tol): 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 @@ def test_phase_decomposition_broadcasted(self, tol): 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 bf6edf8f231..cbd91c30266 100644 --- a/tests/transforms/test_batch_transform.py +++ b/tests/transforms/test_batch_transform.py @@ -214,9 +214,11 @@ def my_transform(self, tape): 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 @@ def my_transform(self, tape, **kwargs): 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 @@ def qnode(x): 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 8de375628db..aec234aefc2 100644 --- a/tests/transforms/test_compile.py +++ b/tests/transforms/test_compile.py @@ -30,6 +30,7 @@ merge_rotations, single_qubit_fusion, ) +from pennylane.transforms.optimization.optimization_utils import _fuse_global_phases def build_qfunc(wires): @@ -250,7 +251,7 @@ def test_compile_decompose_into_basis_gates(self, wires): 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 @@ def test_compile_decompose_into_basis_gates(self, wires): "CNOT", "RY", "CNOT", + "GlobalPhase", ] wires_expected = [ @@ -289,9 +291,11 @@ def test_compile_decompose_into_basis_gates(self, wires): 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 e8eceb89292..e57d06e789c 100644 --- a/tests/transforms/test_tape_expand.py +++ b/tests/transforms/test_tape_expand.py @@ -228,9 +228,10 @@ def generator(self): 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 @@ def test_expand_nonunitary_generator(self): 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 NonDiffPhaseShift(qml.PhaseShift): 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