Repository: PennyLaneAI/pennylane
Pull request #4775 (2023-11-02 14:25:34)

Support drawing `MidMeasureMP` with `qml.draw`
**Context:**
Mid-circuit measurements cannot be drawn with `qml.draw`. This is the first PR to support drawing mid-circuit measurements

**Description of the Change:**
* Added label to `MidMeasureMP`, including cases of qubit reset and postselection.
* Minor changes to `qml.drawer.tape_text` to support drawing measurements in the ops queue. Measurements without a defined `label` method (which is all measurements except MidMeasureMP`) will still break the drawer, which is what we want.

**Benefits:**
We can now draw mid-circuit measurements! `qml.cond` is still not supported. That will come in future PRs

**Possible Drawbacks:**

**Related GitHub Issues:**


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4775?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
Attention: `6 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`8aee680`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/8aee6805684cffe3fc0bb7b5c641f94ad7301859?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.64% compared to head [(`0322db1`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4775?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.62%.
> Report is 1 commits behind head on master.

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


```diff
@@            Coverage Diff             @@
##           master    #4775      +/-   ##
==========================================
- Coverage   99.64%   99.62%   -0.03%     
==========================================
  Files         381      381              
  Lines       34308    34055     -253     
==========================================
- Hits        34187    33927     -260     
- Misses        121      128       +7     
```


| [Files](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4775?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/drawer/utils.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4775?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RyYXdlci91dGlscy5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/measurements/mid\_measure.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4775?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21lYXN1cmVtZW50cy9taWRfbWVhc3VyZS5weQ==) | `95.08% <14.28%> (-4.92%)` | :arrow_down: |

... and [42 files with indirect coverage changes](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4775/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4775?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).

trbromley: @mudit2812 please could you link the SC story
mudit2812: [sc-49111]

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 8b2410381..e7cd3acc3 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -9,6 +9,9 @@
 
 <h3>Improvements 🛠</h3>
 
+* `qml.draw` now supports drawing mid-circuit measurements.
+  [(#4775)](https://github.com/PennyLaneAI/pennylane/pull/4775)
+
 * Autograd can now use vjps provided by the device from the new device API. If a device provides
   a vector Jacobian product, this can be selected by providing `device_vjp=True` to
   `qml.execute`.
diff --git a/pennylane/drawer/utils.py b/pennylane/drawer/utils.py
index 6a0a36dd1..603653eb8 100644
--- a/pennylane/drawer/utils.py
+++ b/pennylane/drawer/utils.py
@@ -77,7 +77,7 @@ def unwrap_controls(op):
     # Get wires and control values of base operation; need to make a copy of
     # control values, otherwise it will modify the list in the operation itself.
     control_wires = getattr(op, "control_wires", [])
-    control_values = op.hyperparameters.get("control_values", None)
+    control_values = getattr(op, "hyperparameters", {}).get("control_values", None)
 
     if isinstance(control_values, list):
         control_values = control_values.copy()
diff --git a/pennylane/measurements/mid_measure.py b/pennylane/measurements/mid_measure.py
index 825c02838..ae5734677 100644
--- a/pennylane/measurements/mid_measure.py
+++ b/pennylane/measurements/mid_measure.py
@@ -223,6 +223,28 @@ class MidMeasureMP(MeasurementProcess):
         self.reset = reset
         self.postselect = postselect
 
+    def label(self, decimals=None, base_label=None, cache=None):  # pylint: disable=unused-argument
+        r"""How the mid-circuit measurement is represented in diagrams and drawings.
+
+        Args:
+            decimals=None (Int): If ``None``, no parameters are included. Else,
+                how to round the parameters.
+            base_label=None (Iterable[str]): overwrite the non-parameter component of the label.
+                Must be same length as ``obs`` attribute.
+            cache=None (dict): dictionary that carries information between label calls
+                in the same drawing
+
+        Returns:
+            str: label to use in drawings
+        """
+        _label = "┤↗"
+        if self.postselect is not None:
+            _label += "₁" if self.postselect == 1 else "₀"
+
+        _label += "├" if not self.reset else "│  │0⟩"
+
+        return _label
+
     @property
     def return_type(self):
         return MidMeasure


Test patch:
diff --git a/tests/drawer/test_draw.py b/tests/drawer/test_draw.py
index 053335503..06cc0a646 100644
--- a/tests/drawer/test_draw.py
+++ b/tests/drawer/test_draw.py
@@ -300,6 +300,60 @@ def test_mid_circuit_measurement_device_api(device_name, mocker):
     spy.assert_called_once()
 
 
+@pytest.mark.parametrize(
+    "postselect, reset, mid_measure_label",
+    [
+        (None, False, "┤↗├"),
+        (None, True, "┤↗│  │0⟩"),
+        (0, False, "┤↗₀├"),
+        (0, True, "┤↗₀│  │0⟩"),
+        (1, False, "┤↗₁├"),
+        (1, True, "┤↗₁│  │0⟩"),
+    ],
+)
+def test_draw_mid_circuit_measurement(postselect, reset, mid_measure_label):
+    """Test that mid-circuit measurements are drawn correctly."""
+
+    def func():
+        qml.Hadamard(0)
+        qml.measure(0, reset=reset, postselect=postselect)
+        qml.PauliX(0)
+        return qml.expval(qml.PauliZ(0))
+
+    drawing = qml.draw(func)()
+    expected_drawing = "0: ──H──" + mid_measure_label + "──X─┤  <Z>"
+
+    assert drawing == expected_drawing
+
+
+def test_draw_mid_circuit_measurement_multiple_wires():
+    """Test that mid-circuit measurements are correctly drawn in circuits
+    with multiple wires."""
+
+    def circ(weights):
+        qml.RX(weights[0], 0)
+        qml.measure(0, reset=True)
+        qml.RX(weights[1], 1)
+        qml.measure(1)
+        qml.CNOT([0, 3])
+        qml.measure(3, postselect=0, reset=True)
+        qml.RY(weights[2], 2)
+        qml.CNOT([1, 2])
+        qml.measure(2, postselect=1)
+        qml.MultiRZ(0.5, [0, 2])
+        return qml.expval(qml.PauliZ(2))
+
+    drawing = qml.draw(circ)(np.array([np.pi, 3.124, 0.456]))
+    expected_drawing = (
+        "0: ──RX(3.14)──┤↗│  │0⟩─╭●─────────────────────╭MultiRZ(0.50)─┤     \n"
+        "1: ──RX(3.12)──┤↗├──────│─────────────╭●───────│──────────────┤     \n"
+        "3: ─────────────────────╰X──┤↗₀│  │0⟩─│────────│──────────────┤     \n"
+        "2: ──RY(0.46)─────────────────────────╰X──┤↗₁├─╰MultiRZ(0.50)─┤  <Z>"
+    )
+
+    assert drawing == expected_drawing
+
+
 @pytest.mark.parametrize(
     "transform",
     [
diff --git a/tests/measurements/test_mid_measure.py b/tests/measurements/test_mid_measure.py
index 6aa70a214..de192de51 100644
--- a/tests/measurements/test_mid_measure.py
+++ b/tests/measurements/test_mid_measure.py
@@ -53,6 +53,24 @@ class TestMeasure:
         assert m1.hash != m3.hash
         assert m1.hash == m4.hash
 
+    @pytest.mark.parametrize(
+        "postselect, reset, expected",
+        [
+            (None, False, "┤↗├"),
+            (None, True, "┤↗│  │0⟩"),
+            (0, False, "┤↗₀├"),
+            (0, True, "┤↗₀│  │0⟩"),
+            (1, False, "┤↗₁├"),
+            (1, True, "┤↗₁│  │0⟩"),
+        ],
+    )
+    def test_label(self, postselect, reset, expected):
+        """Test that the label for a MidMeasureMP is correct"""
+        mp = MidMeasureMP(0, postselect=postselect, reset=reset)
+
+        label = mp.label()
+        assert label == expected
+
 
 mp1 = MidMeasureMP(Wires(0), id="m0")
 mp2 = MidMeasureMP(Wires(1), id="m1")