Skip to content

Gate broadcasting over multiple registers unrolls to the wrong circuit #384

Description

@TheGupta2012

Limitation

Broadcasting defines register arguments as an
element-wise shorthand: every register operand must have the same length, and the gate is
applied once per index, pairing operands positionally. Single-qubit operands are repeated.

The spec's own example:

qubit[1] qr0;
qubit[3] qr1;
qubit[2] qr2;
qubit[3] qr3;

g4 qr0[0], qr1, qr2[0], qr3;   // ok

is stated to be equivalent to

g4 qr0[0], qr1[0], qr2[0], qr3[0];
g4 qr0[0], qr1[1], qr2[0], qr3[1];
g4 qr0[0], qr1[2], qr2[0], qr3[2];

pyqasm instead flattens every operand into one list and slices it into consecutive chunks
of the gate's arity. For a two-register call this produces a different circuit with no
error raised
, and for the mixed register/qubit form it raises a spurious validation error.

Example QASM failure

Wrong output, silently:

OPENQASM 3.0;
include "stdgates.inc";
qubit[4] q;
qubit[4] r;
cx q, r;
>>> import pyqasm
>>> m = pyqasm.loads(qasm); m.unroll(); print(pyqasm.dumps(m))
OPENQASM 3.0;
include "stdgates.inc";
qubit[4] q;
qubit[4] r;
cx q[0], q[1];
cx q[2], q[3];
cx r[0], r[1];
cx r[2], r[3];

Expected per spec:

cx q[0], r[0];
cx q[1], r[1];
cx q[2], r[2];
cx q[3], r[3];

Spurious rejection of the mixed form:

OPENQASM 3.0;
include "stdgates.inc";
qubit[4] q;
qubit[1] r;
cx q, r[0];
// ValidationError: Invalid number of qubits 5 for operation cx
OPENQASM 3.0;
gate g4 a, b, c, d { }
qubit[1] qr0; qubit[3] qr1; qubit[2] qr2; qubit[3] qr3;
g4 qr0[0], qr1, qr2[0], qr3;
// ValidationError: Qubit count mismatch for gate 'g4'. Expected 4 qubits, but got 8 instead.

Change Requested

Replace the flatten-and-chunk expansion with spec broadcasting:

  1. Resolve each operand to a list of qubits, keeping operand boundaries.
  2. Let n be the length of the register operands. All register operands must share the same
    n; otherwise raise a ValidationError naming the two mismatched operands.
  3. Emit n gate applications; at iteration i take element i from each register operand
    and repeat single-qubit operands unchanged.
  4. If there are no register operands, emit exactly one application.

Implementation Details

  • The defect is in Qasm3Visitor._unroll_multiple_target_qubits
    (src/pyqasm/visitor.py:1039). It calls self._get_op_bits(operation, qubits=True), which
    already discards operand boundaries, then chunks linearly:

    for i in range(0, len(op_qubits), gate_qubit_count):
        qubit_subsets.append(op_qubits[i : i + gate_qubit_count])
  • _get_op_bits needs a variant that returns list[list[...]] (one inner list per operand)
    so the boundary information survives. _broadcast_gate_operation (visitor.py:1067)
    consumes the result and needs no change.

  • This is a deliberate behaviour change, not a pure bug fix. The current docstring
    documents flatten-and-chunk as intended: 'cx q[0], q[1], q[2], q[3]' →
    [[q[0], q[1]], [q[2], q[3]]]. That calling form — four single-qubit operands for a
    two-qubit gate — is not valid OpenQASM 3 broadcasting in the first place. Maintainers
    should decide whether to keep it as a documented pyqasm extension or reject it; either way
    the register case above must change.

  • The same expansion path serves the modifier route at visitor.py:1241 and visitor.py:1463,
    so ctrl @ / inv @ broadcasts are affected identically and need coverage.

  • Tests: add tests/qasm3/test_gate.py cases for two-register cx, mixed register/qubit,
    the spec g4 example, the size-mismatch error, and a broadcast under ctrl @.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingllm-assistedUsed LLMs to fine tune issue description.qasm3Related to openqasm3transformationRelated to qasm program transformation

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions