Skip to content
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@

<h3>Improvements 🛠</h3>

* Added decompositions of the ``RX``, ``RY`` and ``RZ`` rotations into one of the other two, as well
as basis changing Clifford gates, to the graph-based decomposition system.
[(#8569)](https://github.com/PennyLaneAI/pennylane/pull/8569)

* The new graph based decompositions system enabled via :func:`~.decomposition.enable_graph` now supports the following
additional templates.
[(#8520)](https://github.com/PennyLaneAI/pennylane/pull/8520)
Expand Down
8 changes: 2 additions & 6 deletions pennylane/ops/qubit/non_parametric_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,13 +726,9 @@ def compute_diagonalizing_gates(wires: WiresLike) -> list[qml.operation.Operator
**Example**

>>> print(qml.Y.compute_diagonalizing_gates(wires=[0]))
[Z(0), S(0), H(0)]
[Adjoint(S(0)), H(0)]
"""
return [
Z(wires=wires),
S(wires=wires),
Hadamard(wires=wires),
]
return [qml.adjoint(S(wires=wires)), Hadamard(wires=wires)]

@staticmethod
def compute_decomposition(wires: WiresLike) -> list[qml.operation.Operator]:
Expand Down
100 changes: 96 additions & 4 deletions pennylane/ops/qubit/parametric_ops_single_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@
import scipy as sp

import pennylane as qml
from pennylane.decomposition import add_decomps, register_resources, resource_rep
from pennylane.decomposition import (
add_decomps,
adjoint_resource_rep,
change_op_basis_resource_rep,
register_resources,
resource_rep,
)
from pennylane.decomposition.symbolic_decomposition import (
adjoint_rotation,
flip_zero_control,
Expand Down Expand Up @@ -180,7 +186,25 @@ def _rx_to_rz_ry(phi, wires: WiresLike, **__):
qml.RZ(-np.pi / 2, wires=wires)


add_decomps(RX, _rx_to_rot, _rx_to_rz_ry)
def _rx_to_ry_cliff_resources():
return {change_op_basis_resource_rep(qml.S, qml.RY): 1}


@register_resources(_rx_to_ry_cliff_resources)
def _rx_to_ry_cliff(phi, wires: WiresLike, **__):
qml.change_op_basis(qml.S(wires), qml.RY(phi, wires))


def _rx_to_rz_cliff_resources():
return {change_op_basis_resource_rep(qml.Hadamard, qml.RZ, qml.Hadamard): 1}
Copy link
Contributor

@comp-phys-marc comp-phys-marc Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know Hadamard transforms between X and Z bases.



@register_resources(_rx_to_rz_cliff_resources)
def _rx_to_rz_cliff(phi, wires: WiresLike, **__):
qml.change_op_basis(qml.Hadamard(wires), qml.RZ(phi, wires), qml.Hadamard(wires))


add_decomps(RX, _rx_to_rot, _rx_to_rz_ry, _rx_to_ry_cliff, _rx_to_rz_cliff)
add_decomps("Adjoint(RX)", adjoint_rotation)
add_decomps("Pow(RX)", pow_rotation)

Expand Down Expand Up @@ -349,7 +373,41 @@ def _ry_to_rz_rx(phi, wires: WiresLike, **__):
qml.RZ(np.pi / 2, wires=wires)


add_decomps(RY, _ry_to_rot, _ry_to_rz_rx)
def _ry_to_rx_cliff_resources():
return {change_op_basis_resource_rep(adjoint_resource_rep(qml.S), qml.RX, qml.S): 1}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one checks out!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(checked this one by hand...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image



@register_resources(_ry_to_rx_cliff_resources)
def _ry_to_rx_cliff(phi, wires: WiresLike, **__):
qml.change_op_basis(qml.adjoint(qml.S(wires)), qml.RX(phi, wires), qml.S(wires))


def _ry_to_rz_cliff_resources():
return {
change_op_basis_resource_rep(
resource_rep(
qml.ops.op_math.Prod,
resources={adjoint_resource_rep(qml.S): 1, resource_rep(qml.Hadamard): 1},
),
qml.RZ,
resource_rep(
qml.ops.op_math.Prod,
resources={resource_rep(qml.S): 1, resource_rep(qml.Hadamard): 1},
),
): 1
}


@register_resources(_ry_to_rz_cliff_resources)
def _ry_to_rz_cliff(phi, wires: WiresLike, **__):
qml.change_op_basis(
qml.Hadamard(wires) @ qml.adjoint(qml.S(wires)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one checks out!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wires = 0
phi = 1.5 * 3.14

matrix = qml.matrix(qml.S(wires) @ qml.Hadamard(wires) @ qml.RZ(phi, wires) @ qml.Hadamard(wires) @ qml.adjoint(qml.S(wires)))
ry = qml.matrix(qml.RY(phi, wires))
assert np.allclose(matrix, ry)

qml.RZ(phi, wires),
qml.S(wires) @ qml.Hadamard(wires),
)


add_decomps(RY, _ry_to_rot, _ry_to_rz_rx, _ry_to_rx_cliff, _ry_to_rz_cliff)
add_decomps("Adjoint(RY)", adjoint_rotation)
add_decomps("Pow(RY)", pow_rotation)

Expand Down Expand Up @@ -556,7 +614,41 @@ def _rz_to_ry_rx(phi, wires: WiresLike, **__):
qml.RY(-np.pi / 2, wires=wires)


add_decomps(RZ, _rz_to_rot, _rz_to_ry_rx)
def _rz_to_rx_cliff_resources():
return {change_op_basis_resource_rep(qml.Hadamard, qml.RX, qml.Hadamard): 1}


@register_resources(_rz_to_rx_cliff_resources)
def _rz_to_rx_cliff(phi, wires: WiresLike, **__):
qml.change_op_basis(qml.Hadamard(wires), qml.RX(phi, wires), qml.Hadamard(wires))


def _rz_to_ry_cliff_resources():
return {
change_op_basis_resource_rep(
resource_rep(
qml.ops.op_math.Prod,
resources={resource_rep(qml.S): 1, resource_rep(qml.Hadamard): 1},
),
qml.RY,
resource_rep(
qml.ops.op_math.Prod,
resources={adjoint_resource_rep(qml.S): 1, resource_rep(qml.Hadamard): 1},
),
): 1
}


@register_resources(_rz_to_ry_cliff_resources)
def _rz_to_ry_cliff(phi, wires: WiresLike, **__):
qml.change_op_basis(
qml.S(wires) @ qml.Hadamard(wires),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checks out.

wires = 0
phi = 1.5 * 3.14

matrix = qml.matrix(qml.Hadamard(wires) @ qml.adjoint(qml.S(wires)) @ qml.RY(phi, wires) @ qml.S(wires) @ qml.Hadamard(wires))
rz = qml.matrix(qml.RZ(phi, wires))
assert np.allclose(matrix, rz)

qml.RY(phi, wires),
qml.Hadamard(wires) @ qml.adjoint(qml.S(wires)),
)


add_decomps(RZ, _rz_to_rot, _rz_to_ry_rx, _rz_to_rx_cliff, _rz_to_ry_cliff)
add_decomps("Adjoint(RZ)", adjoint_rotation)
add_decomps("Pow(RZ)", pow_rotation)

Expand Down
17 changes: 17 additions & 0 deletions tests/ops/qubit/test_parametric_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,24 @@ def test_validity(self, op):
)


SKIP_ASSERT_VALID = {
qml.GlobalPhase: True,
qml.QubitUnitary: {"skip_differentiation": True},
qml.DiagonalQubitUnitary: {"skip_differentiation": True},
qml.ControlledQubitUnitary: {"skip_differentiation": True},
}


class TestOperations:

@pytest.mark.parametrize("op", ALL_OPERATIONS)
def test_assert_valid(self, op):
kwargs = SKIP_ASSERT_VALID.get(type(op), {})
if kwargs is True:
pytest.skip()

qml.ops.functions.assert_valid(op, **kwargs)
Comment on lines +164 to +170
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this!


@pytest.mark.parametrize("op", ALL_OPERATIONS + BROADCASTED_OPERATIONS)
def test_parametrized_op_copy(self, op, tol):
"""Tests that copied parametrized ops function as expected"""
Expand Down
Loading