Repository: PennyLaneAI/pennylane (python)

Issue:
[Enhancement] Avoid expansion of whitespace when using many MCMs in MPL drawer
![Image](https://github.com/user-attachments/assets/9c3ccfc2-c3bd-4fcc-9d52-91242cb90d83)
When using many MCM operations, the MPL drawer fails to recycle whitespace. This makes readability a challenge when MCMs grow to large numbers.

The enhancement request is to:
* Reuse available whitespace for the classical lines from MCMs
* Ensure area is between by MCM counts and where they are used.

Pull request description:
Reuse lines for classical wires if possible
**Context:**
As described in #7118 , a lot of white space is produced by the circuit drawer if there are many MCMs, even if their impact is localized, and the same lines below the quantum wires could be reused for different classical wires.

**Description of the Change:**
This PR changes the classical wire allocation in an additional processing step, reusing lines for classical wires if possible.

**Benefits:**
More concise circuit drawings.

**Possible Drawbacks:**
A little more processing happening. Worst case should be something like $\mathcal{O}(k^2)$ scaling, where $k$ is the number of classical wires. However, most operations are $\mathcal{O}(k)$ and the only $\mathcal{O}(k^2)$ operation uses `np.where` as an inner loop, which should come with very small constants.

**Related GitHub Issues:**
Resolves #7118 

[sc-86882]


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 6cd17e700..eae6f3da0 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -494,6 +494,7 @@ With `qml.decompositions.enable_graph()`, the following new features are availab
 
 <h3>Improvements 🛠</h3>
 
+  
 <h4>QNode improvements</h4>
 
 * `QNode` objects now have an `update` method that allows for re-configuring settings like `diff_method`, `mcm_method`, and more. This allows for easier on-the-fly adjustments to workflows. Any arguments not specified will retain their original value.
@@ -584,6 +585,10 @@ With `qml.decompositions.enable_graph()`, the following new features are availab
 
 <h4>Better drawing functionality</h4>
 
+* `qml.draw` and `qml.draw_mpl` can now reuse lines for different classical wires, saving whitespace without
+  changing the represented circuit.
+  [(#7163)](https://github.com/PennyLaneAI/pennylane/pull/7163)
+
 * `PrepSelPrep` now has a concise representation when drawn with `qml.draw` or `qml.draw_mpl`.
   [(#7164)](https://github.com/PennyLaneAI/pennylane/pull/7164)
 
diff --git a/pennylane/devices/preprocess.py b/pennylane/devices/preprocess.py
index 26e2f69d8..510e83724 100644
--- a/pennylane/devices/preprocess.py
+++ b/pennylane/devices/preprocess.py
@@ -312,7 +312,7 @@ def decompose(  # pylint: disable = too-many-positional-arguments
         stopping_condition (Callable): a function from an operator to a boolean. If ``False``,
             the operator should be decomposed. If an operator cannot be decomposed and is not
             accepted by ``stopping_condition``, an ``Exception`` will be raised (of a type
-            specified by the ``error`` keyward argument).
+            specified by the ``error`` keyword argument).
 
     Keyword Args:
         stopping_condition_shots (Callable): a function from an operator to a boolean. If
diff --git a/pennylane/drawer/_add_obj.py b/pennylane/drawer/_add_obj.py
index 3d3b51d40..ffc038756 100644
--- a/pennylane/drawer/_add_obj.py
+++ b/pennylane/drawer/_add_obj.py
@@ -38,21 +38,25 @@ def _add_cond_grouping_symbols(op, layer_str, config):
     mapped_wires = [config.wire_map[w] for w in op.wires]
     mapped_bits = [config.bit_map[m] for m in op.meas_val.measurements]
     max_w = max(mapped_wires)
-    max_b = max(mapped_bits) + n_wires
+    max_b = max(mapped_bits)
 
-    ctrl_symbol = "╩" if config.cur_layer != config.cwire_layers[max(mapped_bits)][-1] else "╝"
-    layer_str[max_b] = f"═{ctrl_symbol}"
+    ctrl_symbol = "╩"
+    if any(config.cur_layer == stretch[-1] for stretch in config.cwire_layers[max_b]):
+        ctrl_symbol = "╝"
+    layer_str[max_b + n_wires] = f"═{ctrl_symbol}"
 
     for w in range(max_w + 1, max(config.wire_map.values()) + 1):
         layer_str[w] = "─║"
 
-    for b in range(n_wires, max_b):
-        if b - n_wires in mapped_bits:
-            intersection = "╣" if config.cur_layer == config.cwire_layers[b - n_wires][-1] else "╬"
-            layer_str[b] = f"═{intersection}"
+    for b in range(max_b):
+        if b in mapped_bits:
+            intersection = "╬"
+            if any(config.cur_layer == stretch[-1] for stretch in config.cwire_layers[b]):
+                intersection = "╣"
+            layer_str[b + n_wires] = f"═{intersection}"
         else:
-            filler = " " if layer_str[b][-1] == " " else "═"
-            layer_str[b] = f"{filler}║"
+            filler = " " if layer_str[b + n_wires][-1] == " " else "═"
+            layer_str[b + n_wires] = f"{filler}║"
 
     return layer_str
 
diff --git a/pennylane/drawer/drawable_layers.py b/pennylane/drawer/drawable_layers.py
index a2d78e772..a5d9cfc08 100644
--- a/pennylane/drawer/drawable_layers.py
+++ b/pennylane/drawer/drawable_layers.py
@@ -51,19 +51,15 @@ def _recursive_find_layer(layer_to_check, op_occupied_wires, occupied_wires_per_
     return _recursive_find_layer(layer_to_check - 1, op_occupied_wires, occupied_wires_per_layer)
 
 
-def _recursive_find_mcm_stats_layer(
-    layer_to_check, op_occupied_cwires, used_cwires_per_layer, bit_map
-):
-    """Determine correct layer for a terminal measurement that is collectings statistics
+def _recursive_find_mcm_stats_layer(layer_to_check, op_occupied_cwires, used_cwires_per_layer):
+    """Determine correct layer for a terminal measurement that is collecting statistics
     for mid-circuit measurement values.
 
     Args:
         layer_to_check (int): the function determines if the operation fits on this layer
         op_occupied_cwires (set(int)): classical wires occupied by measurement
-        occupied_cwires_per_layer (list[set[int]]): which classical wires are already
+        used_cwires_per_layer (list[set[int]]): which classical wires are already
             in use for collecting statistics. Each set is a different layer.
-        bit_map (dict): A map containing mid-circuit measurements used for classical conditions
-            or collecting statistics as keys
 
     Returns:
         int: layer to place measurement process in
@@ -78,7 +74,7 @@ def _recursive_find_mcm_stats_layer(
         return 0
     # keep pushing the operation to lower layers
     return _recursive_find_mcm_stats_layer(
-        layer_to_check - 1, op_occupied_cwires, used_cwires_per_layer, bit_map
+        layer_to_check - 1, op_occupied_cwires, used_cwires_per_layer
     )
 
 
@@ -179,7 +175,7 @@ def drawable_layers(operations, wire_map=None, bit_map=None):
             )
             op_occupied_cwires = set(range(min(mapped_cwires), max(mapped_cwires) + 1))
             op_layer = _recursive_find_mcm_stats_layer(
-                max_layer, op_occupied_cwires, used_cwires_per_layer, bit_map
+                max_layer, op_occupied_cwires, used_cwires_per_layer
             )
 
         else:
diff --git a/pennylane/drawer/tape_mpl.py b/pennylane/drawer/tape_mpl.py
index a41104e57..5cf407e11 100644
--- a/pennylane/drawer/tape_mpl.py
+++ b/pennylane/drawer/tape_mpl.py
@@ -190,18 +190,20 @@ def _get_measured_wires(measurements, wires) -> set:
     return measured_wires
 
 
-def _add_classical_wires(drawer, layers, wires):
-    for cwire, (cwire_layers, layer_wires) in enumerate(zip(layers, wires), start=drawer.n_wires):
-        xs, ys = [], []
+def _add_classical_wires(drawer, cwire_layers, cwire_wires):
 
-        len_diff = len(cwire_layers) - len(layer_wires)
-        if len_diff > 0:
-            layer_wires += [cwire] * len_diff
-        for l, w in zip(cwire_layers, layer_wires):
-            xs.extend([l, l, l])
-            ys.extend([cwire, w, cwire])
+    for cwire, layer_ids_per_cwire in cwire_layers.items():
+        for layer_ids, layer_wires in zip(layer_ids_per_cwire, cwire_wires[cwire], strict=True):
+            xs, ys = [], []
 
-        drawer.classical_wire(xs, ys)
+            len_diff = len(layer_ids) - len(layer_wires)
+            if len_diff > 0:
+                layer_wires += [cwire + drawer.n_wires] * len_diff
+            for l, w in zip(layer_ids, layer_wires, strict=True):
+                xs.extend([l, l, l])
+                ys.extend([cwire + drawer.n_wires, w, cwire + drawer.n_wires])
+
+            drawer.classical_wire(xs, ys)
 
 
 def _get_measured_bits(measurements, bit_map, offset):
@@ -237,7 +239,7 @@ def _tape_mpl(tape, wire_order=None, show_all_wires=False, decimals=None, *, fig
     n_layers = len(layers)
     n_wires = len(wire_map)
 
-    cwire_layers, cwire_wires = cwire_connections(layers + [tape.measurements], bit_map)
+    bit_map, cwire_layers, cwire_wires = cwire_connections(layers + [tape.measurements], bit_map)
 
     drawer = MPLDrawer(
         n_layers=n_layers,
@@ -251,7 +253,7 @@ def _tape_mpl(tape, wire_order=None, show_all_wires=False, decimals=None, *, fig
         decimals=decimals,
         active_wire_notches=active_wire_notches,
         bit_map=bit_map,
-        terminal_layers=[cl[-1] for cl in cwire_layers],
+        terminal_layers=[cl[-1][-1] for cl in cwire_layers.values()],
     )
 
     if n_wires == 0:
diff --git a/pennylane/drawer/tape_text.py b/pennylane/drawer/tape_text.py
index e884765fa..7653d47d9 100644
--- a/pennylane/drawer/tape_text.py
+++ b/pennylane/drawer/tape_text.py
@@ -68,13 +68,17 @@ class _Config:
     def bit_filler(self, bit, next_layer: bool = False) -> str:
         """The filler character for bits at the current layer and the designated bit."""
         layer = self.cur_layer + 1 if next_layer else self.cur_layer
-        b_is_occupied = self.cwire_layers[bit][0] < layer <= self.cwire_layers[bit][-1]
-        return "═" if self.cur_layer < self.num_op_layers and b_is_occupied else " "
+        if self.cur_layer >= self.num_op_layers:
+            return " "
+        for layer_stretch in self.cwire_layers[bit]:
+            if layer_stretch[0] < layer <= layer_stretch[-1]:
+                return "═"
+        return " "
 
     @property
     def n_bits(self) -> int:
         """The number of bits."""
-        return len(self.bit_map)
+        return len(set(self.bit_map.values()))
 
     @property
     def n_wires(self) -> int:
@@ -88,12 +92,13 @@ def _initialize_wire_and_bit_totals(
     """Initialize the wire totals and bit_totals with the required wire labels."""
     if show_wire_labels:
         wire_totals = [f"{wire}: " for wire in config.wire_map]
+        line_length = max(len(s) for s in wire_totals)
+        wire_totals = [s.rjust(line_length, " ") for s in wire_totals]
+        bit_totals = [" " * line_length] * config.n_bits
     else:
         wire_totals = [""] * config.n_wires
-    line_length = max(len(s) for s in wire_totals)
+        bit_totals = [""] * config.n_bits
 
-    wire_totals = [s.rjust(line_length, " ") for s in wire_totals]
-    bit_totals = [" " * line_length for _ in config.bit_map]
     return wire_totals, bit_totals
 
 
@@ -193,7 +198,7 @@ def _finalize_layers(totals: _CurrentTotals, config: _Config) -> _CurrentTotals:
     """Add ending characters to separate the operation layers from the measurement layers"""
     totals.wire_totals = [f"{s}─┤" for s in totals.wire_totals]
     for b in range(config.n_bits):
-        if config.cwire_layers[b][-1] >= config.num_op_layers:
+        if config.cwire_layers[b][-1][-1] >= config.num_op_layers:
             totals.bit_totals[b] += "═╡"
         else:
             totals.bit_totals[b] += "  "
@@ -402,7 +407,8 @@ def tape_text(
 
     wire_map = convert_wire_order(tape, wire_order=wire_order, show_all_wires=show_all_wires)
     bit_map = default_bit_map(tape)
-    if len(wire_map) == 0:
+    n_wires = len(wire_map)
+    if n_wires == 0:
         return ""
 
     layers = drawable_layers(tape.operations, wire_map=wire_map, bit_map=bit_map)
@@ -411,7 +417,7 @@ def tape_text(
 
     # Update bit map and collect information about connections between mid-circuit measurements,
     # classical conditions, and terminal measurements for processing mid-circuit measurements.
-    cwire_layers, _ = cwire_connections(layers, bit_map)
+    bit_map, cwire_layers, _ = cwire_connections(layers, bit_map)
 
     # Collect information needed for drawing layers
     config = _Config(
diff --git a/pennylane/drawer/utils.py b/pennylane/drawer/utils.py
index 1115c6ba6..b25021aec 100644
--- a/pennylane/drawer/utils.py
+++ b/pennylane/drawer/utils.py
@@ -14,6 +14,8 @@
 """
 This module contains some useful utility functions for circuit drawing.
 """
+import numpy as np
+
 from pennylane.measurements import MeasurementProcess, MeasurementValue, MidMeasureMP
 from pennylane.ops import Conditional, Controlled
 
@@ -44,8 +46,14 @@ def default_bit_map(tape):
     Returns:
         dict: map from mid-circuit measurements to classical wires."""
     bit_map = {}
+    mcms = {}
 
+    mcm_idx = 0
     for op in tape:
+        if isinstance(op, MidMeasureMP):
+            mcms[op] = mcm_idx
+            mcm_idx += 1
+
         if isinstance(op, Conditional):
             for m in op.meas_val.measurements:
                 bit_map[m] = None
@@ -58,11 +66,7 @@ def default_bit_map(tape):
                 for m in op.mv:
                     bit_map[m.measurements[0]] = None
 
-    cur_cwire = 0
-    for op in tape:
-        if isinstance(op, MidMeasureMP) and op in bit_map:
-            bit_map[op] = cur_cwire
-            cur_cwire += 1
+    bit_map = {mcm: i for i, mcm in enumerate(sorted(bit_map, key=mcms.get))}
 
     return bit_map
 
@@ -149,8 +153,13 @@ def cwire_connections(layers, bit_map):
             classical conditions or measurement statistics as keys.
 
     Returns:
-        list, list: list of list of accessed layers for each classical wire, and largest wire
-        corresponding to the accessed layers in the list above.
+        dict, dict, dict: The first dictionary is the updated ``bit_map``, potentially with
+        some mid-circuit measurements mapped to new (smaller) classical wires. The second and third
+        dictionaries have the classical wires as keys and lists of lists as values, with the outer
+        list running over different (re)usages of the classical wire. For the second dictionary,
+        the inner lists contain the indices of the accessed layers, for the third dictionary,
+        they contain the measured quantum wires and the largest quantum wire of conditionally
+        applied operations (no entries for terminal statistics of mid-circuit measurements).
 
     >>> with qml.queuing.AnnotatedQueue() as q:
     ...     m0 = qml.measure(0)
@@ -159,48 +168,96 @@ def cwire_connections(layers, bit_map):
     ...     qml.cond(m0, qml.S)(3)
     >>> tape = qml.tape.QuantumScript.from_queue(q)
     >>> layers = drawable_layers(tape)
-    >>> bit_map, cwire_layers, cwire_wires = cwire_connections(layers)
-    >>> bit_map
-    {measure(wires=[0]): 0, measure(wires=[1]): 1}
+    >>> bit_map = {m0.measurements[0]: 0, m1.measurements[0]: 1}
+    >>> new_bit_map, cwire_layers, cwire_wires = cwire_connections(layers, bit_map)
+    >>> new_bit_map == bit_map # No reusage happening
+    True
     >>> cwire_layers
-    [[0, 2, 3], [1, 2]]
+    {0: [[0, 2, 3]], 1: [[1, 2]]}
     >>> cwire_wires
-    [[0, 0, 3], [1, 0]]
+    {0: [[0, 0, 3]], 1: [[1, 0]]}
 
-    From this information, we can see that the first classical wire is active in layers
-    0, 2, and 3 while the second classical wire is active in layers 1 and 2.  The first "active"
+    From this information, we can see that classical wire ``0`` is active in layers
+    0, 2, and 3 while classical wire ``1`` is active in layers 1 and 2, with both classical
+    wires being used only once (the outer lists all have length 1). The first "active"
     layer will always be the one with the mid circuit measurement.
-
     """
     if len(bit_map) == 0:
-        return [], []
+        return bit_map, {}, {}
 
-    connected_layers = [[] for _ in bit_map]
-    connected_wires = [[] for _ in bit_map]
+    old_cwires = list(bit_map.values())
+    connected_layers = {cwire: [] for cwire in old_cwires}
+    connected_wires = {cwire: [] for cwire in old_cwires}
 
     for layer_idx, layer in enumerate(layers):
         for op in layer:
             if isinstance(op, MidMeasureMP) and op in bit_map:
-                connected_layers[bit_map[op]].append(layer_idx)
-                connected_wires[bit_map[op]].append(op.wires[0])
+                _meas = [op]
+                con_wire = op.wires[0]
 
             elif isinstance(op, Conditional):
-                for m in op.meas_val.measurements:
-                    cwire = bit_map[m]
-                    connected_layers[cwire].append(layer_idx)
-                    connected_wires[cwire].append(max(op.wires))
+                _meas = op.meas_val.measurements
+                con_wire = max(op.wires)
 
             elif isinstance(op, MeasurementProcess) and op.mv is not None:
                 if isinstance(op.mv, MeasurementValue):
-                    for m in op.mv.measurements:
-                        cwire = bit_map[m]
-                        connected_layers[cwire].append(layer_idx)
+                    _meas = op.mv.measurements
                 else:
-                    for m in op.mv:
-                        cwire = bit_map[m.measurements[0]]
-                        connected_layers[cwire].append(layer_idx)
+                    _meas = [m.measurements[0] for m in op.mv]
+                con_wire = None
 
-    return connected_layers, connected_wires
+            else:
+                continue
+
+            for m in _meas:
+                cwire = bit_map[m]
+                connected_layers[cwire].append(layer_idx)
+                if con_wire is not None:
+                    connected_wires[cwire].append(con_wire)
+
+    bit_map, connected_layers, connected_wires = _try_reusing_cwires(
+        bit_map, connected_layers, connected_wires
+    )
+
+    return bit_map, connected_layers, connected_wires
+
+
+def _try_reusing_cwires(bit_map, connected_layers, connected_wires):
+    # Extract (start, end) tuples (incl end) where each cwire is occupied with old bit map
+    occupation = {
+        cwire: (min(con_layer), max(con_layer)) for cwire, con_layer in connected_layers.items()
+    }
+    # Mark until where each cwire is currently occupied during the following loop.
+    # Start with -1 for each cwire
+    occ_ends = -np.ones(len(bit_map))
+    # Write a map from old cwires to new cwires
+    cwire_map = {}
+    for cwire, occ in occupation.items():
+        # Find the first cwire that is currently not occupied, i.e. that has its occupation end
+        # before the current occ starts (first entry of occ)
+        new_cwire = int(np.where(occ_ends < occ[0])[0][0])
+        # allocate a new (or the old) cwire based on the first one that was free above
+        cwire_map[cwire] = new_cwire
+        # Update the occupation end of the newly allocated cwire
+        occ_ends[new_cwire] = occ[1]
+    # Create an inverted cwire map that maps new cwires to all old cwires that are mapped to it
+    inv_cwire_map = {new_cwire: [] for new_cwire in cwire_map.values()}
+    for old_cwire in bit_map.values():
+        inv_cwire_map[cwire_map[old_cwire]].append(old_cwire)
+
+    # Collect the connected layers from all old cwires that are being mapped to the same new cwire
+    connected_layers = {
+        new_cwire: [connected_layers[w] for w in old_cwires]
+        for new_cwire, old_cwires in inv_cwire_map.items()
+    }
+    # Collect the connected wires from all old cwires that are being mapped to the same new cwire
+    connected_wires = {
+        new_cwire: [connected_wires[w] for w in old_cwires]
+        for new_cwire, old_cwires in inv_cwire_map.items()
+    }
+    # Update bit map according to the condensed/reused cwires
+    bit_map = {op: cwire_map[cwire] for op, cwire in bit_map.items()}
+    return bit_map, connected_layers, connected_wires
 
 
 def transform_deferred_measurements_tape(tape):


Test patch:
diff --git a/tests/drawer/test_draw.py b/tests/drawer/test_draw.py
index 2d255290d..b5a9a296e 100644
--- a/tests/drawer/test_draw.py
+++ b/tests/drawer/test_draw.py
@@ -575,12 +575,10 @@ class TestMidCircuitMeasurements:
             "1: ──RX(0.50)───║─────────┤↗├─├●─────┤↗├────├●─────║─├●────────────────────┤ │     \n"
             "2: ─────────────║──────────║──├●──Z───║──╭X─├○─────║─│───────────────────H─┤ ╰<Z@Z>\n"
             "3: ─────────────║──────────║──╰○──║───║──╰●─╰SWAP──║─╰●──┤↗₀├──RX(1.23)──║─┤       \n"
-            "                ╚══════════║═══╬══║═══║══════╝     ║      ║    ║         ║         \n"
-            "                           ╚═══╩══╩═══║════════════╬══════║════║═════════╣         \n"
-            "                                      ╚════════════╝      ║    ║         ║         \n"
-            "                                                          ╚════╩═════════╝         "
+            "                ╚══════════║═══╬══║═══║══════╝     ║      ╚════╩═════════╣         \n"
+            "                           ╚═══╩══╩═══║════════════╬═════════════════════╝         \n"
+            "                                      ╚════════════╝                               "
         )
-
         assert drawing == expected_drawing
 
     def test_single_meas_multi_cond_split_lines(self):
@@ -723,16 +721,14 @@ class TestMidCircuitMeasurements:
             "3: ─────────────║──────────║──╰○──║───║──╰●─╰SWAP──║─╰●\n"
             "                ╚══════════║═══╬══║═══║══════╝     ║   \n"
             "                           ╚═══╩══╩═══║════════════╬═══\n"
-            "                                      ╚════════════╝   \n"
-            "                                                       \n\n"
+            "                                      ╚════════════╝   \n\n"
             "─────────────────────┤ ╭<Z@Z>\n"
             "─────────────────────┤ │     \n"
             "───────────────────H─┤ ╰<Z@Z>\n"
             "───┤↗₀├──RX(1.23)──║─┤       \n"
-            "    ║    ║         ║         \n"
-            "════║════║═════════╣         \n"
-            "    ║    ║         ║         \n"
-            "    ╚════╩═════════╝         "
+            "    ╚════╩═════════╣         \n"
+            "═══════════════════╝         \n"
+            "                             "
         )
 
         assert drawing == expected_drawing
diff --git a/tests/drawer/test_drawable_layers.py b/tests/drawer/test_drawable_layers.py
index 729e0f746..123e86478 100644
--- a/tests/drawer/test_drawable_layers.py
+++ b/tests/drawer/test_drawable_layers.py
@@ -60,50 +60,33 @@ class TestRecursiveFindLayer:
 
     def test_first_mcm_stats_layer(self):
         """Test operation remains in 0th layer if not blocked"""
-        mp0 = qml.measurements.MidMeasureMP(0, id="foo")
-        mp1 = qml.measurements.MidMeasureMP(1, id="bar")
-        bit_map = {mp0: 0, mp1: 1}
-
         out = _recursive_find_mcm_stats_layer(
-            layer_to_check=0, op_occupied_cwires={0}, used_cwires_per_layer=[{1}], bit_map=bit_map
+            layer_to_check=0, op_occupied_cwires={0}, used_cwires_per_layer=[{1}]
         )
         assert out == 0
 
     def test_blocked_mcm_stats_layer(self):
         """Test operation moved to higher layer if blocked on 0th layer."""
-        mp = qml.measurements.MidMeasureMP(0)
-        bit_map = {mp: 0}
-
         out = _recursive_find_mcm_stats_layer(
-            layer_to_check=0, op_occupied_cwires={0}, used_cwires_per_layer=[{0}], bit_map=bit_map
+            layer_to_check=0, op_occupied_cwires={0}, used_cwires_per_layer=[{0}]
         )
         assert out == 1
 
     def test_recursion_no_block_mcm_stats(self):
         """Test recursion to zero if start in higher layer and not blocked"""
-        mp0 = qml.measurements.MidMeasureMP(0)
-        mp1 = qml.measurements.MidMeasureMP(1)
-        bit_map = {mp0: 0, mp1: 1}
-
         out = _recursive_find_mcm_stats_layer(
             layer_to_check=2,
             op_occupied_cwires={0},
             used_cwires_per_layer=[{1}, {1}, {1}],
-            bit_map=bit_map,
         )
         assert out == 0
 
     def test_recursion_block_mcm_stats(self):
         """Test blocked on layer 1 gives placement on layer 2"""
-        mp0 = qml.measurements.MidMeasureMP(0)
-        mp1 = qml.measurements.MidMeasureMP(1)
-        bit_map = {mp0: 0, mp1: 1}
-
         out = _recursive_find_mcm_stats_layer(
             layer_to_check=3,
             op_occupied_cwires={0},
             used_cwires_per_layer=[{1}, {0}, {1}, {1}],
-            bit_map=bit_map,
         )
         assert out == 2
 
diff --git a/tests/drawer/test_drawer_utils.py b/tests/drawer/test_drawer_utils.py
index ba4b35c1c..c21451e21 100644
--- a/tests/drawer/test_drawer_utils.py
+++ b/tests/drawer/test_drawer_utils.py
@@ -198,15 +198,17 @@ class TestCwireConnections:
 
     def test_null_circuit(self):
         """Test null behavior with an empty circuit."""
-        layers, wires = cwire_connections([[]], {})
-        assert layers == []
-        assert wires == []
+        bit_map, layers, wires = cwire_connections([[]], {})
+        assert layers == {}
+        assert wires == {}
+        assert bit_map == {}
 
     def test_single_measure(self):
         """Test a single meassurment that does not have a conditional."""
-        layers, wires = cwire_connections([qml.measure(0).measurements], {})
-        assert layers == []
-        assert wires == []
+        bit_map, layers, wires = cwire_connections([qml.measure(0).measurements], {})
+        assert layers == {}
+        assert wires == {}
+        assert bit_map == {}
 
     def test_single_measure_single_cond(self):
         """Test a case with a single measurement and a single conditional."""
@@ -215,9 +217,10 @@ class TestCwireConnections:
         layers = [m.measurements, [cond]]
         bit_map = {m.measurements[0]: 0}
 
-        clayers, wires = cwire_connections(layers, bit_map)
-        assert clayers == [[0, 1]]
-        assert wires == [[0, 0]]
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 1]]}
+        assert wires == {0: [[0, 0]]}
+        assert new_bit_map == bit_map
 
     def test_multiple_measure_multiple_cond(self):
         """Test a case with multiple measurements and multiple conditionals."""
@@ -230,9 +233,10 @@ class TestCwireConnections:
         bit_map = {m0.measurements[0]: 0, m1.measurements[0]: 1}
 
         layers = [m0.measurements, m1.measurements, [cond0], m2_nonused.measurements, [cond1]]
-        clayers, wires = cwire_connections(layers, bit_map)
-        assert clayers == [[0, 2], [1, 2, 4]]
-        assert wires == [[0, 1], [1, 1, 2]]
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 2]], 1: [[1, 2, 4]]}
+        assert wires == {0: [[0, 1]], 1: [[1, 1, 2]]}
+        assert new_bit_map == bit_map
 
     def test_measurements_layer(self):
         """Test cwire_connections works if measurement layers are appended at the end."""
@@ -241,9 +245,10 @@ class TestCwireConnections:
         cond0 = qml.ops.Conditional(m0, qml.S(0))
         layers = [m0.measurements, [cond0], [qml.expval(qml.PauliX(0))]]
         bit_map = {m0.measurements[0]: 0}
-        clayers, wires = cwire_connections(layers, bit_map)
-        assert clayers == [[0, 1]]
-        assert wires == [[0, 0]]
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 1]]}
+        assert wires == {0: [[0, 0]]}
+        assert new_bit_map == bit_map
 
     def test_mid_measure_stats_layer(self):
         """Test cwire_connections works if layers contain terminal measurements using measurement
@@ -252,6 +257,125 @@ class TestCwireConnections:
         m0 = qml.measure(0)
         layers = [m0.measurements, [qml.expval(m0)]]
         bit_map = {m0.measurements[0]: 0}
-        clayers, wires = cwire_connections(layers, bit_map)
-        assert clayers == [[0, 1]]
-        assert wires == [[0]]
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 1]]}
+        assert wires == {0: [[0]]}
+        assert new_bit_map == bit_map
+
+    def test_single_mid_measure_cond_and_stats_layer(self):
+        """Test cwire_connections works if layers contain terminal measurements using measurement
+        values"""
+
+        m0 = qml.measure(1)
+        cond0 = qml.ops.Conditional(m0, qml.X(0))
+        layers = [m0.measurements, [cond0], [qml.expval(m0)]]
+        bit_map = {m0.measurements[0]: 0}
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 1, 2]]}
+        assert wires == {0: [[1, 0]]}
+        assert new_bit_map == bit_map
+
+    def test_multi_mid_measure_stats_layer(self):
+        """Test cwire_connections works if layers contain multiple MCMs, no conditionals,
+        and one or multiple terminal measurements using measurement values"""
+
+        m0 = qml.measure(0)
+        m1 = qml.measure(1)
+        m2 = qml.measure(0)
+
+        # final expvals prevent wire reusing
+        layers = [
+            m0.measurements,
+            m1.measurements,
+            m2.measurements,
+            [qml.expval(m0), qml.expval(m1), qml.expval(m2)],
+        ]
+        bit_map = {m0.measurements[0]: 0, m1.measurements[0]: 1, m2.measurements[0]: 2}
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 3]], 1: [[1, 3]], 2: [[2, 3]]}
+        assert wires == {0: [[0]], 1: [[1]], 2: [[0]]}
+        assert new_bit_map == bit_map
+
+        # should not draw cwire for m2 if there is no usage of it
+        layers = [
+            m0.measurements,
+            m1.measurements,
+            m2.measurements,
+            [qml.expval(m0), qml.expval(m1)],
+        ]
+        bit_map = {m0.measurements[0]: 0, m1.measurements[0]: 1}
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 3]], 1: [[1, 3]]}
+        assert wires == {0: [[0]], 1: [[1]]}
+        assert new_bit_map == bit_map
+
+        # should not draw cwire for m1 if there is no usage of it
+        layers = [
+            m0.measurements,
+            m1.measurements,
+            m2.measurements,
+            [qml.expval(m0), qml.expval(m2)],
+        ]
+        bit_map = {m0.measurements[0]: 0, m2.measurements[0]: 1}
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 3]], 1: [[2, 3]]}
+        assert wires == {0: [[0]], 1: [[0]]}
+        assert new_bit_map == bit_map
+
+    def test_multi_mid_measure_cond_and_stats_layer(self):
+        """Test cwire_connections works if layers contain multiple MCMs, multiple conditionals,
+        and one or multiple terminal measurements using measurement values"""
+
+        m0 = qml.measure(0)
+        m1 = qml.measure(1)
+        cond0 = qml.ops.Conditional(m0, qml.X(1))
+        cond1 = qml.ops.Conditional(m1, qml.S(1))
+        bit_map = {m0.measurements[0]: 0, m1.measurements[0]: 1}
+
+        # final expval prevents wire reusing
+        layers = [m0.measurements, [cond0], m1.measurements, [qml.X(0), cond1], [qml.expval(m0)]]
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 1, 4]], 1: [[2, 3]]}
+        assert wires == {0: [[0, 1]], 1: [[1, 1]]}
+        assert new_bit_map == bit_map
+
+        # Nested measuring + cond already prevents wire reusing
+        layers = [m0.measurements, m1.measurements, [cond0], [qml.X(0), cond1], [qml.expval(m1)]]
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 2]], 1: [[1, 3, 4]]}
+        assert wires == {0: [[0, 1]], 1: [[1, 1]]}
+        assert new_bit_map == bit_map
+
+        # Wire can be reused
+        layers = [m0.measurements, [cond0], m1.measurements, [qml.X(0), cond1], [qml.expval(m1)]]
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 1], [2, 3, 4]]}
+        assert wires == {0: [[0, 1], [1, 1]]}
+        assert new_bit_map == {key: 0 for key in bit_map}
+
+        # Wire could be reused if it wasn't for the second expval
+        layers = [
+            m0.measurements,
+            [cond0],
+            m1.measurements,
+            [qml.X(0), cond1],
+            [qml.expval(m1), qml.expval(m0)],
+        ]
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+        assert clayers == {0: [[0, 1, 4]], 1: [[2, 3, 4]]}
+        assert wires == {0: [[0, 1]], 1: [[1, 1]]}
+        assert new_bit_map == bit_map
+
+    @pytest.mark.parametrize("rep", [2, 10])
+    def test_reuse_cwire_many_times(self, rep):
+        """Test that a measure + conditional executed multiple times only uses one cwire."""
+        m = [qml.measure(0) for _ in range(rep)]
+        conds = [qml.ops.Conditional(_m, qml.RX(i * 0.1, 2)) for i, _m in enumerate(m)]
+        bit_map = {_m.measurements[0]: i for i, _m in enumerate(m)}
+        layers = sum(([_m.measurements, [_c]] for _m, _c in zip(m, conds)), start=[])
+
+        new_bit_map, clayers, wires = cwire_connections(layers, bit_map)
+
+        assert clayers == {0: [[2 * i, 2 * i + 1] for i in range(rep)]}
+        assert wires == {0: [[0, 2]] * rep}
+        assert new_bit_map == {_m.measurements[0]: 0 for _m in m}
diff --git a/tests/drawer/test_tape_text.py b/tests/drawer/test_tape_text.py
index b291f27e7..b3045b098 100644
--- a/tests/drawer/test_tape_text.py
+++ b/tests/drawer/test_tape_text.py
@@ -84,7 +84,7 @@ def test_error_if_unsupported_object_in_tape():
         qml.drawer.tape_text(_tape)
 
 
-class TestHelperFunctions:  # pylint: disable=too-many-arguments
+class TestHelperFunctions:  # pylint: disable=too-many-arguments, too-many-positional-arguments
     """Test helper functions for the tape text."""
 
     @pytest.mark.parametrize(
@@ -100,11 +100,7 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
         config = _Config(
             wire_map=default_wire_map, bit_map=default_bit_map, num_op_layers=4, cur_layer=0
         )
-        assert out == _add_grouping_symbols(
-            op,
-            ["", "", "", ""],
-            config,
-        )
+        assert out == _add_grouping_symbols(op, ["", "", "", ""], config)
 
     @pytest.mark.parametrize(
         "op, bit_map, layer_str, out",
@@ -126,11 +122,8 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
     )
     def test_add_mid_measure_grouping_symbols(self, op, layer_str, bit_map, out):
         """Test private _add_grouping_symbols function renders as expected for MidMeasureMPs."""
-        assert out == _add_mid_measure_grouping_symbols(
-            op,
-            layer_str,
-            _Config(wire_map=default_wire_map, bit_map=bit_map, num_op_layers=4, cur_layer=1),
-        )
+        config = _Config(wire_map=default_wire_map, bit_map=bit_map, num_op_layers=4, cur_layer=1)
+        assert out == _add_mid_measure_grouping_symbols(op, layer_str, config)
 
     @pytest.mark.parametrize(
         "cond_op, args, kwargs, out, bit_map, mv, cur_layer",
@@ -191,15 +184,11 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
             wire_map=default_wire_map,
             bit_map=bit_map,
             cur_layer=cur_layer,
-            cwire_layers=[[0], [1]],
+            cwire_layers={0: [[0]], 1: [[1]]},
             num_op_layers=4,
         )
 
-        assert out == _add_cond_grouping_symbols(
-            op,
-            layer_str,
-            config,
-        )
+        assert out == _add_cond_grouping_symbols(op, layer_str, config)
 
     @pytest.mark.parametrize(
         "mps, bit_map, out",
@@ -222,11 +211,7 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
         layer_str = [" "] * (len(default_wire_map) + len(bit_map))
         config = _Config(wire_map=default_wire_map, bit_map=bit_map, num_op_layers=4, cur_layer=1)
 
-        assert out == _add_cwire_measurement_grouping_symbols(
-            mps,
-            layer_str,
-            config,
-        )
+        assert out == _add_cwire_measurement_grouping_symbols(mps, layer_str, config)
 
     @pytest.mark.parametrize(
         "mp, bit_map, out",
@@ -256,11 +241,7 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
         """Test private _add_cwire_measurement renders as expected."""
         layer_str = [" "] * (len(default_wire_map) + len(bit_map))
         config = _Config(wire_map=default_wire_map, bit_map=bit_map, num_op_layers=4, cur_layer=1)
-        assert out == _add_cwire_measurement(
-            mp,
-            layer_str,
-            config,
-        )
+        assert out == _add_cwire_measurement(mp, layer_str, config)
 
     @pytest.mark.parametrize(
         "op, out",
@@ -280,11 +261,7 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
         config = _Config(
             wire_map=default_wire_map, bit_map=default_bit_map, num_op_layers=4, cur_layer=1
         )
-        assert out == _add_measurement(
-            op,
-            [""] * 4,
-            config,
-        )
+        assert out == _add_measurement(op, [""] * 4, config)
 
     def test_add_measurements_cache(self):
         """Test private _add_measurement function with a matrix cache."""
@@ -297,27 +274,13 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
             num_op_layers=4,
             cur_layer=1,
         )
-        assert _add_measurement(
-            op,
-            ["", ""],
-            config,
-        ) == [
-            "<𝓗(M0)>",
-            "",
-        ]
+        assert _add_measurement(op, ["", ""], config) == ["<𝓗(M0)>", ""]
 
         assert qml.math.allclose(cache["matrices"][0], np.eye(2))
 
         op2 = qml.expval(qml.Hermitian(np.eye(2), wires=1))
         # new op with same matrix, should have same M0 designation
-        assert _add_measurement(
-            op2,
-            ["", ""],
-            config,
-        ) == [
-            "",
-            "<𝓗(M0)>",
-        ]
+        assert _add_measurement(op2, ["", ""], config) == ["", "<𝓗(M0)>"]
 
     @pytest.mark.parametrize(
         "op, out",
@@ -336,11 +299,7 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
         config = _Config(
             wire_map=default_wire_map, bit_map=default_bit_map, num_op_layers=4, cur_layer=1
         )
-        assert out == _add_obj(
-            op,
-            ["─"] * 4,
-            config,
-        )
+        assert out == _add_obj(op, ["─"] * 4, config)
 
     @pytest.mark.parametrize(
         "op, bit_map, layer_str, out",
@@ -402,15 +361,11 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
             wire_map=default_wire_map,
             bit_map=bit_map,
             cur_layer=1,
-            cwire_layers=[[0], [1]],
+            cwire_layers={0: [[0]], 1: [[1]]},
             num_op_layers=4,
         )
 
-        assert out == _add_obj(
-            op,
-            layer_str,
-            config,
-        )
+        assert out == _add_obj(op, layer_str, config)
 
     @pytest.mark.parametrize(
         "op, out",
@@ -425,11 +380,7 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
         config = _Config(
             wire_map=default_wire_map, bit_map=default_bit_map, num_op_layers=4, cur_layer=1
         )
-        start = _add_obj(
-            qml.PauliX(0),
-            ["─"] * 4,
-            config,
-        )
+        start = _add_obj(qml.PauliX(0), ["─"] * 4, config)
         assert out == _add_obj(op, start, config)
 
     def test_add_obj_cache(self):
@@ -443,19 +394,11 @@ class TestHelperFunctions:  # pylint: disable=too-many-arguments
             num_op_layers=4,
             cur_layer=1,
         )
-        assert _add_obj(
-            op1,
-            ["", ""],
-            config,
-        ) == ["U(M0)", ""]
+        assert _add_obj(op1, ["", ""], config) == ["U(M0)", ""]
 
         assert qml.math.allclose(cache["matrices"][0], np.eye(2))
         op2 = qml.QubitUnitary(np.eye(2), wires=1)
-        assert _add_obj(
-            op2,
-            ["", ""],
-            config,
-        ) == ["", "U(M0)"]
+        assert _add_obj(op2, ["", ""], config) == ["", "U(M0)"]
 
 
 class TestEmptyTapes: