Skip to content

fix: preserve physical qubits in reset statements#325

Open
ryanhill1 wants to merge 3 commits into
mainfrom
fix/physical-qubit-reset
Open

fix: preserve physical qubits in reset statements#325
ryanhill1 wants to merge 3 commits into
mainfrom
fix/physical-qubit-reset

Conversation

@ryanhill1

@ryanhill1 ryanhill1 commented Jul 12, 2026

Copy link
Copy Markdown
Member

Summary of changes

reset on a physical qubit rewrote the operand to the internal pulse register: reset $2; unrolled to reset __PYQASM_QUBITS__[2];.

Two consequences:

  1. The unrolled output isn't valid OpenQASM. __PYQASM_QUBITS__ is a register the program never declares, so the unrolled program does not round-trip through dumps()loads().
  2. The qubit is never counted. The early return skipped _register_physical_qubit, so a program whose only operation is reset $3; reports num_qubits == 0.

Gate and measurement operands already branch on _openpulse_grammar_declared and keep $n as-is for plain QASM programs (# Plain QASM program: keep the physical qubit identifier as-is.). Reset was applying the OpenPulse rename unconditionally. It now follows the same rule: keep $n and register the qubit for plain QASM, rename only for OpenPulse programs, where the pulse visitor expects it.

Physical qubits are valid OpenQASM 3 and are what Qiskit emits when a circuit is transpiled against a backend (qasm3.dumps(transpile(circuit, backend))), so this shape shows up in real user programs.

How it was found

Downstream in qbraid-qir, whose QIR visitor could not lower the __PYQASM_QUBITS__[2] identifier. Surfaced from 52 production job failures on qBraid's QIR simulator.

Tests

Three new tests in tests/qasm3/test_reset.py, each failing before the fix:

  • test_reset_physical_qubit_preserves_identifier — unrolled output keeps reset $2;
  • test_reset_physical_qubit_is_countedreset $3; alone gives num_qubits == 4
  • test_reset_physical_qubit_unrolled_output_is_reloadable — the unrolled program reloads and validates

Full suite green with the CI extras (test, cli, pulse): 674 passed, 3 skipped (baseline 671 + these 3). pylint 10/10, mypy and black clean.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed physical-qubit reset operations in plain OpenQASM so operands remain unchanged.
    • Corrected qubit counting for programs containing only a physical-qubit reset.
    • Ensured unrolled reset operations produce valid, reloadable OpenQASM.
    • Preserved expected internal handling for OpenPulse programs.
  • Tests

    • Added coverage for identifier preservation, qubit counting, round-tripping, and validation.

_visit_reset rewrote a physical qubit operand to the internal pulse
register unconditionally, so "reset $2;" unrolled to
"reset __PYQASM_QUBITS__[2];". That names a register the program never
declares, so the unrolled output did not round-trip through
dumps()/loads(), and the qubit was never registered -- a program whose
only operation was "reset $3;" reported num_qubits == 0.

Gate and measurement operands already branch on the OpenPulse grammar
flag and keep "$n" as-is for plain QASM programs. Reset now does the
same, applying the rename only for OpenPulse programs, where the pulse
visitor expects it.
@ryanhill1 ryanhill1 requested a review from TheGupta2012 as a code owner July 12, 2026 14:27
@coderabbitai

coderabbitai Bot commented Jul 12, 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

Run ID: 7160a393-72c4-47e6-b5c0-c322a8f0a825

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:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/physical-qubit-reset

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

codecov-commenter commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pyqasm/visitor.py 92.30% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/pyqasm/visitor.py (1)

711-714: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add an OpenPulse regression case.

The added tests cover plain-QASM preservation, counting, and reloadability, but not the OpenPulse branch that rewrites $n to __PYQASM_QUBITS__[n]. Add a test asserting that behavior remains valid.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pyqasm/visitor.py` around lines 711 - 714, Add an OpenPulse regression
test covering the branch in the visitor that handles _openpulse_grammar_declared
and rewrites a $n qubit reference to __PYQASM_QUBITS__[n]. Assert the
transformed name is correct and the resulting program remains valid, while
preserving the existing plain-QASM test coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pyqasm/visitor.py`:
- Around line 698-705: Add an Args entry to _resolve_unindexed_reset_qubit’s
docstring documenting the statement parameter as the QuantumReset being
resolved, while preserving the existing Returns documentation.
- Line 722: Update the internal-register check in the visitor method containing
the __PYQASM_QUBITS__ prefix test so it matches only the generated indexed
register form, not user identifiers with the same prefix; preserve
_get_op_bits() validation and unrolling for all other operands.

In `@tests/qasm3/test_reset.py`:
- Around line 123-148: Update all three newly added test functions in
tests/qasm3/test_reset.py to declare an explicit -> None return annotation and
add a “Returns: None” section to each test’s docstring, including
test_reset_physical_qubit_preserves_identifier and the two adjacent tests.

---

Nitpick comments:
In `@src/pyqasm/visitor.py`:
- Around line 711-714: Add an OpenPulse regression test covering the branch in
the visitor that handles _openpulse_grammar_declared and rewrites a $n qubit
reference to __PYQASM_QUBITS__[n]. Assert the transformed name is correct and
the resulting program remains valid, while preserving the existing plain-QASM
test coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e2d5b345-2d11-42b2-9175-c4a5cb130a4e

📥 Commits

Reviewing files that changed from the base of the PR and between 13a1b0b and 55fafb7.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/pyqasm/visitor.py
  • tests/qasm3/test_reset.py

Comment thread src/pyqasm/visitor.py
Comment thread src/pyqasm/visitor.py Outdated
Comment thread tests/qasm3/test_reset.py
Raised in review. The reset path treated any identifier starting with
"__PYQASM_QUBITS__" as the internal pulse register, which also swallowed a
user register that merely starts with that name:

    qubit[2] __PYQASM_QUBITS__foo;
    reset __PYQASM_QUBITS__foo;      // silently reset nothing

The statement was short-circuited out of unrolling and emitted verbatim,
where a normally-named register expands to one reset per qubit.

Match the exact name, or the name followed by an index or a slice, which are
the forms the transformer actually generates ("__PYQASM_QUBITS__",
"__PYQASM_QUBITS__[2]", "__PYQASM_QUBITS__[0:2]"). Matching only the indexed
form, as first suggested, would silently drop the bare and slice forms.

Also documents the helper's parameter.
Comment thread src/pyqasm/visitor.py
logger = logging.getLogger(__name__)

# Reserved register that physical qubits are consolidated onto for OpenPulse programs.
_INTERNAL_QUBIT_REGISTER = "__PYQASM_QUBITS__"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a good refactoring opportunity. Can you use this across the file so that this convention is embedded properly across the code base?

@TheGupta2012 TheGupta2012 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good mostly @ryanhill1 , can you just trickle the _INTERNAL_QUBIT_REGISTER across the file?

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.

3 participants