Repository: PennyLaneAI/pennylane
Pull request #5850 (2024-06-13 16:20:36)

Update `QNode` to not mutate original MCM config
As name says. Mutating the config in place was causing unexpected results such as below. Now we create a copy of the original config before execution begins, which can be mutated as needed.

```python
import pennylane as qml

dev = qml.device("default.qubit")

@qml.qnode(dev, postselect_mode="fill-shots")
def f(x, mp):
    qml.RX(x, 0)
    qml.measure(0, postselect=1)
    return mp(qml.PauliZ(0))
```
```pycon
>>> print(f(1.8, qml.sample, shots=10))
[-1. -1. -1. -1. -1. -1. -1. -1. -1. -1.]
>>> print(f(1.8, qml.expval))
-0.9999999999999998
>>> print(f(1.8, qml.sample, shots=10))
[-1. -1. -1. -1. -1. -1.]
```


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.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5850?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 [(`2450f16`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/2450f169e14efb70c1caf86803d20aff42834bc1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`244e61b`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/244e61bc11a7ce4b7c0f810ec30e7c068ba92fe0?dropdown=coverage&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    #5850      +/-   ##
==========================================
- Coverage   99.67%   99.67%   -0.01%     
==========================================
  Files         420      420              
  Lines       40170    39874     -296     
==========================================
- Hits        40041    39744     -297     
- Misses        129      130       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5850?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/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 5b2896486..298a8fc83 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -34,21 +34,6 @@
   [1 1 0]
   ```
 
-* `qml.QNode` and `qml.qnode` now accept two new keyword arguments: `postselect_mode` and `mcm_method`.
-  These keyword arguments can be used to configure how the device should behave when running circuits with
-  mid-circuit measurements.
-  [(#5679)](https://github.com/PennyLaneAI/pennylane/pull/5679)
-  [(#5833)](https://github.com/PennyLaneAI/pennylane/pull/5833)
-
-  * `postselect_mode="hw-like"` will indicate to devices to discard invalid shots when postselecting
-    mid-circuit measurements. Use `postselect_mode="fill-shots"` to unconditionally sample the postselected
-    value, thus making all samples valid. This is equivalent to sampling until the number of valid samples
-    matches the total number of shots.
-  * `mcm_method` will indicate which strategy to use for running circuits with mid-circuit measurements.
-    Use `mcm_method="deferred"` to use the deferred measurements principle, or `mcm_method="one-shot"`
-    to execute once for each shot. If using `qml.jit` with the Catalyst compiler, `mcm_method="single-branch-statistics"`
-    is also available. Using this method, a single branch of the execution tree will be randomly explored.
-
 * The `default.tensor` device is introduced to perform tensor network simulations of quantum circuits using the `mps` (Matrix Product State) method.
   [(#5699)](https://github.com/PennyLaneAI/pennylane/pull/5699)
 
@@ -110,6 +95,22 @@
 
 <h4>Mid-circuit measurements and dynamic circuits</h4>
 
+* `qml.QNode` and `qml.qnode` now accept two new keyword arguments: `postselect_mode` and `mcm_method`.
+  These keyword arguments can be used to configure how the device should behave when running circuits with
+  mid-circuit measurements.
+  [(#5679)](https://github.com/PennyLaneAI/pennylane/pull/5679)
+  [(#5833)](https://github.com/PennyLaneAI/pennylane/pull/5833)
+  [(#5850)](https://github.com/PennyLaneAI/pennylane/pull/5850)
+
+  * `postselect_mode="hw-like"` will indicate to devices to discard invalid shots when postselecting
+    mid-circuit measurements. Use `postselect_mode="fill-shots"` to unconditionally sample the postselected
+    value, thus making all samples valid. This is equivalent to sampling until the number of valid samples
+    matches the total number of shots.
+  * `mcm_method` will indicate which strategy to use for running circuits with mid-circuit measurements.
+    Use `mcm_method="deferred"` to use the deferred measurements principle, or `mcm_method="one-shot"`
+    to execute once for each shot. If using `qml.jit` with the Catalyst compiler, `mcm_method="single-branch-statistics"`
+    is also available. Using this method, a single branch of the execution tree will be randomly explored.
+
 * The `dynamic_one_shot` transform is made compatible with the Catalyst compiler.
   [(#5766)](https://github.com/PennyLaneAI/pennylane/pull/5766)
   
diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py
index f07dbdb08..985eb24cb 100644
--- a/pennylane/workflow/qnode.py
+++ b/pennylane/workflow/qnode.py
@@ -1042,7 +1042,7 @@ class QNode:
         )
         self._tape_cached = using_custom_cache and self.tape.hash in cache
 
-        mcm_config = self.execute_kwargs["mcm_config"]
+        mcm_config = copy.copy(self.execute_kwargs["mcm_config"])
         finite_shots = _get_device_shots if override_shots is False else override_shots
         if not finite_shots:
             mcm_config.postselect_mode = None


Test patch:
diff --git a/tests/test_qnode.py b/tests/test_qnode.py
index b11ca5db1..f4700935b 100644
--- a/tests/test_qnode.py
+++ b/tests/test_qnode.py
@@ -16,7 +16,7 @@ import copy
 
 # pylint: disable=import-outside-toplevel, protected-access, no-member
 import warnings
-from dataclasses import replace
+from dataclasses import asdict, replace
 from functools import partial
 from typing import Callable, Tuple
 
@@ -1839,6 +1839,32 @@ class TestMCMConfiguration:
         with pytest.raises(ValueError, match="Cannot use mcm_method='single-branch-statistics'"):
             _ = circuit(param)
 
+    @pytest.mark.parametrize("postselect_mode", [None, "fill-shots", "hw-like"])
+    @pytest.mark.parametrize("mcm_method", [None, "one-shot", "deferred"])
+    def test_execution_does_not_mutate_config(self, mcm_method, postselect_mode):
+        """Test that executing a QNode does not mutate its mid-circuit measurement config options"""
+        dev = qml.device("default.qubit", wires=2)
+
+        original_config = qml.devices.MCMConfig(
+            postselect_mode=postselect_mode, mcm_method=mcm_method
+        )
+
+        @qml.qnode(dev, **asdict(original_config))
+        def circuit(x, mp):
+            qml.RX(x, 0)
+            qml.measure(0, postselect=1)
+            return mp(qml.PauliZ(0))
+
+        _ = circuit(1.8, qml.expval, shots=10)
+        assert circuit.execute_kwargs["mcm_config"] == original_config
+
+        if mcm_method != "one-shot":
+            _ = circuit(1.8, qml.expval)
+            assert circuit.execute_kwargs["mcm_config"] == original_config
+
+        _ = circuit(1.8, qml.expval, shots=10)
+        assert circuit.execute_kwargs["mcm_config"] == original_config
+
 
 class TestTapeExpansion:
     """Test that tape expansion within the QNode works correctly"""