Repository: PennyLaneAI/pennylane
Pull request #7243 (2025-04-09 18:14:49)

Don't defer mid-circuit measurements when mcm_method is "device"
**Context:**
Some mid-circuit measurement handling is device-specific, like tree-traversal. When a method is device-specific, we want to skip transforming the tape in the standard preprocess pipeline, and instead rely on handling that is implemented in the `device` class.

This is currently the case for tree-traversal, but the more generic specification `"device"` defaults to deferred measurements. 

**Description of the Change:**

We handle `mcm_method="device"` in the `mid_circuit_measurements` transform the same way we handle `mcm_method="tree-traversal"` - with a null transform and `null_postprocessing`.

**Benefits:**
Generally, we can use `"device"` the way it seems to have been intended as a method for handling mid-circuit measurements.

More specifically, we can use `"device"` as an MCM method on `null.qubit` if we don't want to include transforming and post-processing MCMs in our mock execution, but just want to treat MCMs like any other operation on the tape with `null.qubit` and skip it. This will be helpful for benchmarking the device processing and post-processing for large-scale FTQC workloads on `null.qubit`.

[sc-89247]

github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
lillian542: @andrijapau good catch, updated :)

Patch:
diff --git a/.github/stable/external.txt b/.github/stable/external.txt
index 867982cf5..f07d8ce9d 100644
--- a/.github/stable/external.txt
+++ b/.github/stable/external.txt
@@ -140,10 +140,10 @@ pandocfilters==1.5.1
 parso==0.8.4
 pathspec==0.12.1
 pbr==6.1.1
-PennyLane-Catalyst==0.12.0.dev4
-PennyLane-qiskit @ git+https://github.com/PennyLaneAI/pennylane-qiskit.git@14779eb0cecfafd5a56c025bd22c411359936dd1
+PennyLane-Catalyst==0.11.0
+PennyLane-qiskit @ git+https://github.com/PennyLaneAI/pennylane-qiskit.git@af5226f4a10885e6e01935c6fc3564d94d30984d
 PennyLane_Lightning==0.41.0
-PennyLane_Lightning_Kokkos==0.41.0.dev22
+PennyLane_Lightning_Kokkos==0.41.0
 pexpect==4.9.0
 pillow==11.2.1
 platformdirs==4.3.7
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 148200eb3..8eb6fddf2 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -26,6 +26,10 @@
 * Using finite differences with program capture without x64 mode enabled now raises a warning.
   [(#7282)](https://github.com/PennyLaneAI/pennylane/pull/7282)
 
+* When the `mcm_method` is specified to the `"device"`, the `defer_measurements` transform will 
+  no longer be applied. Instead, the device will be responsible for all MCM handling.
+  [(#7243)](https://github.com/PennyLaneAI/pennylane/pull/7243)
+
 <h3>Contributors ✍️</h3>
 
 This release contains contributions from (in alphabetical order):
diff --git a/pennylane/compiler/compiler.py b/pennylane/compiler/compiler.py
index 6c38bb3d2..c31f04522 100644
--- a/pennylane/compiler/compiler.py
+++ b/pennylane/compiler/compiler.py
@@ -23,7 +23,7 @@ from typing import List, Optional
 
 from packaging.version import Version
 
-PL_CATALYST_MIN_VERSION = Version("0.10.0")
+PL_CATALYST_MIN_VERSION = Version("0.11.0")
 
 
 class CompileError(Exception):
diff --git a/pennylane/devices/preprocess.py b/pennylane/devices/preprocess.py
index 510e83724..520f2dfc9 100644
--- a/pennylane/devices/preprocess.py
+++ b/pennylane/devices/preprocess.py
@@ -187,8 +187,9 @@ def mid_circuit_measurements(
 ) -> tuple[QuantumScriptBatch, PostprocessingFn]:
     """Provide the transform to handle mid-circuit measurements.
 
-    If the tape or device uses finite-shot, use the native implementation (i.e. no transform),
-    and use the ``qml.defer_measurements`` transform otherwise.
+    In the case where no method is specified, if the tape or device
+    uses finite-shot, the ``qml.dynamic_one_shot`` transform will be
+    applied, otherwise ``qml.defer_measurements`` is used instead.
     """
     if isinstance(mcm_config, dict):
         mcm_config = MCMConfig(**mcm_config)
@@ -198,7 +199,7 @@ def mid_circuit_measurements(
 
     if mcm_method == "one-shot":
         return qml.dynamic_one_shot(tape, postselect_mode=mcm_config.postselect_mode)
-    if mcm_method == "tree-traversal":
+    if mcm_method in ("tree-traversal", "device"):
         return (tape,), null_postprocessing
     return qml.defer_measurements(
         tape, allow_postselect=isinstance(device, qml.devices.DefaultQubit)
diff --git a/setup.py b/setup.py
index 20042551d..b65d80b52 100644
--- a/setup.py
+++ b/setup.py
@@ -30,7 +30,7 @@ requirements = [
     "appdirs",
     "autoray>=0.6.11",
     "cachetools",
-    "pennylane-lightning>=0.40",
+    "pennylane-lightning>=0.41",
     "requests",
     "typing_extensions",
     "packaging",


Test patch:
diff --git a/tests/devices/test_preprocess.py b/tests/devices/test_preprocess.py
index fba3deeff..d8208b781 100644
--- a/tests/devices/test_preprocess.py
+++ b/tests/devices/test_preprocess.py
@@ -22,6 +22,7 @@ from pennylane.devices.preprocess import (
     decompose,
     mid_circuit_measurements,
     no_sampling,
+    null_postprocessing,
     validate_adjoint_trainable_params,
     validate_device_wires,
     validate_measurements,
@@ -483,6 +484,20 @@ class TestMidCircuitMeasurements:
         _, _ = mid_circuit_measurements(tape, dev, mcm_config)
         spy.assert_called_once()
 
+    @pytest.mark.parametrize("mcm_method", ["device", "tree-traversal"])
+    @pytest.mark.parametrize("shots", [10, None])
+    def test_device_mcm_method(self, mcm_method, shots):
+        """Test that no transform is applied by mid_circuit_measurements when the
+        mcm method is handled by the device"""
+        dev = qml.device("default.qubit")
+        mcm_config = {"postselect_mode": None, "mcm_method": mcm_method}
+        tape = QuantumScript([qml.measurements.MidMeasureMP(0)], [], shots=shots)
+
+        (new_tape,), post_processing_fn = mid_circuit_measurements(tape, dev, mcm_config)
+
+        assert qml.equal(tape, new_tape)
+        assert post_processing_fn == null_postprocessing
+
     def test_error_incompatible_mcm_method(self):
         """Test that an error is raised if requesting the one-shot transform without shots"""
         dev = qml.device("default.qubit")