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 twoqnum
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 tofalse
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.