Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Types of changes:
### Removed

### Fixed
- 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))
- 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))
- 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))
- Updated CI to use `macos-15-intel` image due to deprecation of `macos-13` image. ([#283](https://github.com/qBraid/pyqasm/pull/283))
Expand Down
4 changes: 4 additions & 0 deletions src/pyqasm/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,10 @@ def consolidate_qubit_registers( # pylint: disable=too-many-branches, too-many-
if device_qubits is None:
device_qubits = sum(global_qreg_size_map.values())

# Deep-copy so that in-place mutations below never corrupt the
# original AST nodes (which may be re-visited on a subsequent unroll).
unrolled_stmts = deepcopy(unrolled_stmts)

def _get_pyqasm_device_qubit_index(
reg: str, idx: int, qubit_reg_offsets: dict[str, int], global_qreg: dict[str, int]
):
Expand Down
28 changes: 28 additions & 0 deletions tests/qasm3/test_device_qubits.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,34 @@ def test_gates():
check_unrolled_qasm(dumps(result), expected_qasm)


def test_double_unroll_with_consolidate_qubits():
"""Test that calling unroll(consolidate_qubits=True) twice on the same
module does not raise due to in-place AST mutation from the first call."""
qasm = """OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
qreg q2[3];
barrier q2;
barrier q, q2;
"""
expected_qasm = """OPENQASM 3.0;
qubit[5] __PYQASM_QUBITS__;
include "stdgates.inc";
barrier __PYQASM_QUBITS__[2], __PYQASM_QUBITS__[3], __PYQASM_QUBITS__[4];
barrier __PYQASM_QUBITS__[0], __PYQASM_QUBITS__[1], __PYQASM_QUBITS__[2], __PYQASM_QUBITS__[3], __PYQASM_QUBITS__[4];
"""
mod = loads(qasm)
mod.unroll(consolidate_qubits=True)
first_result = dumps(mod)

# Second unroll should produce identical output without raising
mod.unroll(consolidate_qubits=True)
second_result = dumps(mod)

check_unrolled_qasm(first_result, expected_qasm)
check_unrolled_qasm(second_result, expected_qasm)


def test_validate(caplog):
with pytest.raises(ValidationError, match=r"Total qubits '4' exceed device qubits '3'."):
with caplog.at_level("ERROR"):
Expand Down
Loading