Repository: PennyLaneAI/pennylane
Pull request #5046 (2024-01-11 19:26:19)

remove support for transform arguments without partial in decorators
**Context:**
We used to advise users to pass transforms additional arguments positionally in transforms being used as decorators. Now, it's forbidden (they need to use `functools.partial`). It has been deprecated for 2 releases, so it's time to remove it.

**Description of the Change:**
Raise a `TransformError` if someone uses the old transform decorator syntax

**Benefits:**
Less support for old style of doing things, helps us keep our code cleaner

**Possible Drawbacks:**
It felt intuitive for me to turn the warning into an explicit error, but usually we just remove code outright when doing removals. This constructor doesn't have an intuitive way of just removing this functionality, so I feel like raises an error explicitly makes sense

[sc-51278]

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5046?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:
> Comparison is base [(`c01cb4b`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/c01cb4b96f758a9f1a0c4e422115829b80e113cb?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.67% compared to head [(`3f2abc3`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5046?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.66%.


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


```diff
@@            Coverage Diff             @@
##           master    #5046      +/-   ##
==========================================
- Coverage   99.67%   99.66%   -0.01%     
==========================================
  Files         394      394              
  Lines       35670    35396     -274     
==========================================
- Hits        35554    35279     -275     
- Misses        116      117       +1     
```



</details>

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


Patch:
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index 0ded796dd..f267a0c10 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -48,6 +48,37 @@ Pending deprecations
   - Deprecated in v0.34
   - Will be removed in v0.35
 
+* ``qml.ExpvalCost`` has been deprecated, and usage will now raise a warning.
+
+  - Deprecated in v0.24
+  - Will be removed in v0.35
+
+  Instead, it is recommended to simply
+  pass Hamiltonians to the ``qml.expval`` function inside QNodes:
+
+  .. code-block:: python
+
+    @qml.qnode(dev)
+    def ansatz(params):
+        some_qfunc(params)
+        return qml.expval(Hamiltonian)
+
+* ``ClassicalShadow.entropy()`` no longer needs an ``atol`` keyword as a better
+  method to estimate entropies from approximate density matrix reconstructions
+  (with potentially negative eigenvalues) has been implemented.
+
+  - Deprecated in v0.34
+  - Will be removed in v0.35
+
+* ``PauliWord`` and ``PauliSentence`` no longer use ``*`` for matrix and tensor products,
+  but instead use ``@`` to conform with the PennyLane convention.
+
+  - Deprecated in v0.35
+  - Will be removed in v0.36
+
+Completed deprecation cycles
+----------------------------
+
 * Passing additional arguments to a transform that decorates a QNode should now be done through use
   of ``functools.partial``. For example, the :func:`~pennylane.metric_tensor` transform has an
   optional ``approx`` argument which should now be set using:
@@ -61,7 +92,7 @@ Pending deprecations
     def circuit(weights):
         ...
 
-  The previously-recommended approach is now deprecated:
+  The previously-recommended approach is now removed:
 
   .. code-block:: python
 
@@ -81,38 +112,7 @@ Pending deprecations
     transformed_circuit = qml.metric_tensor(circuit, approx="block-diag")
 
   - Deprecated in v0.33
-  - Will be removed in v0.35
-
-* ``qml.ExpvalCost`` has been deprecated, and usage will now raise a warning.
-
-  - Deprecated in v0.24
-  - Will be removed in v0.35
-
-  Instead, it is recommended to simply
-  pass Hamiltonians to the ``qml.expval`` function inside QNodes:
-
-  .. code-block:: python
-
-    @qml.qnode(dev)
-    def ansatz(params):
-        some_qfunc(params)
-        return qml.expval(Hamiltonian)
-
-* ``ClassicalShadow.entropy()`` no longer needs an ``atol`` keyword as a better 
-  method to estimate entropies from approximate density matrix reconstructions
-  (with potentially negative eigenvalues) has been implemented.
-
-  - Deprecated in v0.34
-  - Will be removed in v0.35
-
-* ``PauliWord`` and ``PauliSentence`` no longer use ``*`` for matrix and tensor products,
-  but instead use ``@`` to conform with the PennyLane convention.
-
-  - Deprecated in v0.35
-  - Will be removed in v0.36
-
-Completed deprecation cycles
-----------------------------
+  - Removed in v0.35
 
 * Specifying ``control_values`` passed to ``qml.ctrl`` as a string is no longer supported.
 
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 5fda47973..a01dc954d 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -41,6 +41,10 @@
 
 <h3>Breaking changes 💔</h3>
 
+* Passing additional arguments to a transform that decorates a QNode must be done through the use
+  of `functools.partial`.
+  [(#5046)](https://github.com/PennyLaneAI/pennylane/pull/5046)
+
 <h3>Deprecations 👋</h3>
 
 * Matrix and tensor products between `PauliWord` and `PauliSentence` instances are done using the `@` operator, `*` will be used only for scalar multiplication.
diff --git a/pennylane/drawer/draw.py b/pennylane/drawer/draw.py
index f153c01e5..4ccd9cc61 100644
--- a/pennylane/drawer/draw.py
+++ b/pennylane/drawer/draw.py
@@ -179,8 +179,10 @@ def draw(
 
     .. code-block:: python
 
-        @qml.gradients.param_shift(shifts=[(0.1,)])
-        @qml.qnode(qml.device('lightning.qubit', wires=1))
+        from functools import partial
+
+        @partial(qml.gradients.param_shift, shifts=[(0.1,)])
+        @qml.qnode(qml.device('default.qubit', wires=1))
         def transformed_circuit(x):
             qml.RX(x, wires=0)
             return qml.expval(qml.PauliZ(0))
diff --git a/pennylane/transforms/core/transform_dispatcher.py b/pennylane/transforms/core/transform_dispatcher.py
index 3eea403e8..147fddce7 100644
--- a/pennylane/transforms/core/transform_dispatcher.py
+++ b/pennylane/transforms/core/transform_dispatcher.py
@@ -127,28 +127,14 @@ class TransformDispatcher:
         #
         # result = some_transform(*transform_args)(qnode)(*qnode_args)
 
-        warnings.warn(
+        raise TransformError(
             "Decorating a QNode with @transform_fn(**transform_kwargs) has been "
-            "deprecated and will be removed in a future version. Please decorate "
-            "with @functools.partial(transform_fn, **transform_kwargs) instead, "
-            "or call the transform directly using qnode = transform_fn(qnode, "
+            "removed. Please decorate with @functools.partial(transform_fn, **transform_kwargs) "
+            "instead, or call the transform directly using qnode = transform_fn(qnode, "
             "**transform_kwargs). Visit the deprecations page for more details: "
-            "https://docs.pennylane.ai/en/stable/development/deprecations.html",
-            qml.PennyLaneDeprecationWarning,
+            "https://docs.pennylane.ai/en/stable/development/deprecations.html#completed-deprecation-cycles",
         )
 
-        if obj is not None:
-            targs = (obj, *targs)
-
-        def wrapper(obj):
-            return self(obj, *targs, **tkwargs)
-
-        wrapper.__doc__ = (
-            f"Partial of transform {self._transform} with bound arguments and keyword arguments."
-        )
-
-        return wrapper
-
     def __repr__(self):
         return f"<transform: {self._transform.__name__}>"
 


Test patch:
diff --git a/tests/drawer/test_draw.py b/tests/drawer/test_draw.py
index c9308ab78..1678f47bb 100644
--- a/tests/drawer/test_draw.py
+++ b/tests/drawer/test_draw.py
@@ -181,7 +181,7 @@ class TestMatrixParameters:
     def test_matrix_parameters_batch_transform(self):
         """Test matrix parameters only printed once after a batch transform."""
 
-        @qml.gradients.param_shift(shifts=[(0.2,)])  # pylint:disable=no-value-for-parameter
+        @partial(qml.gradients.param_shift, shifts=[(0.2,)])
         @qml.qnode(qml.device("default.qubit", wires=2))
         def matrices_circuit(x):
             qml.StatePrep([1.0, 0.0, 0.0, 0.0], wires=(0, 1))
@@ -819,17 +819,10 @@ class TestMidCircuitMeasurements:
         assert drawing == expected_drawing
 
 
-@pytest.mark.parametrize(
-    "transform",
-    [
-        qml.gradients.param_shift(shifts=[(0.2,)]),  # pylint:disable=no-value-for-parameter
-        partial(qml.gradients.param_shift, shifts=[(0.2,)]),
-    ],
-)
-def test_draw_batch_transform(transform):
+def test_draw_batch_transform():
     """Test that drawing a batch transform works correctly."""
 
-    @transform
+    @partial(qml.gradients.param_shift, shifts=[(0.2,)])
     @qml.qnode(qml.device("default.qubit", wires=1))
     def circ(x):
         qml.Hadamard(wires=0)
diff --git a/tests/shadow/test_shadow_transforms.py b/tests/shadow/test_shadow_transforms.py
index 3fe0c9056..51a6ab521 100644
--- a/tests/shadow/test_shadow_transforms.py
+++ b/tests/shadow/test_shadow_transforms.py
@@ -14,6 +14,7 @@
 """Unit tests for the classical shadows transforms"""
 # pylint: disable=too-few-public-methods
 
+from functools import partial
 import pytest
 
 import pennylane as qml
@@ -142,7 +143,7 @@ class TestStateForward:
     def test_max_entangled_state(self, wires, diffable):
         """Test that the state reconstruction is correct for a maximally entangled state"""
         circuit = max_entangled_circuit(wires)
-        circuit = qml.shadows.shadow_state(wires=range(wires), diffable=diffable)(circuit)
+        circuit = qml.shadows.shadow_state(circuit, wires=range(wires), diffable=diffable)
 
         actual = circuit()
         expected = np.zeros((2**wires, 2**wires))
@@ -157,7 +158,7 @@ class TestStateForward:
         wires_list = [[0], [0, 1]]
 
         circuit = max_entangled_circuit(3)
-        circuit = qml.shadows.shadow_state(wires=wires_list, diffable=diffable)(circuit)
+        circuit = qml.shadows.shadow_state(circuit, wires=wires_list, diffable=diffable)
 
         actual = circuit()
 
@@ -212,7 +213,7 @@ class TestStateForwardInterfaces:
     def test_qft_state(self, interface, diffable):
         """Test that the state reconstruction is correct for a QFT state"""
         circuit = qft_circuit(3, interface=interface)
-        circuit = qml.shadows.shadow_state(wires=[0, 1, 2], diffable=diffable)(circuit)
+        circuit = qml.shadows.shadow_state(circuit, wires=[0, 1, 2], diffable=diffable)
 
         actual = circuit()
         expected = np.exp(np.arange(8) * 2j * np.pi / 8) / np.sqrt(8)
@@ -236,7 +237,7 @@ class TestStateBackward:
         shadow_circuit = basic_entangler_circuit(3, shots=20000, interface="autograd")
 
         sub_wires = [[0, 1], [1, 2]]
-        shadow_circuit = qml.shadows.shadow_state(wires=sub_wires, diffable=True)(shadow_circuit)
+        shadow_circuit = qml.shadows.shadow_state(shadow_circuit, wires=sub_wires, diffable=True)
 
         x = np.array(self.x, requires_grad=True)
 
@@ -260,7 +261,7 @@ class TestStateBackward:
         shadow_circuit = basic_entangler_circuit(3, shots=20000, interface="jax")
 
         sub_wires = [[0, 1], [1, 2]]
-        shadow_circuit = qml.shadows.shadow_state(wires=sub_wires, diffable=True)(shadow_circuit)
+        shadow_circuit = qml.shadows.shadow_state(shadow_circuit, wires=sub_wires, diffable=True)
 
         x = jnp.array(self.x)
         actual = jax.jacobian(lambda x: qml.math.real(qml.math.stack(shadow_circuit(x))))(x)
@@ -279,7 +280,7 @@ class TestStateBackward:
         shadow_circuit = basic_entangler_circuit(3, shots=20000, interface="tf")
 
         sub_wires = [[0, 1], [1, 2]]
-        shadow_circuit = qml.shadows.shadow_state(wires=sub_wires, diffable=True)(shadow_circuit)
+        shadow_circuit = qml.shadows.shadow_state(shadow_circuit, wires=sub_wires, diffable=True)
 
         x = tf.Variable(self.x)
 
@@ -307,7 +308,7 @@ class TestStateBackward:
         shadow_circuit = basic_entangler_circuit(3, shots=20000, interface="torch")
 
         sub_wires = [[0, 1], [1, 2]]
-        shadow_circuit = qml.shadows.shadow_state(wires=sub_wires, diffable=True)(shadow_circuit)
+        shadow_circuit = qml.shadows.shadow_state(shadow_circuit, wires=sub_wires, diffable=True)
 
         x = torch.tensor(self.x, requires_grad=True)
 
@@ -382,7 +383,7 @@ class TestExpvalTransform:
         return shadows"""
         dev = qml.device("default.qubit", wires=1, shots=100)
 
-        @qml.shadows.shadow_expval(qml.PauliZ(0))
+        @partial(qml.shadows.shadow_expval, H=qml.PauliZ(0))
         @qml.qnode(dev)
         def circuit():
             qml.Hadamard(0)
diff --git a/tests/transforms/test_experimental/test_transform_dispatcher.py b/tests/transforms/test_experimental/test_transform_dispatcher.py
index 8e77553e0..b7a5160bb 100644
--- a/tests/transforms/test_experimental/test_transform_dispatcher.py
+++ b/tests/transforms/test_experimental/test_transform_dispatcher.py
@@ -257,19 +257,18 @@ class TestTransformDispatcher:  # pylint: disable=too-many-public-methods
         )
 
     @pytest.mark.parametrize("valid_transform", valid_transforms)
-    def test_integration_dispatcher_with_valid_transform_decorator(self, valid_transform):
-        """Test that a warning is raised with the transform function and that the transform dispatcher returns
-        the right object."""
+    def test_integration_dispatcher_with_valid_transform_decorator_fails(self, valid_transform):
+        """Test that an error is raised with the transform function."""
 
         dispatched_transform = transform(valid_transform)
         targs = [0]
 
-        msg = r"Decorating a QNode with @transform_fn\(\*\*transform_kwargs\) has been deprecated"
-        with pytest.warns(UserWarning, match=msg):
+        msg = r"Decorating a QNode with @transform_fn\(\*\*transform_kwargs\) has been removed"
+        with pytest.raises(TransformError, match=msg):
 
             @dispatched_transform(targs)
             @qml.qnode(device=dev)
-            def qnode_circuit(a):
+            def qnode_circuit(a):  # pylint:disable=unused-variable
                 """QNode circuit."""
                 qml.Hadamard(wires=0)
                 qml.CNOT(wires=[0, 1])
@@ -277,12 +276,6 @@ class TestTransformDispatcher:  # pylint: disable=too-many-public-methods
                 qml.RZ(a, wires=1)
                 return qml.expval(qml.PauliZ(wires=0))
 
-        assert isinstance(qnode_circuit, qml.QNode)
-        assert isinstance(qnode_circuit.transform_program, qml.transforms.core.TransformProgram)
-        assert isinstance(
-            qnode_circuit.transform_program.pop_front(), qml.transforms.core.TransformContainer
-        )
-
     def test_equality(self):
         """Tests that we can compare TransformContainer objects with the '==' and '!=' operators."""