Skip to content

fix(transpiler): RemoveFinalMeasurements incorrectly removes measurements feeding control-flow conditions#15959

Open
adcorcol wants to merge 1 commit intoQiskit:mainfrom
adcorcol:fix/remove-final-measurements-control-flow-14319
Open

fix(transpiler): RemoveFinalMeasurements incorrectly removes measurements feeding control-flow conditions#15959
adcorcol wants to merge 1 commit intoQiskit:mainfrom
adcorcol:fix/remove-final-measurements-control-flow-14319

Conversation

@adcorcol
Copy link
Copy Markdown
Member

@adcorcol adcorcol commented Apr 7, 2026

Summary

RemoveFinalMeasurements was incorrectly removing measurements whose classical output feeds a control-flow condition (while_loop, if_else, switch_case). The pass only checked quantum wire successors to determine if a measurement was "final", completely ignoring classical wire successors. Removing such measurements left the condition register at its default value 0, causing while_loop circuits to run indefinitely.

Fixes #14319.

Root cause

In calc_final_ops, the reverse DAG traversal walks backwards through quantum wire predecessors. A measure node's classical wire successors were never inspected, so a measurement whose clbit feeds a while_loop condition was indistinguishable from a truly final one.

Fix

Before marking a node as final, check whether any classical successor is a DAGOpNode that is not itself a measure. A measure successor merely overwrites the clbit (safe to remove); any other op means the clbit value is being read and the measurement must be preserved. For all other classical successors we err on the side of preservation (conservative but correct).

if any(
    isinstance(s, DAGOpNode) and s.name != "measure"
    for s in dag.classical_successors(node)
):
    continue

Test plan

  • test_measure_feeding_while_loop_condition_not_removed
  • test_measure_feeding_if_else_condition_not_removed
  • test_measure_feeding_switch_case_condition_not_removed
  • test_chained_measures_first_is_removed_second_feeds_condition — exercises the != "measure" branch: first measure is overwritten and correctly removed, second feeds the condition and is preserved
  • All 9 existing tests continue to pass
  • LightCone pass (which also uses calc_final_ops) tested and unaffected

🤖 Generated with Claude Code

… control-flow conditions

calc_final_ops only checked quantum wire successors to decide whether a
measurement was final, completely ignoring classical wire successors. This
caused measurements whose classical output feeds a while_loop, if_else, or
switch_case condition to be incorrectly removed, leaving the condition
register at its default value 0 and causing while_loop circuits to run
indefinitely.

Fix: before marking a measurement as final, check whether any classical
successor is a DAGOpNode that is not itself a measure. A measure successor
merely overwrites the clbit and is safe to ignore; any other op (control-flow
condition, etc.) means the clbit value is being read and the measurement must
be preserved. For all other classical successors we err on the side of
preservation (conservative but correct).

Fixes Qiskit#14319.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@adcorcol adcorcol requested a review from a team as a code owner April 7, 2026 17:59
@qiskit-bot
Copy link
Copy Markdown
Collaborator

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

  • @Qiskit/terra-core

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

while_loop enters infinite loop after RemoveFinalMeasurements pass

2 participants