Skip to content

QubitArray ​

QubitArray[N] is a fixed-width, indexable qubit register. It's the right tool when you have a register of qubits that you want to address by index — addresses for QROM, oracle inputs, multi-control patterns — without exploding the register into N individual Qubit parameters.

When to Use Each Quantum Type ​

TypeUse For
QubitSingle qubits passed individually
List[Qubit]Variable-length collections; cannot be indexed
QInt[N]Quantum integer arithmetic (+=, -=, qft, phi_add)
QubitArray[N]Bit-level register access (oracle inputs, address decoding)

QubitArray and QInt are parallel types — convert between them with qint_from_qarray and qarray_from_qint when you need both arithmetic and bit-level access.

Construction ​

kettle
qa: QubitArray[4] = [0, 1, 0, 1]    -- type-directed, length must match

The list literal length must equal N. Each element must be 0 or 1 (or any Int expression — non-literals are validated at runtime).

Indexed Operations ​

kettle
qa: QubitArray[3] = [0, 0, 0]
qa[0] <- hadamard                    -- single-slot gate
qa[1] <- pauli_x
qa[0], qa[2] <- cnot                 -- two-slot gate

The whole register is consumed and rebuilt by every indexed rebind. The typechecker tracks qa as one linear value — there is no per-slot tracking.

Cross-register operations work the same way:

kettle
control: QubitArray[3] = [1, 0, 0]
target: QubitArray[3] = [0, 0, 0]
control[0], target[2] <- cnot        -- consumes both, rebuilds both

Extraction ​

To pull individual qubits out of a register, use destructure:

kettle
qa: QubitArray[3] = [1, 0, 1]
[q0, q1, q2]: List[Qubit] = destructure(qa)
m0 = measure(q0)
m1 = measure(q1)
m2 = measure(q2)

q = qa[0] is a type error. The only legal positions for qa[i] are the LHS of a rebind.

This restriction keeps linearity tracking simple — the whole register is one linear value, never decomposed implicitly.

Measurement ​

measure(qa) returns the integer value of the register, reconstructed LSB-first from per-qubit measurements:

kettle
qa: QubitArray[3] = [1, 0, 1]
result: Int = measure(qa)        -- 1 + 0*2 + 1*4 = 5

This is equivalent to converting through QInt (measure(qint_from_qarray(qa))) but skips the wrapping step. Endianness matches QInt's convention: bit i of the register contributes 2^i to the result.

Iteration ​

There is no special for q in qa syntax. Use the standard for-loop accumulator pattern with index-based access:

kettle
final = for i in range(0, 8) with state = qa
  state[i] <- hadamard
end for

Each iteration consumes and rebuilds state; the loop accumulator threads it through.

Conversion to/from QInt ​

QubitArray and QInt are parallel types. Conversion is a zero-cost view change:

kettle
qa: QubitArray[8] = [1, 0, 1, 0, 0, 0, 0, 0]
qi: QInt[8] = qint_from_qarray(qa)
qi += 5                              -- arithmetic via QInt
qa: QubitArray[8] = qarray_from_qint(qi)
qa[7] <- pauli_x                     -- bit-level access via QubitArray

Use this pattern when an algorithm needs both — e.g., a Grover oracle that performs arithmetic on the input register.

Multi-Control Gates ​

mcnot and mcz generalize the Toffoli and doubly-controlled-Z gates to arbitrary control width.

kettle
fn toffoli_n(controls: QubitArray[4], target: Qubit) -> Tuple[QubitArray[4], Qubit] with Quantum
  mcnot(controls, target)
end fn

mcnot flips target if and only if every qubit in controls is |1⟩:

kettle
controls: QubitArray[3] = [1, 1, 1]
target: Qubit = 0
(controls, target) = mcnot(controls, target)
measure(target)    -- 1 (flipped)

mcz applies a Z phase to the last qubit, controlled on all preceding qubits:

kettle
controls: QubitArray[3] = [1, 1, 1]   -- last qubit is the phase target
controls = mcz(controls)

Both functions follow the standard linearity convention: controls and target are consumed and returned as fresh IDs. For N ≥ 3, the implementation uses an internal V-chain ancilla decomposition — no manual ancilla management needed.

Common Pitfalls ​

  • q = qa[i] is a type error. Use destructure(qa) to extract.
  • Subarray slicing (qa[0..3]) is not yet supported. Workaround: destructure and reassemble with qarray_from_qubits.
  • Width mismatch in conversions — qint_from_qarray of a QubitArray[3] returns QInt[3], not QInt[N] for some other N.
  • Compile-time bounds checks: qa[5] on a QubitArray[3] is a typecheck error when the index is a literal.
  • Duplicate indices: qa[1], qa[1] <- cnot is rejected at compile time; runtime-equal dynamic indices (qa[i], qa[j] <- cnot where i == j at runtime) are rejected at runtime.

When NOT to Use QubitArray ​

  • Variable-width or runtime-determined width: use List[Qubit] and explicit destructure.
  • Pure arithmetic: use QInt[N] directly.
  • Single qubits: use Qubit.

See Also ​