Description
For a program that mixes declared registers with physical qubits, unroll(consolidate_qubits=True) emits OpenQASM describing two address spaces, while num_qubits collapses them into one. Nothing in the output states which is intended.
qubit[2] q; qubit[2] __PYQASM_QUBITS__;
h q[0]; ------> h __PYQASM_QUBITS__[0];
cx q[0], q[1]; cx __PYQASM_QUBITS__[0], __PYQASM_QUBITS__[1];
h $1; h $1;
module.num_qubits == 2. Internally $1 and __PYQASM_QUBITS__[1] are counted as the same qubit — _register_physical_qubit does num_qubits = max(num_qubits, phys_idx + 1) — but the emitted program never says so. A consumer sees a virtual register the hardware may map anywhere, plus an absolute reference to physical qubit 1. If they are the same qubit, the output should say so; if they are not, num_qubits under-counts.
A second shape makes it starker:
qubit[2] q; ------> qubit[6] __PYQASM_QUBITS__;
h q[0]; h __PYQASM_QUBITS__[0];
h $5; h $5;
num_qubits == 6 — a six-slot register of which one slot is used, sized by a physical index that register does not contain.
Related: unreferenced consolidated declaration
For a program with no declared registers at all, the consolidated declaration is still emitted and nothing references it:
h $1; ------> qubit[2] __PYQASM_QUBITS__;
h $1;
It parses, but a device backend may allocate against a register the program never uses. Suppressing it when nothing was consolidated — sum(global_qreg_size_map.values()) == 0 in _add_pyqasm_qubit_register — would fall out of whichever option below is chosen.
Note that with device_qubits set, the declaration is sized from that instead, giving a third number: loads(src, device_qubits=5) on h $1; cz $2, $1; emits qubit[5] __PYQASM_QUBITS__; while num_qubits == 3.
Provenance
Pre-existing #325 semantics, not introduced by #344. Running both shapes on main through the measure/reset paths (qubit[2] q; h q[0]; c = measure $1; and qubit[2] q; h q[0]; reset $5;) gives identical output and identical counts. #344 only stops the AttributeError that previously masked the question for gate operands.
Options
Author's call — this issue exists to settle it rather than to prescribe:
- Map physical operands into the consolidated register using the same offset scheme, so the whole program speaks one address space — arguably what "consolidate" implies.
- Raise a
ValidationError when a program mixes declared registers with physical qubits under consolidate_qubits=True, rather than emitting something ambiguous.
- Keep current behaviour, document the semantics explicitly, and fix
num_qubits so it does not conflate the two address spaces.
tests/qasm3/test_device_qubits.py::test_physical_qubits_only and ::test_physical_qubits_are_not_consolidated assert num_qubits explicitly, so whichever option is taken shows up as a deliberate test edit.
Found in review of #344 (M1, L1).
Description
For a program that mixes declared registers with physical qubits,
unroll(consolidate_qubits=True)emits OpenQASM describing two address spaces, whilenum_qubitscollapses them into one. Nothing in the output states which is intended.module.num_qubits == 2. Internally$1and__PYQASM_QUBITS__[1]are counted as the same qubit —_register_physical_qubitdoesnum_qubits = max(num_qubits, phys_idx + 1)— but the emitted program never says so. A consumer sees a virtual register the hardware may map anywhere, plus an absolute reference to physical qubit 1. If they are the same qubit, the output should say so; if they are not,num_qubitsunder-counts.A second shape makes it starker:
num_qubits == 6— a six-slot register of which one slot is used, sized by a physical index that register does not contain.Related: unreferenced consolidated declaration
For a program with no declared registers at all, the consolidated declaration is still emitted and nothing references it:
It parses, but a device backend may allocate against a register the program never uses. Suppressing it when nothing was consolidated —
sum(global_qreg_size_map.values()) == 0in_add_pyqasm_qubit_register— would fall out of whichever option below is chosen.Note that with
device_qubitsset, the declaration is sized from that instead, giving a third number:loads(src, device_qubits=5)onh $1; cz $2, $1;emitsqubit[5] __PYQASM_QUBITS__;whilenum_qubits == 3.Provenance
Pre-existing #325 semantics, not introduced by #344. Running both shapes on
mainthrough themeasure/resetpaths (qubit[2] q; h q[0]; c = measure $1;andqubit[2] q; h q[0]; reset $5;) gives identical output and identical counts. #344 only stops theAttributeErrorthat previously masked the question for gate operands.Options
Author's call — this issue exists to settle it rather than to prescribe:
ValidationErrorwhen a program mixes declared registers with physical qubits underconsolidate_qubits=True, rather than emitting something ambiguous.num_qubitsso it does not conflate the two address spaces.tests/qasm3/test_device_qubits.py::test_physical_qubits_onlyand::test_physical_qubits_are_not_consolidatedassertnum_qubitsexplicitly, so whichever option is taken shows up as a deliberate test edit.Found in review of #344 (M1, L1).