Skip to content

fix: emit fresh operand nodes from gate decompositions#335

Open
ryanhill1 wants to merge 2 commits into
mainfrom
fix-unroll-aliased-operand-nodes
Open

fix: emit fresh operand nodes from gate decompositions#335
ryanhill1 wants to merge 2 commits into
mainfrom
fix-unroll-aliased-operand-nodes

Conversation

@ryanhill1

Copy link
Copy Markdown
Member

Summary of changes

Closes #333. Root-cause fix for the operand-node aliasing behind #331 as well.

Gate decomposition functions in maps/gates.py pass the same qubit operand nodes into every statement they emit (e.g. crz_gate hands qubit1 to two u3_gate and two cx expansions), so the unrolled AST contained IndexedIdentifier objects shared across many statements. Any transformation that rewrites qubit indices in place then mutated a shared node once per referencing statement:

This PR fixes the class of bug at the source: the five statement constructors in maps/gates.py (one_qubit_gate_op, one_qubit_rotation_op, two_qubit_gate_op, ccx_gate_op, global_phase_gate) now deep-copy their qubit operands via a shared _fresh_qubits helper, and Decomposer deep-copies the operands of each rule-emitted gate. Every decomposition composes these constructors, so no emitted statement can share a node with another statement or with the source operation.

With fresh nodes, reverse_qubit_order()'s existing marker logic is correct as-is, and this branch (without #332's guard) independently resolves the #331 repro. #332's guard remains useful belt-and-braces and merges cleanly alongside this.

Verified beyond the test suite: the reversed/remapped crz circuits are unitarily equivalent to the expected reference circuits via qiskit Operator.equiv.

Tests

  • test_reverse_qubit_order_gate_decomposition: the reverse_qubit_order() KeyError on aliased operand nodes (same root cause as #331) #333 repro, checked against the full expected unrolled program.
  • test_unroll_emits_fresh_operand_nodes: invariant test asserting no two quantum statements share an operand or index node after unroll(), parametrized over crz, crx, c4x, ecr, and inv @ crz.
  • test_rebase_emits_fresh_operand_nodes: same invariant for rebase() output.

🤖 Generated with Claude Code

Gate decomposition functions passed the same IndexedIdentifier objects
into every statement they emitted, so in-place index rewrites in
remove_idle_qubits() and reverse_qubit_order() mutated a shared node
once per referencing statement, raising KeyError whenever the remap
was not the identity.

Statement constructors in maps/gates.py and Decomposer now deep-copy
their qubit operands so every emitted statement owns its nodes.

Fixes #333
@ryanhill1
ryanhill1 requested a review from TheGupta2012 as a code owner July 24, 2026 16:36
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 61f038e4-3890-4920-93c5-5599d2d9f549

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-unroll-aliased-operand-nodes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

reverse_qubit_order() KeyError on aliased operand nodes (same root cause as #331)

2 participants