Repository: PennyLaneAI/pennylane
Pull request #5676 (2024-05-10 19:56:11)

Deprecate `map_batch_transform`
**Context:** ***Part of  deprecations and removals for pennylane-0.37***

**Description of the Change:** Deprecating ``qml.transforms.map_batch_transform``, since a transform can be applied directly to a batch of tapes. See #5163 and #5212.

[sc-58987]

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5676?dropdown=coverage&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:
> Project coverage is 99.67%. Comparing base [(`be35a7b`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/be35a7b1780a1b1d818e9cfa300df214ce838f53?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`e8515f6`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5676?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


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


```diff
@@            Coverage Diff             @@
##           master    #5676      +/-   ##
==========================================
- Coverage   99.68%   99.67%   -0.01%     
==========================================
  Files         414      414              
  Lines       38814    38524     -290     
==========================================
- Hits        38691    38400     -291     
- Misses        123      124       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5676?dropdown=coverage&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 35936e6a6..5a8f0cbac 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -9,6 +9,11 @@ deprecations are listed below.
 Pending deprecations
 --------------------
 
+* ``qml.transforms.map_batch_transform`` is deprecated, since transforms can be applied directly to a batch of tapes.
+  See :func:`~.pennylane.transform` for more information.
+
+  - Deprecated in v0.37
+
 New operator arithmetic deprecations
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 7cbbcd67b..0b6587eac 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -100,6 +100,9 @@
 
 <h3>Deprecations 👋</h3>
 
+* ``qml.transforms.map_batch_transform`` is deprecated, since a transform can be applied directly to a batch of tapes.
+  [(#5676)](https://github.com/PennyLaneAI/pennylane/pull/5676)
+
 <h3>Documentation 📝</h3>
 
 * A small typo was fixed in the docstring for `qml.sample`.
diff --git a/pennylane/transforms/batch_transform.py b/pennylane/transforms/batch_transform.py
index 43aeede21..9d07f1d5e 100644
--- a/pennylane/transforms/batch_transform.py
+++ b/pennylane/transforms/batch_transform.py
@@ -14,6 +14,7 @@
 """Contains tools and decorators for registering batch transforms."""
 # pylint: disable=too-few-public-methods
 
+import warnings
 from typing import Callable, Tuple
 
 import pennylane as qml
@@ -64,7 +65,19 @@ def map_batch_transform(
     >>> dev = qml.device("default.qubit", wires=2)
     >>> fn(qml.execute(tapes, dev, qml.gradients.param_shift))
     [array(0.99500417), array(0.8150893)]
+
+    .. warning::
+        qml.transforms.map_batch_transform is deprecated and will be removed in a future release.
+        Instead, a transform can be applied directly to a batch of tapes. See :func:`~.pennylane.transform` for more details.
     """
+
+    warnings.warn(
+        "qml.transforms.map_batch_transform is deprecated."
+        "Instead, a transform can be applied directly to a batch of tapes."
+        "See :func:`~.pennylane.transform` for more details.",
+        qml.PennyLaneDeprecationWarning,
+    )
+
     execution_tapes = []
     batch_fns = []
     tape_counts = []
diff --git a/pennylane/transforms/core/transform.py b/pennylane/transforms/core/transform.py
index f2327bd1d..fc4c3b721 100644
--- a/pennylane/transforms/core/transform.py
+++ b/pennylane/transforms/core/transform.py
@@ -126,6 +126,48 @@ def transform(
     and creates a sequence of tapes to be executed. The execution results are then post-processed in the
     reverse order of the transform program to obtain the final results.
 
+    .. details::
+        :title: Dispatch a transform onto a batch of tapes
+
+        We can compose multiple transforms when working in the tape paradigm and apply them to more than one tape.
+        The following example demonstrates how to apply a transform to a batch of tapes.
+
+        **Example**
+
+        In this example, we apply sequentially a transform to a tape and another one to a batch of tapes.
+        We then execute the transformed tapes on a device and post-process the results.
+
+        .. code-block:: python
+
+            import pennylane as qml
+
+            H = qml.PauliY(2) @ qml.PauliZ(1) + 0.5 * qml.PauliZ(2) + qml.PauliZ(1)
+            measurement = [qml.expval(H)]
+            operations = [qml.Hadamard(0), qml.RX(0.2, 0), qml.RX(0.6, 0), qml.CNOT((0, 1))]
+            tape = qml.tape.QuantumTape(operations, measurement)
+
+            batch1, function1 = qml.transforms.hamiltonian_expand(tape)
+            batch2, function2 = qml.transforms.merge_rotations(batch1)
+
+            dev = qml.device("default.qubit", wires=3)
+            result = dev.execute(batch2)
+
+        The first ``hamiltonian_expand`` transform splits the original tape, returning a batch of tapes ``batch1`` and a processing function ``function1``.
+        The second ``merge_rotations`` transform is applied to the batch of tapes returned by the first transform.
+        It returns a new batch of tapes ``batch2``, each of which has been transformed by the second transform, and a processing function ``function2``.
+
+        >>> batch2
+        (<QuantumTape: wires=[0, 1, 2], params=2>,
+        <QuantumTape: wires=[0, 1, 2], params=1>)
+
+        >>> type(function2)
+        function
+
+        We can combine the processing functions to post-process the results of the execution.
+
+        >>> function1(function2(result))
+        [array(0.5)]
+
     .. details::
         :title: Signature of a transform
 
@@ -134,6 +176,7 @@ def transform(
         - :class:`pennylane.QNode`
         - a quantum function (callable)
         - :class:`pennylane.tape.QuantumTape`
+        - a batch of :class:`pennylane.tape.QuantumTape`
         - :class:`pennylane.devices.Device`.
 
         For each object, the transform will be applied in a different way, but it always preserves the underlying
@@ -157,6 +200,10 @@ def transform(
           :class:`~.QuantumTape`. It returns a sequence of :class:`~.QuantumTape` and a processing
           function to be applied after execution.
 
+        - For a batch of :class:`pennylane.tape.QuantumTape`, the quantum transform is mapped across all the tapes.
+          It returns a sequence of :class:`~.QuantumTape` and a processing function to be applied after execution.
+          Each tape in the sequence is transformed by the transform.
+
         - For a :class:`~.devices.Device`, the transform is added to the device's transform program
           and a transformed :class:`pennylane.devices.Device` is returned. The transform is added
           to the end of the device program and will be last in the overall transform program.
diff --git a/pennylane/transforms/core/transform_dispatcher.py b/pennylane/transforms/core/transform_dispatcher.py
index f8263fa42..b9a4f4a71 100644
--- a/pennylane/transforms/core/transform_dispatcher.py
+++ b/pennylane/transforms/core/transform_dispatcher.py
@@ -324,13 +324,13 @@ class TransformDispatcher:
         return TransformedDevice(original_device, self._transform)
 
     def _batch_transform(self, original_batch, targs, tkwargs):
-        """Apply the transform on a batch of tapes"""
+        """Apply the transform on a batch of tapes."""
         execution_tapes = []
         batch_fns = []
         tape_counts = []
 
         for t in original_batch:
-            # Preprocess the tapes by applying batch transforms
+            # Preprocess the tapes by applying transforms
             # to each tape, and storing corresponding tapes
             # for execution, processing functions, and list of tape lengths.
             new_tapes, fn = self(t, *targs, **tkwargs)
@@ -342,14 +342,14 @@ class TransformDispatcher:
             """Applies a batch of post-processing functions to results.
 
             Args:
-                res (ResultBatch): the results of executing a batch of circuits
+                res (ResultBatch): the results of executing a batch of circuits.
 
             Returns:
-                ResultBatch : results that have undergone classical post processing
+                ResultBatch: results that have undergone classical post processing.
 
             Closure variables:
-                tape_counts: the number of tapes outputted from each application of the transform
-                batch_fns: the post processing functions to apply to each sub-batch
+                tape_counts: the number of tapes outputted from each application of the transform.
+                batch_fns: the post processing functions to apply to each sub-batch.
 
             """
             count = 0


Test patch:
diff --git a/tests/param_shift_dev.py b/tests/param_shift_dev.py
index 4454b6ead..12fd11eea 100644
--- a/tests/param_shift_dev.py
+++ b/tests/param_shift_dev.py
@@ -52,7 +52,7 @@ class ParamShiftDerivativesDevice(qml.devices.DefaultQubit):
             self.tracker.update(derivative_batches=1, derivatives=len(circuits))
             self.tracker.record()
 
-        diff_batch, fn = qml.transforms.map_batch_transform(qml.gradients.param_shift, circuits)
+        diff_batch, fn = qml.gradients.param_shift(circuits)
         diff_results = self.execute(diff_batch)
 
         jacs = fn(diff_results)
@@ -73,7 +73,7 @@ class ParamShiftDerivativesDevice(qml.devices.DefaultQubit):
             )
             self.tracker.record()
 
-        diff_batch, fn = qml.transforms.map_batch_transform(qml.gradients.param_shift, circuits)
+        diff_batch, fn = qml.gradients.param_shift(circuits)
         combined_batch = tuple(circuits) + tuple(diff_batch)
         all_results = self.execute(combined_batch)
         results = all_results[: len(circuits)]
diff --git a/tests/transforms/test_batch_transform.py b/tests/transforms/test_batch_transform.py
index aaba0a814..7ce2f07e4 100644
--- a/tests/transforms/test_batch_transform.py
+++ b/tests/transforms/test_batch_transform.py
@@ -16,6 +16,8 @@ Unit tests for the batch transform.
 """
 # pylint: disable=too-few-public-methods,not-callable
 
+import pytest
+
 import pennylane as qml
 from pennylane import numpy as np
 
@@ -23,6 +25,18 @@ from pennylane import numpy as np
 class TestMapBatchTransform:
     """Tests for the map_batch_transform function"""
 
+    def test_map_batch_transform_is_deprecated(self):
+        """Test that map_batch_transform is deprecated."""
+        ops = [qml.RX(1.2345, wires=0)]
+        measurements = [qml.expval(qml.Z(0))]
+        tape = qml.tape.QuantumTape(ops, measurements)
+        transform = qml.transforms.compile
+        with pytest.warns(
+            qml.PennyLaneDeprecationWarning,
+            match="deprecated",
+        ):
+            _ = qml.transforms.map_batch_transform(transform, [tape])
+
     def test_result(self, mocker):
         """Test that it correctly applies the transform to be mapped"""
         dev = qml.device("default.qubit.legacy", wires=2)
@@ -45,9 +59,13 @@ class TestMapBatchTransform:
 
         tape2 = qml.tape.QuantumScript.from_queue(q2)
         spy = mocker.spy(qml.transforms, "hamiltonian_expand")
-        tapes, fn = qml.transforms.map_batch_transform(
-            qml.transforms.hamiltonian_expand, [tape1, tape2]
-        )
+        with pytest.warns(
+            qml.PennyLaneDeprecationWarning,
+            match="deprecated",
+        ):
+            tapes, fn = qml.transforms.map_batch_transform(
+                qml.transforms.hamiltonian_expand, [tape1, tape2]
+            )
 
         spy.assert_called()
         assert len(tapes) == 5
@@ -79,9 +97,13 @@ class TestMapBatchTransform:
                 qml.expval(H + qml.Hamiltonian([0.5], [qml.PauliY(0)]))
 
             tape2 = qml.tape.QuantumScript.from_queue(q2)
-            tapes, fn = qml.transforms.map_batch_transform(
-                qml.transforms.hamiltonian_expand, [tape1, tape2]
-            )
+            with pytest.warns(
+                qml.PennyLaneDeprecationWarning,
+                match="deprecated",
+            ):
+                tapes, fn = qml.transforms.map_batch_transform(
+                    qml.transforms.hamiltonian_expand, [tape1, tape2]
+                )
             res = qml.execute(tapes, dev, qml.gradients.param_shift, device_batch_transform=False)
             return np.sum(fn(res))