Skip to content

fix: drop unroll-emitted global phase for QASM 2 targets - #358

Open
ryanhill1 wants to merge 3 commits into
mainfrom
fix-qasm2-gphase
Open

fix: drop unroll-emitted global phase for QASM 2 targets#358
ryanhill1 wants to merge 3 commits into
mainfrom
fix-qasm2-gphase

Conversation

@ryanhill1

@ryanhill1 ryanhill1 commented Aug 7, 2026

Copy link
Copy Markdown
Member

Fixes #351

rzz/rxx decompose to a body containing a QuantumPhase node, which Qasm2Module emitted as gphase(...) — syntax OpenQASM 2 does not have, so unrolling a legal qelib1 gate produced output no QASM 2 parser accepts. The same node also broke re-filtering after reverse_qubit_order()/remove_idle_qubits().

Global phase is unobservable, so Qasm2Module.accept() now strips QuantumPhase statements (descending into conditional bodies) from the unrolled AST. A user-written gphase in QASM 2 source is still rejected, and the #339 interim diagnostic is retained for that case with its message trimmed.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed QASM 2 transformations involving conditional rzz and rxx operations.
    • Generated global-phase operations are now removed during unrolling, while user-authored gphase statements remain unsupported and are rejected.
    • Improved compatibility with idle-qubit removal and repeated reload/unroll workflows.
  • Tests

    • Added regression coverage for operation decomposition, conditional transformations, and round-trip processing.

rzz/rxx decompose to a body containing a QuantumPhase node, which
Qasm2Module emitted as gphase(...) — syntax OpenQASM 2 does not have,
so the unrolled output was not a loadable QASM 2 program. Global phase
is unobservable, so Qasm2Module.accept() now strips QuantumPhase
statements (descending into conditional bodies) from the unrolled AST.
User-written gphase in QASM 2 source is still rejected.

Fixes #351
@ryanhill1
ryanhill1 requested a review from TheGupta2012 as a code owner August 7, 2026 13:08
@argus-eye

argus-eye Bot commented Aug 7, 2026

Copy link
Copy Markdown

Argus review

Auto-review is off for this repo. Tick the box below to run a review on this PR.

  • Trigger Argus review

Estimated cost

  • Files changed: 4
  • Diff lines (±): 142
  • Historical avg: ~318.9k tokens · ~$1.35 · across last 6 review(s)

Tip: you can also comment @argus-eye review at any time.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

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: 951fafdd-0bbb-4d32-9654-e59c42083ca0

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
📝 Walkthrough

Walkthrough

QASM 2 unrolling now removes generated global phases from top-level and conditional statements. User-written gphase statements remain invalid. Tests cover decompositions, conditional handling, refiltering, round trips, and validation.

Changes

QASM 2 global-phase handling

Layer / File(s) Summary
Filter generated global phases
src/pyqasm/modules/qasm2.py
Conditional validation distinguishes user-written phases. Recursive filtering removes QuantumPhase statements before the AST is stored.
Validate QASM 2 unrolling
tests/qasm2/test_operations.py, tests/qasm2/test_conditional_body.py, CHANGELOG.md
Tests cover rzz and rxx decompositions, conditional unrolling, refiltering, round-trip unrolling, and continued rejection of authored gphase statements. The changelog records the fix.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: thegupta2012

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: removing unroll-emitted global phases for QASM 2 targets.
Linked Issues check ✅ Passed The changes address issue #351 by removing emitted QuantumPhase nodes, preserving user-written gphase rejection, and adding rzz/rxx regression tests.
Out of Scope Changes check ✅ Passed The changelog, implementation, and tests are directly related to preventing invalid gphase output in QASM 2.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-qasm2-gphase

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!

@ryanhill1

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ryanhill1

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ryanhill1

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pyqasm/modules/qasm2.py (1)

151-176: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add required type annotations and complete docstrings.

_drop_global_phase has no parameter or return annotation. Its docstring has no Args or Returns section. accept also has an untyped visitor parameter and no documented return value. Add compatible annotations and document the None return values.

As per coding guidelines, **/*.py requires type annotations for all functions and methods, and docstrings that explain purpose, parameters, and return values.

🤖 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/modules/qasm2.py` around lines 151 - 176, Add compatible type
annotations to _drop_global_phase, including its statements parameter and list
return type, and document the parameter and returned filtered statements in its
docstring. Annotate accept’s visitor parameter with the appropriate visitor type
and its return type as None, then add a Returns section documenting that it
returns None.

Source: Coding guidelines

🧹 Nitpick comments (1)
tests/qasm2/test_conditional_body.py (1)

67-73: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Keep coverage for authored conditional gphase.

This test replaces the conditional global-phase rejection test. test_user_written_gphase_rejected covers only a top-level statement. Add a companion test that verifies if(m==1) gphase(0.3); raises ValidationError, because Qasm2Module._filter_branch_body() owns this separate validation path.

🤖 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 `@tests/qasm2/test_conditional_body.py` around lines 67 - 73, Add a companion
test near test_conditional_rzz_survives_refiltering that loads an authored
conditional gphase statement, such as if(m==1) gphase(0.3);, and asserts
module.unroll() raises ValidationError. Keep the existing refiltering test
unchanged and target the conditional validation path implemented by
Qasm2Module._filter_branch_body().
🤖 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 `@CHANGELOG.md`:
- Line 29: Update the changelog entry describing the `rzz`/`rxx` fix to
explicitly state that OpenQASM 2 unrolling emitted invalid `gphase(...)` output,
while preserving the existing details about dropping unroll-emitted phases and
rejecting user-written `gphase`.

In `@tests/qasm2/test_conditional_body.py`:
- Around line 67-69: Add the return type annotation -> None to
test_conditional_rzz_survives_refiltering, and extend its docstring with a
Returns section documenting that the test returns None.

In `@tests/qasm2/test_operations.py`:
- Around line 70-72: Update all affected test functions in this file, including
test_rzz_unrolls_without_gphase and the additional referenced tests, to declare
-> None and extend each docstring with a Returns section documenting that the
function returns None.

---

Outside diff comments:
In `@src/pyqasm/modules/qasm2.py`:
- Around line 151-176: Add compatible type annotations to _drop_global_phase,
including its statements parameter and list return type, and document the
parameter and returned filtered statements in its docstring. Annotate accept’s
visitor parameter with the appropriate visitor type and its return type as None,
then add a Returns section documenting that it returns None.

---

Nitpick comments:
In `@tests/qasm2/test_conditional_body.py`:
- Around line 67-73: Add a companion test near
test_conditional_rzz_survives_refiltering that loads an authored conditional
gphase statement, such as if(m==1) gphase(0.3);, and asserts module.unroll()
raises ValidationError. Keep the existing refiltering test unchanged and target
the conditional validation path implemented by
Qasm2Module._filter_branch_body().
🪄 Autofix

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 Plus

Run ID: 9b6c4e00-49f0-49df-ad43-a03e36765e9d

📥 Commits

Reviewing files that changed from the base of the PR and between 7e05f4c and 34693e1.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/pyqasm/modules/qasm2.py
  • tests/qasm2/test_conditional_body.py
  • tests/qasm2/test_operations.py

Comment thread CHANGELOG.md Outdated
### Removed

### Fixed
- Fixed `rzz`/`rxx` in an OpenQASM 2 program unrolling to a `gphase(...)` statement, which QASM 2 has no syntax for, so the output was not a loadable QASM 2 program. Global phase is unobservable, so unroll-emitted phases are now dropped for a QASM 2 target; a user-written `gphase` is still rejected. ([#351](https://github.com/qBraid/pyqasm/issues/351))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify the failed unrolling behavior.

The phrase “rzz/rxx in an OpenQASM 2 program unrolling to” is unclear. State that OpenQASM 2 unrolling of rzz and rxx emitted invalid gphase(...) output.

🤖 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 `@CHANGELOG.md` at line 29, Update the changelog entry describing the
`rzz`/`rxx` fix to explicitly state that OpenQASM 2 unrolling emitted invalid
`gphase(...)` output, while preserving the existing details about dropping
unroll-emitted phases and rejecting user-written `gphase`.

Comment on lines +67 to +69
def test_conditional_rzz_survives_refiltering():
"""Test that transformations which re-filter an already unrolled body no longer
trip over the rzz global phase: it is dropped for a QASM 2 target (issue #351)"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add the test annotation and return documentation.

Add -> None. Add a Returns section that documents the None return value.

As per coding guidelines, **/*.py requires type annotations for all functions and docstrings that explain return values.

🤖 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 `@tests/qasm2/test_conditional_body.py` around lines 67 - 69, Add the return
type annotation -> None to test_conditional_rzz_survives_refiltering, and extend
its docstring with a Returns section documenting that the test returns None.

Source: Coding guidelines

Comment on lines +70 to +72
def test_rzz_unrolls_without_gphase():
"""Test that the global phase from the rzz decomposition is dropped for a QASM 2
target, which has no global-phase syntax (issue #351)"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add test function annotations and return documentation.

Add -> None to each test function. Add a Returns section that documents the None return value.

As per coding guidelines, **/*.py requires type annotations for all functions and docstrings that explain return values.

Also applies to: 98-100, 126-127, 146-148, 163-165

🤖 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 `@tests/qasm2/test_operations.py` around lines 70 - 72, Update all affected
test functions in this file, including test_rzz_unrolls_without_gphase and the
additional referenced tests, to declare -> None and extend each docstring with a
Returns section documenting that the function returns None.

Source: Coding guidelines

@ryanhill1

Copy link
Copy Markdown
Member Author

Applied the changelog wording fix. Skipping the -> None / Returns annotations on test functions: no test in this repo's suite annotates -> None or documents a Returns section (0 occurrences across tests/), so adding it to only these six would be inconsistent with the established convention.

@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.

Verdict

Approve with non-blocking comments (carried as prose here; this review is posted as a comment, not as a GitHub approval). The fix is correct and well-scoped. The central safety question — whether dropping QuantumPhase can ever discard an observable phase — was tested adversarially, and the answer is no, for a reason that holds structurally rather than by luck: _visit_phase_operation (src/pyqasm/visitor.py:1430-1445) rewrites a controlled phase into a p gate and clears .modifiers, so every QuantumPhase reaching _drop_global_phase is a genuine global phase. The two Medium findings below are worth addressing before merge; neither blocks.

Findings

# Type Severity Summary
1 Implementation Medium A conditional whose body is entirely global phase is emitted as a bodiless if, silently, where main raised
2 Maintenance Medium The conditional-body gphase diagnostic had its message edited and its only test deleted
3 Maintenance Low The drop is isinstance-only; correctness rests on an invariant held in visitor.py and undocumented at the drop site
4 Implementation Low A test comment claims the output is "a loadable QASM 2 program"; an external QASM 2 parser rejects it

How this was verified

Every claim below comes from executing code in a worktree at 1246c656, compared against a second worktree at origin/main (7c31308).

Global phase is genuinely global — the load-bearing check. Three independent lines of evidence:

  • ctrl @ rzz(0.3) and ctrl @ rxx(0.3), and their negctrl @ forms, are rejected upstream — ValidationError: Unsupported controlled QASM operation: rzz with 1 controls — on both main and this branch. Wrapping rzz in a user-defined gate and controlling that wrapper is rejected identically.
  • Controlling a gate that does carry a phase reaches _visit_phase_operation with a non-empty ctrls, which converts it into a p gate. For gate myg(t) a { gphase(t); x a; } applied under ctrl @, zero QuantumPhase nodes survive and the emitted body is h; rx(0.3); h; cx — the phase became a real rotation rather than a dropped node.
  • Numerically, that emitted circuit matches Operator(UnitaryGate(exp(i*theta)*X).control(1)) under .equiv(). The repository's own tests/qasm3/test_gates.py:768 test_ctrl_gphase_eq_p already pins the same ctrl @ gphase == p identity.

Re-filter matrix, for rzz and rxx, five sequences each: remove_idle_qubits()validate(); remove_idle_qubits() twice; remove_idle_qubits()reverse_qubit_order(); reverse_qubit_order()remove_idle_qubits(); unroll()remove_measurements()validate(). All 10 fail on main with Statement of type <class 'openqasm3.ast.QuantumPhase'> not supported in QASM 2.0. All 10 pass here, and none of the outputs contain gphase.

Emitted output under an external parser. qiskit.qasm2.loads(..., custom_instructions=LEGACY_CUSTOM_INSTRUCTIONS) accepts the unrolled rzz and rxx programs; it rejects main's output. Both decompositions do drop a nonzero phase — gphase(-0.15) for rzz(0.3) and gphase(-0.35) for rxx(0.7), i.e. -theta/2 in each case. Operator(...).equiv() confirms the emitted QASM 2 matches qiskit's RZZGate(0.3) / RXXGate(0.7) and matches pyqasm's own QASM 3 unrolling of the same source, up to global phase in every pairing — exactly the intended difference and nothing more.

Rejection of user-written gphase still holds in both positions: at top level via the whitelist in _filter_statements, and inside a conditional via the retained diagnostic. Both were confirmed under validate() and under unroll().

Containers. _drop_global_phase descends into if_block/else_block only, and that is sufficient rather than merely untested: _filter_statements rejects both Box and Pragma for a QASM 2 module (Statement of type <class 'openqasm3.ast.Box'> not supported in QASM 2.0), so the #pragma braket verbatim box path added in #341 cannot reach a Qasm2Module. BranchingStatement is the only whitelisted container, and the unroller expands loops.

No aliasing consequence follows from mutating stmt.if_block in place: unrolling the same module object twice is byte-identical, and matches a freshly loaded module.

Suite, lint, CI. Full pytest: 719 passed here against 714 on main, with the same two pre-existing tests/cli/test_cli_commands.py failures on both, which are environmental and unrelated. black --check is clean. All 26 CI checks on the PR report success.

Not verified

isort and pylint are not present in the available environment, so black was the only formatter run locally; CI covers the remainder.

Next steps

  • Merge-order conflict with #338. Open PR #338 ("fix: emit OpenQASM 2 syntax for classical conditionals") rewrites this same file with +364 lines, adding _filter_branch, Qasm2Printer and _flatten_branch. Whichever of the two merges second will conflict in src/pyqasm/modules/qasm2.py. Finding 1 interacts with it directly, since #338 introduces the printer that would have to render a branch emptied by the phase drop. Sequencing the two deliberately will cost less than resolving that conflict blind.
  • Pre-existing and out of scope, but adjacent enough to deserve an issue: inv @ rzz(0.3) q[0], q[1]; raises AttributeError: 'QuantumPhase' object has no attribute 'name' on both main and this branch.

Comment on lines +160 to +163
if isinstance(stmt, qasm3_ast.BranchingStatement):
stmt.if_block = self._drop_global_phase(stmt.if_block)
stmt.else_block = self._drop_global_phase(stmt.else_block)
filtered.append(stmt)

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.

Type: Implementation  |  Severity: Medium

Rationale: The drop can empty a conditional body, and nothing then removes the now-bodiless branch. A gate whose body is nothing but a phase reaches this:

gate ph(t) a { gphase(t); }
if(c==1) ph(0.3) q[1];

On this branch that unrolls to if (c[0] == true) { followed by a bare closing brace — an if with no qop, which QASM 2 has no form for. remove_idle_qubits() and reverse_qubit_order() both preserve it. main was wrong here too, but loudly: it emitted gphase(0.3) q[1] inside the branch and raised ValidationError on reload. This PR turns a rejection into silently malformed output, which is the worse failure mode.

Narrow reachability, granted — rzz/rxx never hit it, since their decompositions carry real gates alongside the phase. But #345 already established the precedent for exactly this shape: a box left empty by a removal is dropped, "since pyqasm rejects a box with no statements".

Change requested: Drop the branch when both blocks come back empty. Guarding on if_block alone would be wrong if an else_block ever survives.

Suggested change
if isinstance(stmt, qasm3_ast.BranchingStatement):
stmt.if_block = self._drop_global_phase(stmt.if_block)
stmt.else_block = self._drop_global_phase(stmt.else_block)
filtered.append(stmt)
if isinstance(stmt, qasm3_ast.BranchingStatement):
stmt.if_block = self._drop_global_phase(stmt.if_block)
stmt.else_block = self._drop_global_phase(stmt.else_block)
if not stmt.if_block and not stmt.else_block:
# the body was nothing but global phase, and QASM 2 has no
# form for a conditional without a qop
continue
filtered.append(stmt)

Comment on lines +158 to +159
if isinstance(stmt, qasm3_ast.QuantumPhase):
continue

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.

Type: Maintenance  |  Severity: Low

Rationale: The drop keys on the node type alone. It is correct today, and that was confirmed rather than assumed: _visit_phase_operation in visitor.py clears .modifiers and rewrites any controlled phase into a p gate, so nothing carrying a control modifier ever arrives here. That invariant lives in another module, though, and nothing at this site records the dependency. Should it ever weaken, a controlled phase — a relative phase, and fully observable — would vanish with no error and no visible signal. Silent physical incorrectness is expensive to find later.

Change requested: Assert the invariant instead of relying on it. raise_qasm3_error is already imported at line 26.

Suggested change
if isinstance(stmt, qasm3_ast.QuantumPhase):
continue
if isinstance(stmt, qasm3_ast.QuantumPhase):
# a controlled phase is relative, not global, and is observable;
# the visitor rewrites those to 'p' gates, so none should reach here
if stmt.modifiers:
raise_qasm3_error(
"Modified global phase cannot be dropped for a QASM 2 target",
error_node=stmt,
span=stmt.span,
)
continue

Comment on lines +67 to +73
def test_conditional_rzz_survives_refiltering():
"""Test that transformations which re-filter an already unrolled body no longer
trip over the rzz global phase: it is dropped for a QASM 2 target (issue #351)"""
module = loads(QASM2_PREAMBLE + "if(m==1) rzz(0.3) q[0], q[1];\n")
module.unroll()
module.reverse_qubit_order()
with pytest.raises(ValidationError, match="Global phase is not representable in QASM 2.0"):
module.remove_idle_qubits()
module.remove_idle_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.

Type: Maintenance  |  Severity: Medium

Rationale: This PR edits the wording of the conditional-body gphase diagnostic (qasm2.py:101-106) and, in the same change, removes the only test that exercised it. grep -rn "not representable in QASM 2.0" tests/ returns nothing on this branch; on main it matched the pytest.raises(...) this test replaced.

The new test_user_written_gphase_rejected in test_operations.py does not close the gap: a top-level gphase is caught by the whitelist in _filter_statements and produces a different message (Statement of type <class 'openqasm3.ast.QuantumPhase'> not supported in QASM 2.0). The conditional-body path is still live — if(m==1) gphase(0.3); was confirmed to raise the trimmed message under both validate() and unroll() — but it is now untested, immediately after its text was changed.

Change requested: Keep this test as written, and add one alongside it for the rejection path, so the branch this PR reworded stays pinned:

def test_conditional_user_gphase_rejected():
    """A gphase the user wrote in a conditional body is still rejected (issue #351)."""
    module = loads(QASM2_PREAMBLE + "if(m==1) gphase(0.3);\n")
    with pytest.raises(ValidationError, match="Global phase is not representable in QASM 2.0"):
        module.validate()

Comment on lines +142 to +143
# the unrolled output must be a loadable QASM 2 program
loads(unrolled).validate()

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.

Type: Implementation  |  Severity: Low

Rationale: The comment claims more than the assertion establishes. loads(...).validate() proves pyqasm re-reads its own output; it does not prove the output is QASM 2. It is not: qiskit.qasm2.loads rejects this exact string with QASM2ParseError: needed '==', but instead saw [, because the conditional prints as if (c[0] == true) { ... } — QASM 3 syntax. Emitting gphase was one reason this output would not load, but not the only one.

That remaining half is #338's territory rather than this PR's, so no code change is needed here. A future reader taking the comment at face value would conclude the conditional path is fully fixed, though, and it is not.

Change requested: Narrow the comment to what is actually asserted — for example # the unrolled output must still re-load in pyqasm — or keep the stronger claim and reference #338 as the remaining blocker.

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.

gphase emitted for QASM 2 modules: rzz/rxx unroll to a global phase QASM 2 cannot express

3 participants