PennyLaneAI/pennylane · issue #7807 · branch fix/7807-draw-wires-consistency
Same circuit, two devices that differ only in whether wires=N is passed, two different drawings. The fix sits in a 10-line branch of _add_measurement that forgot to call _add_grouping_symbols on the broadcast-over-all-wires path. Mirroring the pattern already used by _add_global_op closes the asymmetry without touching the single-wire happy path.
The reporter compares two devices that differ only in whether default.qubit receives an explicit wires=N parameter. With wires=3, the State measurement renders with the multi-wire grouping brackets (╭ ├ ╰). Without wires, the same measurement renders as a flush label with a leading space. The circuit itself is identical; only the drawer output differs.
Either form is internally consistent; the bug is the asymmetry between them.
In pennylane/drawer/_add_obj.py, the _add_measurement dispatcher splits the rendering into two branches on line 386:
len(m.wires) > 0 (explicit-wires path): _add_grouping_symbols(m.wires, layer_str, config) runs at line 375 and emits the box characters across the listed wires.len(m.wires) == 0 (broadcast path): the same call returns the input unchanged (_add_grouping_symbols early-exits on empty op_wires), then the label is appended to every wire's string without grouping.When the device is constructed with wires=N, the measurement's m.wires is populated with [0, …, N-1] upstream and the first branch runs. When the device has no fixed wire count, m.wires stays empty and the second branch runs.
flowchart TD
Start[_add_measurement called] --> Probe{len of m.wires}
Probe -- "> 0 (explicit wires)" --> P1[_add_grouping_symbols m.wires]
P1 --> P2[append label to listed wires]
P2 --> OutA["✓ rendered with ╭ ├ ╰ brackets"]
Probe -- "== 0 (broadcast over device)" --> Q1[_add_grouping_symbols early-returns]
Q1 --> Q2[append label to every wire]
Q2 --> OutB["✗ rendered without brackets, issue #7807"]
style OutA fill:#0a1e10,stroke:#10b981,color:#86efac
style OutB fill:#1e0a0a,stroke:#ef4444,color:#fca5a5
_add_global_op already usesPennyLane's drawer already knows how to render an operation that spans every wire, it does so for GlobalPhase and Identity via _add_global_op, which calls _add_grouping_symbols(list(config.wire_map.keys()), …) when the operation's own wires are empty. The fix is to apply the same trick inside the broadcast branch of _add_measurement.
Single-wire devices stay untouched: _add_grouping_symbols returns the input unchanged when there is only one wire (if len(op_wires) <= 1: return layer_str). From two wires up, both render paths now produce the same brackets.
_add_grouping_symbols early-exits._add_cwire_measurement.Two test snapshots tested the function I changed directly and are updated in-place. The remaining multi-line snapshots in test_draw.py ship one column wider after this change, they are documented in the README so the maintainer (or CI) can pin them up against the actual pytest diff.
_add_measurement on the default 4-wire wire map. qp.state() now expected as ["╭State", "├State", "├State", "╰State"]; qp.sample() same shape.test_draw_all_wire_measurements snapshot for the 2-wire (sample, probs, counts) parametrize.test_draw.py:1011-1014 and :1032-1036 (multi-wire qp.probs() rendering inside mid-circuit measurement tests). They ship one column wider; their surrounding column alignment needs eyeballing against pytest output, which I could not run locally this session.╚═══╝ separator and mid-circuit conditional bars are aligned column-perfect. Updating them blind risks a worse snapshot than the one I'd replace. With one local pytest run those updates are mechanical; without one, they are a guess.Local branch fix/7807-draw-wires-consistency, commit 23e005d. Two snapshot tests updated, the remainder documented.
Mail to a named PennyLane drawer maintainer, anchored to the branch URL. The drawer module has a small set of regular contributors; reaching one of them on LinkedIn or via a GitHub @mention is the path to first reply.
Realistic odds for this lead alone: ~30-50% conversation (PennyLane has active maintainers), ~5-15% contract (Xanadu hires more often for full-time than for contract). The win condition here is more likely a longer-cycle FT pipeline than an immediate retainer.