Skip to content

Commit

Permalink
Raise a warning when user-applied dynamic_one_shot is ignored (#6701)
Browse files Browse the repository at this point in the history
Fixes #6551
[sc-77877]

---------

Co-authored-by: Isaac De Vlugt <[email protected]>
Co-authored-by: Mudit Pandey <[email protected]>
  • Loading branch information
3 people authored Dec 11, 2024
1 parent 09f09e4 commit bbd8f3d
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 19 deletions.
28 changes: 15 additions & 13 deletions pennylane/transforms/dynamic_one_shot.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,40 +58,42 @@ def null_postprocessing(results):
def dynamic_one_shot(tape: QuantumScript, **kwargs) -> tuple[QuantumScriptBatch, PostprocessingFn]:
"""Transform a QNode to into several one-shot tapes to support dynamic circuit execution.
This transform enables the ``"one-shot"`` mid-circuit measurement method. The ``"one-shot"`` method prompts the
device to perform a series of one-shot executions, where in each execution, the ``qml.measure``
operation applies a probabilistic mid-circuit measurement to the circuit.
This is in contrast with ``qml.defer_measurement``, which instead introduces an extra
wire for each mid-circuit measurement. The ``"one-shot"`` method is favourable in the few-shots
and several-mid-circuit-measurements limit, whereas ``qml.defer_measurements`` is favourable in
the opposite limit.
Args:
tape (QNode or QuantumTape or Callable): a quantum circuit to add a batch dimension to.
tape (QNode or QuantumScript or Callable): a quantum circuit.
Returns:
qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]:
qnode (QNode) or quantum function (Callable) or tuple[List[QuantumScript], function]:
The transformed circuit as described in :func:`qml.transform <pennylane.transform>`.
This circuit will provide the results of a dynamic execution.
**Example**
Consider the following circuit:
Most devices that support mid-circuit measurements will include this transform in its
preprocessing automatically when applicable. When this is the case, any user-applied
``dynamic_one_shot`` transforms will be ignored. The recommended way to use dynamic one
shot is to specify ``mcm_method="one-shot"`` in the ``qml.qnode`` decorator.
.. code-block:: python
dev = qml.device("default.qubit", shots=100)
params = np.pi / 4 * np.ones(2)
@qml.dynamic_one_shot
@qml.qnode(dev)
@qml.qnode(dev, mcm_method="one-shot", postselect_mode="fill-shots")
def func(x, y):
qml.RX(x, wires=0)
m0 = qml.measure(0)
qml.cond(m0, qml.RY)(y, wires=1)
return qml.expval(op=m0)
The ``qml.dynamic_one_shot`` decorator prompts the QNode to perform a hundred one-shot
calculations, where in each calculation the ``qml.measure`` operations dynamically
measures the 0-wire and collapse the state vector stochastically. This transforms
contrasts with ``qml.defer_measurements``, which instead introduces an extra wire
for each mid-circuit measurement. The ``qml.dynamic_one_shot`` transform is favorable in the
few-shots several-mid-circuit-measurement limit, whereas ``qml.defer_measurements`` is favorable
in the opposite limit.
"""
if not any(is_mcm(o) for o in tape.operations):
return (tape,), null_postprocessing
Expand Down
16 changes: 14 additions & 2 deletions pennylane/workflow/_setup_transform_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"""

import warnings

from cachetools import LRUCache

import pennylane as qml
Expand Down Expand Up @@ -46,10 +48,20 @@ def _prune_dynamic_transform(outer_transform, inner_transform):
if type_to_keep == 0:
return

dynamic_transform_found = inner_transform.prune_dynamic_transform(type_to_keep)
if dynamic_transform_found:
inner_contains_one_shot = inner_transform.prune_dynamic_transform(type_to_keep)
if inner_contains_one_shot:
type_to_keep = 0
original_len = len(outer_transform)
outer_transform.prune_dynamic_transform(type_to_keep)
outer_contained_one_shot = len(outer_transform) < original_len
if inner_contains_one_shot and outer_contained_one_shot:
warnings.warn(
"A dynamic_one_shot transform already exists in the preprocessing program of the "
"device. Therefore, the dynamic_one_shot applied on the qnode will be ignored. "
"See https://docs.pennylane.ai/en/stable/code/api/pennylane.dynamic_one_shot.html "
"for more information on the recommended way to use dynamic_one_shot.",
UserWarning,
)


def _setup_transform_program(
Expand Down
4 changes: 1 addition & 3 deletions tests/transforms/test_dynamic_one_shot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"""
Tests for the transform implementing the deferred measurement principle.
"""
from functools import partial

import numpy as np
import pytest
Expand Down Expand Up @@ -98,8 +97,7 @@ def test_postselect_mode_transform(postselect_mode):
shots = 100
dev = qml.device("default.qubit", shots=shots)

@partial(qml.dynamic_one_shot)
@qml.qnode(dev, postselect_mode=postselect_mode)
@qml.qnode(dev, mcm_method="one-shot", postselect_mode=postselect_mode)
def f(x):
qml.RX(x, 0)
_ = qml.measure(0, postselect=1)
Expand Down
16 changes: 15 additions & 1 deletion tests/workflow/test_setup_transform_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@

from unittest.mock import MagicMock

import pytest

import pennylane as qml
from pennylane.devices import ExecutionConfig
from pennylane.devices import ExecutionConfig, MCMConfig
from pennylane.transforms.core import TransformProgram
from pennylane.workflow._setup_transform_program import (
_prune_dynamic_transform,
Expand Down Expand Up @@ -124,6 +126,18 @@ def test_prune_dynamic_transform_with_mcm():
assert len(program2) == 1


def test_prune_dynamic_transform_warning_raised():
"""Tests that a warning raised when a user-applied dynamic-one-shot transform is ignored."""

user_transform_program = TransformProgram()
user_transform_program.add_transform(qml.transforms.dynamic_one_shot)
device = qml.device("default.qubit")
config = ExecutionConfig(mcm_config=MCMConfig(mcm_method="one-shot"))

with pytest.warns(UserWarning, match="A dynamic_one_shot transform already exists"):
_, __ = _setup_transform_program(user_transform_program, device, config)


def test_interface_data_not_supported():
"""Test that convert_to_numpy_parameters transform is correctly added."""
config = ExecutionConfig()
Expand Down

0 comments on commit bbd8f3d

Please sign in to comment.