Skip to content

consolidate_qubits=True emits two address spaces for mixed declared/physical programs #353

Description

@ryanhill1

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:

  1. Map physical operands into the consolidated register using the same offset scheme, so the whole program speaks one address space — arguably what "consolidate" implies.
  2. Raise a ValidationError when a program mixes declared registers with physical qubits under consolidate_qubits=True, rather than emitting something ambiguous.
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions