Skip to content

Stop OptimizeSwapBeforeMeasure recursing into control-flow blocks - #17012

Open
Eduardo Alcantara (edubraqd) wants to merge 1 commit into
Qiskit:mainfrom
edubraqd:fix/16853-swap-before-measure-control-flow
Open

Eduardo Alcantara (edubraqd) wants to merge 1 commit into
Qiskit:mainfrom
edubraqd:fix/16853-swap-before-measure-control-flow

Conversation

@edubraqd

Copy link
Copy Markdown

Summary

Fixes #16853

OptimizeSwapBeforeMeasure removes a terminal SWAP inside a control-flow block even though the swapped state is observed by measurements after the block:

qc = QuantumCircuit(2, 2)
qc.x(0)
with qc.for_loop(range(1)):
    qc.swap(0, 1)
qc.measure(0, 0)
qc.measure(1, 1)

PassManager([OptimizeSwapBeforeMeasure()]).run(qc)
# loop body is now empty; the program measures '01' instead of '10'

Cause

The pass is decorated with control_flow.trivial_recurse, so each block is optimized as an independent DAGCircuit. Within a block the DAGOutNodes are only the block boundary, not the end of the program, but the eligibility check treats them as terminal and removes the swap. The same problem exists when the swap is followed only by measurements inside the block: retargeting the in-block measurements keeps the classical results, but the qubits remain live after the block in the swapped positions, so any later measurement of them outside the block is wrong.

Fix

Remove the recursion. The pass now only acts on the top level of the circuit and leaves control-flow blocks untouched, which is the conservative behaviour discussed in #7642 (a terminal swap of a complete program has no observable effect; a terminal swap of a block does). The pass is not part of the preset pass managers, so this only affects explicit use.

The two existing tests that asserted the recursion (test_if_else, test_nested_control_flow) are replaced by tests asserting that a block is left unchanged, plus the regression case from the issue, the "swap followed by in-block measurements" case, and a check that a top-level swap after a control-flow block is still optimized.

Details and comments

Verified with a BasicSimulator run on main vs this branch: the issue's reproducer returns {'10': 1024} before and after the pass with this change (was {'01': 1024} after).

AI/LLM disclosure

  • No part of this submission is LLM generated.
  • Some written text was generated by: Claude (Claude Code), edited and reviewed by me.
  • Some submitted code was generated by: Claude (Claude Code), reviewed and tested by me.

🤖 Generated with Claude Code

The pass was decorated with `control_flow.trivial_recurse`, so each
control-flow block was optimized as an independent DAG. Inside a block the
`DAGOutNode`s are only the block boundary, not the end of the program, so a
terminal swap (or a swap followed only by in-block measurements) was removed
while the swapped qubits were still live after the block, and measurements
outside the block observed the wrong state.

Remove the recursion: the pass now only acts on the top level of the circuit
and leaves control-flow blocks untouched.

Fixes Qiskit#16853

Co-Authored-By: Claude Opus 5 <[email protected]>
@qiskit-bot qiskit-bot added the Community PR PRs from contributors that are not 'members' of the Qiskit repo label Sep 19, 2026
@CLAassistant

CLAassistant commented Sep 19, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@qiskit-bot

Copy link
Copy Markdown
Collaborator

Thank you for opening a new pull request.

Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient.

While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone.

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Community PR PRs from contributors that are not 'members' of the Qiskit repo

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

OptimizeSwapBeforeMeasure can drop observable terminal SWAPs inside control-flow blocks

3 participants