fix: support physical qubits in QASM3 to QIR conversion#290
Conversation
qasm3_to_qir raised a bare AssertionError (empty message) for programs addressing physical qubits, e.g. "h $0;". These are valid OpenQASM 3 and are what Qiskit emits for backend-transpiled circuits, but they unroll to plain Identifier nodes rather than IndexedIdentifier, which _get_op_bits assumed. Physical qubits now lower to the QIR qubit of the same index ($3 is qubit 3). The entry point declares enough qubits to cover the highest index used: pyqasm reports num_qubits == 0 for these programs, and a program touching only $7 still needs 8 qubits for $7 to be a valid QIR qubit id. Full-barrier detection has the same problem -- it summed declared register sizes, which is 0 with no registers, so every barrier looked like an unsupported subset barrier. Unsupported operands and target-less measurements now raise Qasm3ConversionError with a message rather than an empty AssertionError. Requires pyqasm >= 1.0.4, the first release that preserves physical qubits in reset statements (qBraid/pyqasm#325).
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
| include "stdgates.inc"; | ||
| bit[3] meas; | ||
| rz(pi / 2) $0; | ||
| sx $0; |
There was a problem hiding this comment.
should add the check for the single qubit op as well in the final qasm
| include "stdgates.inc"; | ||
| bit[1] meas; | ||
| h $2; | ||
| reset $2; |
There was a problem hiding this comment.
add check for reset as well
| touching only "$7" still needs 8 qubits for "$7" to be a valid QIR qubit id. | ||
| """ | ||
| finder = _HighestPhysicalQubit() | ||
| for statement in module.qasm_program.unrolled_ast.statements: |
There was a problem hiding this comment.
This will mean that we are going over the complete AST just for qubit count calculation which seems wasteful. Is it possible to derive this count from the qasm module object? Or keep a property max_physical_qubit_addr in the qasm module object? Should eliminate the need for a recalculation here
| _PHYSICAL_QUBIT_RE = re.compile(r"^\$(\d+)$") | ||
|
|
||
|
|
||
| def _physical_qubit_index(node: Any) -> Optional[int]: |
There was a problem hiding this comment.
As mentioned in the comment below, better to move the parsing semantics to pyqasm
TheGupta2012
left a comment
There was a problem hiding this comment.
Thanks for catching this @ryanhill1 ! The logic seems correct but I think we would be better off keeping the attribute denoting the largest physical qubit in pyqasm.
It might be possible that physical qubit addressing related logic is needed in qBraid SDK or other dependencies so it is better to make sure that pyqasm remains the single place for semantic processing
…iving it pyqasm already registers physical qubits during unrolling and folds them into num_qubits (indices are absolute hardware addresses, so a program touching only "$7" reports 8 qubits). Walking the unrolled AST to recompute the highest index duplicated semantic processing that belongs in pyqasm, so drop _HighestPhysicalQubit / _required_qubits and read qasm3_module.num_qubits. Also extend the physical-qubit tests: assert the single-qubit ops in the Qiskit-style transpiled program (rz, and the h-s-h that "sx" decomposes to, since QIR has no native sx) and assert the reset call in the reset test.
Important
Draft: blocked on a pyqasm release. The
pyqasmpin is bumped to>=1.0.4, which is the first release that will contain qBraid/pyqasm#325 (physical qubits preserved inresetstatements). Mark ready once that release is out — and correct the pin here if the release lands under a different number.Summary of changes
qasm3_to_qirraised a bareAssertionErrorwith an empty message for any program addressing physical qubits, e.g.h $0;:Physical qubits are valid OpenQASM 3, and are exactly what Qiskit emits when a circuit is transpiled against a backend (
qasm3.dumps(transpile(circuit, backend))). pyqasm parses, validates, and unrolls them fine — but they survive unrolling as plainIdentifiernodes rather thanIndexedIdentifier, which_get_op_bitsassumed of every operand.The fix
$3is qubit 3 — for gates, barriers, measurements, and reset.num_qubits == 0after unrolling these programs, so the visitor now derives the count itself. Physical indices are absolute hardware addresses, so a program touching only$7still needs 8 qubits for$7to be a valid QIR qubit id._barrier_applicablesummed declared register sizes, which is0when there are no registers, so every barrier on a physical-qubit program looked like an unsupported "subset barrier" and raisedNotImplementedError.measure q;) now raiseQasm3ConversionErrorwith an actual message.Impact
This was found via 52 production job failures on qBraid's QIR simulator, where the empty exception message surfaced to users as an opaque "Internal server error". All 52 of those programs convert correctly with this change.
Tests
Six new tests in
tests/qasm3_qir/converter/test_physical_qubits.py, all failing before the fix:$3/$7keep their indices and the entry point declares 8 qubitsmeasure q;with no target raisesQasm3ConversionErrorQasm3ConversionErrorFull suite green against both pyqir versions CI covers (0.11.x and 0.12+): 325 passed, 7 skipped. pylint 10/10, black clean.