Skip to content
56 changes: 30 additions & 26 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@

<h3>Improvements 🛠</h3>

* Arithmetic dunder methods (`__add__`, `__mul__`, `__rmul__`) have been added to
:class:`~.transforms.core.TransformDispatcher`, :class:`~.transforms.core.TransformContainer`,
and :class:`~.transforms.core.TransformProgram` to enable intuitive composition of transform
* 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)

* Arithmetic dunder methods (`__add__`, `__mul__`, `__rmul__`) have been added to
:class:`~.transforms.core.TransformDispatcher`, :class:`~.transforms.core.TransformContainer`,
and :class:`~.transforms.core.TransformProgram` to enable intuitive composition of transform
programs using `+` and `*` operators.
[(#8703)](https://github.com/PennyLaneAI/pennylane/pull/8703)

Expand All @@ -52,10 +56,10 @@
with :func:`~.specs` and :func:`~.drawer.draw` instead of having to increment ``level``
by integer amounts when not using any Catalyst passes.
[(#8684)](https://github.com/PennyLaneAI/pennylane/pull/8684)

The :func:`~.marker` function works like a transform in PennyLane, and can be deployed as
a decorator on top of QNodes:

```
from functools import partial

Expand Down Expand Up @@ -106,15 +110,15 @@
[(#8516)](https://github.com/PennyLaneAI/pennylane/pull/8516)
[(#8555)](https://github.com/PennyLaneAI/pennylane/pull/8555)
[(#8558)](https://github.com/PennyLaneAI/pennylane/pull/8558)
[(#8538)](https://github.com/PennyLaneAI/pennylane/pull/8538)
[(#8538)](https://github.com/PennyLaneAI/pennylane/pull/8538)
[(#8534)](https://github.com/PennyLaneAI/pennylane/pull/8534)
[(#8582)](https://github.com/PennyLaneAI/pennylane/pull/8582)
[(#8543)](https://github.com/PennyLaneAI/pennylane/pull/8543)
[(#8554)](https://github.com/PennyLaneAI/pennylane/pull/8554)
[(#8616)](https://github.com/PennyLaneAI/pennylane/pull/8616)
[(#8602)](https://github.com/PennyLaneAI/pennylane/pull/8602)
[(#8600)](https://github.com/PennyLaneAI/pennylane/pull/8600)
[(#8601)](https://github.com/PennyLaneAI/pennylane/pull/8601)
[(#8601)](https://github.com/PennyLaneAI/pennylane/pull/8601)
[(#8595)](https://github.com/PennyLaneAI/pennylane/pull/8595)
[(#8586)](https://github.com/PennyLaneAI/pennylane/pull/8586)
[(#8614)](https://github.com/PennyLaneAI/pennylane/pull/8614)
Expand Down Expand Up @@ -207,7 +211,7 @@
* `qml.transforms.map_wires` no longer supports plxpr transforms.
[(#8683)](https://github.com/PennyLaneAI/pennylane/pull/8683)

* ``QuantumScript.to_openqasm`` has been removed. Please use ``qml.to_openqasm`` instead. This removes duplicated
* ``QuantumScript.to_openqasm`` has been removed. Please use ``qml.to_openqasm`` instead. This removes duplicated
functionality for converting a circuit to OpenQASM code.
[(#8499)](https://github.com/PennyLaneAI/pennylane/pull/8499)

Expand Down Expand Up @@ -299,9 +303,9 @@
We recommend upgrading your version of NumPy to benefit from enhanced support and features.
[(#8578)](https://github.com/PennyLaneAI/pennylane/pull/8578)
[(#8497)](https://github.com/PennyLaneAI/pennylane/pull/8497)
* The ``custom_decomps`` keyword argument to ``qml.device`` has been deprecated and will be removed
in 0.45. Instead, with ``qml.decomposition.enable_graph()``, new decomposition rules can be defined as

* The ``custom_decomps`` keyword argument to ``qml.device`` has been deprecated and will be removed
in 0.45. Instead, with ``qml.decomposition.enable_graph()``, new decomposition rules can be defined as
quantum functions with registered resources. See :mod:`pennylane.decomposition` for more details.

* `qml.measure`, `qml.measurements.MidMeasureMP`, `qml.measurements.MeasurementValue`,
Expand Down Expand Up @@ -340,22 +344,22 @@
and the function should be passed to the ``stopping_condition`` argument instead.
[(#8533)](https://github.com/PennyLaneAI/pennylane/pull/8533)

The example below illustrates how you can provide a function as the ``stopping_condition`` in addition to providing a
``gate_set``. The decomposition of each operator will then stop once it reaches the gates in the ``gate_set`` or the
The example below illustrates how you can provide a function as the ``stopping_condition`` in addition to providing a
``gate_set``. The decomposition of each operator will then stop once it reaches the gates in the ``gate_set`` or the
``stopping_condition`` is satisfied.

```python
import pennylane as qml
from functools import partial

@partial(qml.transforms.decompose, gate_set={"H", "T", "CNOT"}, stopping_condition=lambda op: len(op.wires) <= 2)
@qml.qnode(qml.device("default.qubit"))
def circuit():
qml.Hadamard(wires=[0])
qml.Toffoli(wires=[0,1,2])
return qml.expval(qml.Z(0))
```

```pycon
>>> print(qml.draw(circuit)())
0: ──H────────╭●───────────╭●────╭●──T──╭●─┤ <Z>
Expand All @@ -371,34 +375,34 @@
* Bump `jax` version to `0.7.0` for `capture` module.
[(#8701)](https://github.com/PennyLaneAI/pennylane/pull/8701)

* Improve error handling when using PennyLane's experimental program capture functionality with an incompatible JAX version.
* Improve error handling when using PennyLane's experimental program capture functionality with an incompatible JAX version.
[(#8723)](https://github.com/PennyLaneAI/pennylane/pull/8723)

* Bump `autoray` package version to `0.8.2`.
[(#8674)](https://github.com/PennyLaneAI/pennylane/pull/8674)

* Update the schedule of nightly TestPyPI uploads to occur at the end rather than the beginning of all week days.
[(#8672)](https://github.com/PennyLaneAI/pennylane/pull/8672)

* Add workflow to bump Catalyst and Lightning versions in the RC branch, create a new release tag and draft release, tag the RC branch, and create a PR to merge the RC branch into master.
[(#8352)](https://github.com/PennyLaneAI/pennylane/pull/8352)

* Added `MCM_METHOD` and `POSTSELECT_MODE` `StrEnum` objects to improve validation and handling of `MCMConfig` creation.
[(#8596)](https://github.com/PennyLaneAI/pennylane/pull/8596)

* Updated various docstrings to be compatible with the new documentation testing approach.
[(#8635)](https://github.com/PennyLaneAI/pennylane/pull/8635)

* In program capture, transforms now have a single transform primitive that have a `transform` param that stores
the `TransformDispatcher`. Before, each transform had its own primitive stored on the
the `TransformDispatcher`. Before, each transform had its own primitive stored on the
`TransformDispatcher._primitive` private property. It proved difficult to keep maintaining dispatch behaviour
for every single transform.
[(#8576)](https://github.com/PennyLaneAI/pennylane/pull/8576)
[(#8639)](https://github.com/PennyLaneAI/pennylane/pull/8639)

* Updated documentation check workflow to run on pull requests on `v[0-9]+\.[0-9]+\.[0-9]+-docs` branches.
[(#8590)](https://github.com/PennyLaneAI/pennylane/pull/8590)

* When program capture is enabled, there is no longer caching of the jaxpr on the QNode.
[(#8629)](https://github.com/PennyLaneAI/pennylane/pull/8629)

Expand Down Expand Up @@ -480,7 +484,7 @@
which is parsed and transformed with xDSL.
[(#8587)](https://github.com/PennyLaneAI/pennylane/pull/8587)

* The :func:`~pennylane.compiler.python_compiler.transforms.convert_to_mbqc_formalism_pass` now
* The :func:`~pennylane.compiler.python_compiler.transforms.convert_to_mbqc_formalism_pass` now
supports :class:`~xdsl.dialects.scf.IndexSwitchOp` in IR and ignores regions that have no body.
[(#8632)](https://github.com/PennyLaneAI/pennylane/pull/8632)

Expand Down Expand Up @@ -532,12 +536,12 @@ A warning message has been added to :doc:`Building a plugin <../development/plug
* Update `interface-unit-tests.yml` to use its input parameter `pytest_additional_args` when running pytest.
[(#8705)](https://github.com/PennyLaneAI/pennylane/pull/8705)

* Fixes a bug where in `resolve_work_wire_type` we incorrectly returned a value of `zeroed` if `both work_wires`
* Fixes a bug where in `resolve_work_wire_type` we incorrectly returned a value of `zeroed` if `both work_wires`
and `base_work_wires` were empty, causing an incorrect work wire type.
[(#8718)](https://github.com/PennyLaneAI/pennylane/pull/8718)

* The warnings-as-errors CI action was failing due to an incompatibility between `pytest-xdist` and `pytest-benchmark`.
Disabling the benchmark package allows the tests to be collected an executed.
* The warnings-as-errors CI action was failing due to an incompatibility between `pytest-xdist` and `pytest-benchmark`.
Disabling the benchmark package allows the tests to be collected an executed.
[(#8699)](https://github.com/PennyLaneAI/pennylane/pull/8699)

* Adds an `expand_transform` to `param_shift_hessian` to pre-decompose
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* Unlike the current tape-based implementation of the transform, it doesn't allow for diagonalization of a subset
of observables.
* Unlike the current tape-based implementation of the transform, conversion to measurements based on eigvals
and wires (rather than the PauliZ observable) is not currently supported.
and wires (rather than the PauliZ observable) is not currently supported.
* Unlike the tape-based implementation, this pass will NOT raise an error if given a circuit that is invalid because
it contains non-commuting measurements. It should be assumed that this transform results in incorrect outputs unless
split_non_commuting is applied to break non-commuting measurements into separate tapes.
Expand All @@ -35,6 +35,7 @@
from xdsl.rewriter import InsertPoint

from pennylane.ops import Hadamard, PauliX, PauliY
from pennylane.ops.op_math import Adjoint

from ...dialects.quantum import (
CustomOp,
Expand All @@ -51,17 +52,21 @@
def _generate_mapping():
_gate_map = {}
_params_map = {}
_adj_map = {}

for op in PauliX(0), PauliY(0), Hadamard(0):
diagonalizing_gates = op.diagonalizing_gates()

_gate_map[op.name] = [gate.name for gate in diagonalizing_gates]
_gate_map[op.name] = [
(gate.base if isinstance(gate, Adjoint) else gate).name for gate in diagonalizing_gates
]
_params_map[op.name] = [gate.data for gate in diagonalizing_gates]
_adj_map[op.name] = [isinstance(gate, Adjoint) for gate in diagonalizing_gates]

return _gate_map, _params_map
return _gate_map, _params_map, _adj_map


_gate_map, _params_map = _generate_mapping()
_gate_map, _params_map, _adj_map = _generate_mapping()


def _diagonalize(obs: NamedObsOp) -> bool:
Expand All @@ -87,12 +92,13 @@ def match_and_rewrite(self, observable: NamedObsOp, rewriter: pattern_rewriter.P

diagonalizing_gates = _gate_map[observable.type.data]
params = _params_map[observable.type.data]
adjoints = _adj_map[observable.type.data]

qubit = observable.qubit

insert_point = InsertPoint.before(observable)

for name, op_data in zip(diagonalizing_gates, params):
for name, op_data, adj in zip(diagonalizing_gates, params, adjoints):
if op_data:
param_ssa_values = []
for param in op_data:
Expand All @@ -102,9 +108,11 @@ def match_and_rewrite(self, observable: NamedObsOp, rewriter: pattern_rewriter.P
rewriter.insert_op(paramOp, insert_point)
param_ssa_values.append(paramOp.results[0])

gate = CustomOp(in_qubits=qubit, gate_name=name, params=param_ssa_values)
gate = CustomOp(
in_qubits=qubit, gate_name=name, params=param_ssa_values, adjoint=adj
)
else:
gate = CustomOp(in_qubits=qubit, gate_name=name)
gate = CustomOp(in_qubits=qubit, gate_name=name, adjoint=adj)

rewriter.insert_op(gate, insert_point)

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 @@ -25,7 +25,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 @@ -190,6 +196,24 @@ def _rx_to_rz_ry(phi, wires: WiresLike, **__):
qml.RZ(-np.pi / 2, wires=wires)


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))


def _rx_to_ppr_resources():
return {resource_rep(qml.PauliRot, pauli_word="X"): 1}

Expand All @@ -199,7 +223,7 @@ def _rx_to_ppr(phi, wires, **_):
qml.PauliRot(phi, "X", wires=wires)


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

Expand Down Expand Up @@ -375,6 +399,40 @@ def _ry_to_rz_rx(phi, wires: WiresLike, **__):
qml.RZ(np.pi / 2, wires=wires)


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),
)


def _ry_to_ppr_resources():
return {resource_rep(qml.PauliRot, pauli_word="Y"): 1}

Expand All @@ -384,7 +442,7 @@ def _ry_to_ppr(phi, wires, **_):
qml.PauliRot(phi, "Y", wires=wires)


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

Expand Down Expand Up @@ -598,6 +656,40 @@ def _rz_to_ry_rx(phi, wires: WiresLike, **__):
qml.RY(-np.pi / 2, wires=wires)


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)),
)


def _rz_to_ppr_resources():
return {resource_rep(qml.PauliRot, pauli_word="Z"): 1}

Expand All @@ -607,7 +699,7 @@ def _rz_to_ppr(phi, wires, **_):
qml.PauliRot(phi, "Z", wires=wires)


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

Expand Down
Loading