Skip to content

Commit e132c0d

Browse files
TheGupta2012claude
andauthored
Fix double-unroll ValidationError when consolidate_qubits=True (#297)
* Fix double unroll with consolidate_qubits=True corrupting original AST `consolidate_qubit_registers` mutates AST nodes in-place (renaming qubit identifiers to `__PYQASM_QUBITS__`). Since `self._statements` holds a reference to the original program statements, these mutations corrupt the source AST. A subsequent `unroll(consolidate_qubits=True)` then encounters barrier qubits named `__PYQASM_QUBITS__[...]` that do not exist in the fresh scope, raising a `ValidationError`. Deep-copy `unrolled_stmts` at the entry of `consolidate_qubit_registers` so the transformer operates on isolated copies, leaving the original AST intact for re-processing. Closes #296 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 79901de commit e132c0d

File tree

3 files changed

+33
-0
lines changed

3 files changed

+33
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Types of changes:
2626
### Removed
2727

2828
### Fixed
29+
- Fixed `consolidate_qubit_registers` mutating AST nodes in-place, causing a `ValidationError` when `unroll(consolidate_qubits=True)` is called more than once on the same `QasmModule`. ([#297](https://github.com/qBraid/pyqasm/pull/297))
2930
- Fixed barrier unrolling to preserve multi-qubit barrier statements instead of splitting into individual per-qubit barriers. ([#295](https://github.com/qBraid/pyqasm/pull/295))
3031
- Added support for physical qubit identifiers (`$0`, `$1`, …) in plain QASM 3 programs, including gates, barriers, measurements, and duplicate-qubit detection. ([#291](https://github.com/qBraid/pyqasm/pull/291))
3132
- Updated CI to use `macos-15-intel` image due to deprecation of `macos-13` image. ([#283](https://github.com/qBraid/pyqasm/pull/283))

src/pyqasm/transformer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,10 @@ def consolidate_qubit_registers( # pylint: disable=too-many-branches, too-many-
469469
if device_qubits is None:
470470
device_qubits = sum(global_qreg_size_map.values())
471471

472+
# Deep-copy so that in-place mutations below never corrupt the
473+
# original AST nodes (which may be re-visited on a subsequent unroll).
474+
unrolled_stmts = deepcopy(unrolled_stmts)
475+
472476
def _get_pyqasm_device_qubit_index(
473477
reg: str, idx: int, qubit_reg_offsets: dict[str, int], global_qreg: dict[str, int]
474478
):

tests/qasm3/test_device_qubits.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,34 @@ def test_gates():
223223
check_unrolled_qasm(dumps(result), expected_qasm)
224224

225225

226+
def test_double_unroll_with_consolidate_qubits():
227+
"""Test that calling unroll(consolidate_qubits=True) twice on the same
228+
module does not raise due to in-place AST mutation from the first call."""
229+
qasm = """OPENQASM 3.0;
230+
include "stdgates.inc";
231+
qubit[2] q;
232+
qreg q2[3];
233+
barrier q2;
234+
barrier q, q2;
235+
"""
236+
expected_qasm = """OPENQASM 3.0;
237+
qubit[5] __PYQASM_QUBITS__;
238+
include "stdgates.inc";
239+
barrier __PYQASM_QUBITS__[2], __PYQASM_QUBITS__[3], __PYQASM_QUBITS__[4];
240+
barrier __PYQASM_QUBITS__[0], __PYQASM_QUBITS__[1], __PYQASM_QUBITS__[2], __PYQASM_QUBITS__[3], __PYQASM_QUBITS__[4];
241+
"""
242+
mod = loads(qasm)
243+
mod.unroll(consolidate_qubits=True)
244+
first_result = dumps(mod)
245+
246+
# Second unroll should produce identical output without raising
247+
mod.unroll(consolidate_qubits=True)
248+
second_result = dumps(mod)
249+
250+
check_unrolled_qasm(first_result, expected_qasm)
251+
check_unrolled_qasm(second_result, expected_qasm)
252+
253+
226254
def test_validate(caplog):
227255
with pytest.raises(ValidationError, match=r"Total qubits '4' exceed device qubits '3'."):
228256
with caplog.at_level("ERROR"):

0 commit comments

Comments
 (0)