Skip to content

Quantum Types

In Qmod there are two kinds of scalar quantum types:

  • qbit represents the states \(|0\rangle\), \(|1\rangle\), or a superposition of the two
  • qnum represents numbers, integers or fixed-point reals, in some discrete domain

When declaring a qnum variable you can optionally specify its numeric attributes - overall size in bits, whether it is signed, and the number of binary fraction digits.

Syntax

qbit

qnum [ < size-int-expr , sign-bool-expr , frac-digits-int-expr > ]

In Python the classes QBit and QNum are used as type hints in the declaration of arguments:

name : QBit

name : QNum [ [ size-int-expr , sign-bool-expr , frac-digits-int-expr ] ]

The same classes are used to declare local variables:

name = QBit ( " local_name " )

name = QBit ( " name " , [ [ size = ] size-int-expr , [ [ is_signed = ] sign-bool-expr , [ [ fraction_digits = ] frac-digits-int-expr ] )

Semantics

  • Computational-basis encoding of numeric types is little-endian.
  • size-int-expr determines the overall number of qubits, each corresponding to the respective binary digit.
  • If sign-bool-expr is true two's complement is used to represent signed numbers, utilizing the most-significant bit for sign.
  • frac-digits-int-expr determines the number of least-significant bits representing binary fraction digits.

Examples

In the following example, two 4-qubit numeric variables x and y are prepared to store the bit string 1101. x is declared with no sign bit and no fraction digits and thus represents the number 13. y is declared to be signed and have one fraction digit, and thus with the same bit-level state represents the number -1.5.

qfunc prepare_1101(output qba: qbit[]) {
  allocate<4>(qba);
  X(qba[0]);
  X(qba[2]);
  X(qba[3]);
}

qfunc main(output x: qnum<4, false, 0>, output y: qnum<4, true, 1>) {
  prepare_1101(x);
  prepare_1101(y);
}
from classiq import qfunc, Output, QNum, QArray, allocate, QBit, X


@qfunc
def prepare_1101(qba: Output[QArray[QBit]]):
    allocate(4, qba)
    X(qba[0])
    X(qba[2])
    X(qba[3])


@qfunc
def main(x: Output[QNum[4, False, 0]], y: Output[QNum[4, True, 1]]):
    prepare_1101(x)
    prepare_1101(y)

Numeric Inference Rules

Numeric representation modifiers are optional in the declaration. When left out, the representation attributes of a qnum variable is determined upon its first initialization. Following are the inference rules for these cases:

  • When the variable is passed to a function as its output argument with declared type qnum, the size is determined by the actual size, sign, and fraction-digits of the function's output.
  • When the variable is passed to a function as its output argument with declared type qbit[], the size is determined by the actual array size, while sign and fraction-digits default to false and 0 respectively.
  • When the variable is initialized on the left of an out-of-place assignment =, the representation properties are determined by the domain of the expression.
  • Variables retain their type, including the representation attributes, even after being un-initialized (for example, when occurring on the left side of a bind statement). Subsequent initializations must agree with the specific qnum type.
  • On the right side of a bind statement (->) the representation attributes of qnum variables must already be known either through declaration, or by previous initialization (and subsequent un-initialization).

Examples

The following example demonstrates the default and explicit numeric interpretation of quantum states. Two variables, a and b are initialized to some quantum state. a is left with the default unsigned integer interpretation. b is initialized to a superposition of the bit strings 01 and 10 interpreted with a sign bit and one fraction digit. This implies that its domain is [-1.0, -0.5, 0, 0.5] and its value is in a superposition of -1.0 and 0.5. res is accordingly uniformly distributed on the 8 possible addition values.

qfunc main(output a: qnum, output b: qnum<2, true, 1>, output res: qnum) {
  allocate<2>(a);
  hadamard_transform(a);
  prepare_state<[0, 0.5, 0.5, 0], 0>(b);
  res = a + b;
}
from classiq import (
    Output,
    QNum,
    qfunc,
    prepare_state,
    allocate,
    hadamard_transform,
)


@qfunc
def main(a: Output[QNum], b: Output[QNum[2, True, 1]], res: Output[QNum]) -> None:
    allocate(2, a)  # 'a' is a 3 qubit unsigned int in the domain [0, 1, 2, 3]
    hadamard_transform(a)  # 'a' is in superposition of all values in its domain
    prepare_state([0, 0.5, 0.5, 0], 0, b)  # 'b' is in superposition of 01 and 10
    res |= a + b

Allocate-num

To allocate a qnum in the zero state, with a specific numeric interpretation, use the function allocate_num().

qfunc allocate_num<num_qubits: int, is_signed: bool, fraction_digits: int>(output out: qnum<num_qubits, is_signed, fraction_digits>);
def allocate_num(
    num_qubits: CInt,
    is_signed: CBool,
    fraction_digits: CInt,
    out: Output[QNum[num_qubits, is_signed, fraction_digits]],
) -> None:
    pass

Quantum arrays

Qmod supports quantum array types, currently only of qbit elements. Arrays are quantum objects in their own right, not mere collections of references to separately existing objects. Once initialized, its length and the specific qubits used to store it are fixed.