Classical Types
Classical types in Qmod are not very different from classical types in conventional
programming languages. There are scalar types like int and bool, and aggregate
types, such as arrays and structs.
Classical types are used to declare classical function arguments, and global constants. Variables and literal values of classical types can be used in expressions, and support the conventional set of operators commonly available in conventional programming languages.
Scalar Types
In Qmod, scalar types represent numeric values, Boolean values, and Pauli base elements.
Syntax
Python Classes are used to represent scalar types
CIntrepresents integersCRealrepresents real numbers (using floating point encoding)CBoolrepresents the Boolean valuesFalseandTruePaulirepresents the Pauli base elements using the symbolsPauli.I,Pauli.X,Pauli.Y, andPauli.Z(with the integer values 0, 1, 2, 3 respectively)
intrepresents integersrealrepresents real numbers (using floating point encoding)boolrepresents the Boolean valuesfalseandtruePaulirepresents the Pauli base elements using the symbolsPauli::I,Pauli::X,Pauli::Y, andPauli::Z(with the integer values 0, 1, 2, 3 respectively)
Arrays
Arrays are homogenous collections of scalars or structs with random access.
Syntax
Array types are represented with the generic class CArray. Arguments are declared with the type hint in the form:
name : CArray [ [ element-type [ , length_expression ] ] ]
Array types have the form - element-type [ [ length_expression ] ]
Semantics
element-type is any scalar, array, or struct type. length_expression is optional, determining the length of the array. When left out, the length is determined upon variable initialization.
Expressions of array type support the following operations:
- Subscript: array-expression [ index-expression ]
- Slice: array-expression [ from-index-expression : to-index-expression ]
- Length: array-expression . len
Literal array values are expressed in the form - [ values ], where values is a list of zero or more comma-separated expressions of the same type.
Example
The following example demonstrates the use of classical arrays in Qmod. Function foo
takes an array of reals, and uses the .len attribute and array subscripting to access
the elements of the array. Note that index -1 signifies the last element in an array
(similar to Python).
from classiq import qfunc, CArray, CReal, QBit, RX, allocate, if_
@qfunc
def foo(arr: CArray[CReal], qb: QBit) -> None:
if_(arr.len > 2, lambda: RX(arr[-1], qb), lambda: RX(arr[0], qb))
@qfunc
def main() -> None:
q0 = QBit()
allocate(q0)
foo([0.5, 1.0, 1.5], q0)
qfunc foo(arr: real[], qb: qbit) {
if (arr.len > 2) {
RX(arr[-1], qb);
} else {
RX(arr[0], qb);
}
}
qfunc main() {
q0: qbit;
allocate(q0);
foo([0.5, 1.0, 1.5], q0);
}
Structs
Structs are aggregates of variables, called fields, each with its own name and type.
A Qmod classical struct is defined with a Python data class: A class decorated with
@dataclasses.dataclass.
Fields need to be declared with type-hints like classical arguments of functions. Fields are initialized and accessed like attributes of Python object.
Structs are declared in the form - struct { field-declarations }, where field-declarations is a list of one or more field declarations in the form - name : classical-type ;.
Expressions of struct type support the field-access operation in the form - struct-expression . field-name.
Literal struct values are expressed in the form - struct-name { field-value-list }. where field-value-list is a list of zero or more comma-separated field initializations in the form - name = expression.
Example
In the following example a struct type called MyStruct is defined. Function foo
takes an argument of this type and accesses its fields. Function main instantiates
and populates MyStruct in its call to foo.
from classiq import *
from dataclasses import dataclass
@dataclass
class MyStruct:
loop_counts: CArray[CInt]
angle: CReal
@qfunc
def foo(ms: MyStruct, qv: QArray[QBit, 2]) -> None:
H(qv[0])
repeat(
count=ms.loop_counts[1],
iteration=lambda index: PHASE(ms.angle + 0.5, qv[1]),
)
@qfunc
def main() -> None:
qba = QArray()
allocate(2, qba)
foo(MyStruct(loop_counts=[1, 2], angle=0.1), qba)
struct MyStruct {
loop_counts: int[];
angle: real;
}
qfunc foo(ms: MyStruct, qv: qbit[2]) {
H(qv[0]);
repeat (index: ms.loop_counts[1]) {
PHASE(ms.angle + 0.5, qv[1]);
}
}
qfunc main() {
qba: qbit[];
allocate(2, qba);
foo(MyStruct {
loop_counts = [1, 2],
angle = 0.1
}, qba);
}
Hamiltonians
Qmod's Python embedding offers a specialized syntax for creating
sparse Hamiltonian
objects.
Calling a
Pauli
enum value (e.g., Pauli.X) with an index (e.g., Pauli.X(3)) creates a
single-qubit Pauli operator.
The multiplication of single-qubit Pauli operators (e.g.,
Pauli.X(1) * Pauli.Y(2)) constructs the tensor product of these operators on
the respective qubits.
These can be linearly combined in a sum, each optionally
with a scalar coefficient (e.g., 0.5 * Pauli.X(2) + Pauli.Y(0)*Pauli.Z(2)).
Example
The Hamiltonian specified by the Pauli strings XYZ and IXI with coefficients
0.5 and 0.8 respectively is specified in the standard struct literal syntax
as follows:
H = SparsePauliOp(
terms=[
SparsePauliTerm(
paulis=[
IndexedPauli(pauli=Pauli.Z, index=0),
IndexedPauli(pauli=Pauli.Y, index=1),
IndexedPauli(pauli=Pauli.X, index=2),
],
coefficient=0.5,
),
SparsePauliTerm(
paulis=[
IndexedPauli(pauli=Pauli.X, index=1),
],
coefficient=0.8,
),
],
num_qubits=3,
)
You can specify the same Hamiltonian using the specialized Hamiltonian syntax as follows:
H = 0.5 * Pauli.Z(0) * Pauli.Y(1) * Pauli.X(2) + 0.8 * Pauli.X(1)