Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate map_batch_transform #5676

Merged
merged 18 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/development/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
PietropaoloFrisoni marked this conversation as resolved.
Show resolved Hide resolved

New operator arithmetic deprecations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
3 changes: 3 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
13 changes: 13 additions & 0 deletions pennylane/transforms/batch_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
PietropaoloFrisoni marked this conversation as resolved.
Show resolved Hide resolved
"""

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.",
PietropaoloFrisoni marked this conversation as resolved.
Show resolved Hide resolved
qml.PennyLaneDeprecationWarning,
)

execution_tapes = []
batch_fns = []
tape_counts = []
Expand Down
47 changes: 47 additions & 0 deletions pennylane/transforms/core/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,48 @@ def qnode_circuit(a):
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

Expand All @@ -134,6 +176,7 @@ def qnode_circuit(a):
- :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
Expand All @@ -157,6 +200,10 @@ def qnode_circuit(a):
: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.
Expand Down
12 changes: 6 additions & 6 deletions pennylane/transforms/core/transform_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,13 +324,13 @@ def original_device(self):
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)
Expand All @@ -342,14 +342,14 @@ def processing_fn(res: ResultBatch) -> ResultBatch:
"""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
Expand Down
4 changes: 2 additions & 2 deletions tests/param_shift_dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def compute_derivatives(self, circuits, execution_config=None):
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)
Expand All @@ -73,7 +73,7 @@ def execute_and_compute_derivatives(self, circuits, execution_config=None):
)
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)]
Expand Down
34 changes: 28 additions & 6 deletions tests/transforms/test_batch_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,27 @@
"""
# pylint: disable=too-few-public-methods,not-callable

import pytest

import pennylane as qml
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)
Expand All @@ -45,9 +59,13 @@ def test_result(self, mocker):

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
Expand Down Expand Up @@ -79,9 +97,13 @@ def cost(weights):
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))

Expand Down
Loading