Classical Control Flow
Loops and conditionals on classical expressions are useful means to describe reusable building blocks. Qmod has two basic forms - the repeat statement and the if statement.
Classical Repeat
Syntax
def repeat(count: CInt, iteration: QCallable[CInt]) -> None:
pass
repeat ( iteration_variable : count ) { iteration-statements }
Semantics
- Invoke the iteration block count times, binding the index variable to the respective iteration number - 0, 1,... count-1.
- Inside the statement block, use of quantum variables declared outside it is restricted to contexts where the variable is initialized and remains initialized (see Quantum Variables)
Example
The following example defines a useful function - applying the Hadamard function across all qubits in a qubit array - using repeat. Note that a similar function is available in the Classiq open-library.
from classiq import H, QArray, QBit, qfunc, repeat
@qfunc
def my_hadamard_transform(qba: QArray[QBit]):
repeat(
count=qba.len,
iteration=lambda index: H(qba[index]),
)
qfunc my_hadamard_transform(qba: qbit[]) {
repeat (index: qba.len) {
H(qba[index]);
}
}
Classical If
Syntax
def if_(condition: CBool, then: QCallable, else_: Optional[QCallable] = None) -> None:
pass
Note that identifiers in Qmod that happen to conflict with Python keywords have _
suffix. This is the case with if_ and else_ in the second function.
if ( condition ) { then-statements } else { else-statements }
Semantics
- Invoke the then block if condition evaluates to
trueand otherwise invoke the else block - Inside the statement block, use of quantum variables declared outside it is restricted to contexts where the variable is initialized and remains initialized (see Quantum Variables)
Example
from classiq import CBool, X, Y, QBit, qfunc, if_
@qfunc
def my_conditional_gate(cond: CBool, qb: QBit):
if_(
condition=cond,
then=lambda _: X(qb),
else_=lambda _: Y(qb),
)
qfunc my_conditional_gate(cond: bool, qb: qbit) {
if (cond) {
X(qb);
} else {
Y(qb);
}
}
Classical Foreach
The foreach statement iterates through the elements of a classical array. Its compilation and simulation are more efficient.
Warning
The foreach statement is an experimental feature. It is only supported in
Qmod Python.
Syntax
def foreach(values: CArray | list, iteration: Callable) -> None:
pass
The iteration callable accepts one or more iteration variables.
Semantics
- Invoke the iteration block once for every element of values.
- If the iteration block accepts a single iteration variable, the elements of values will be bound to it sequentially.
- If values is a nested array (
CArray[CArray]), the length of values' elements is \(n\), \(n>=2\), and the iteration block has \(n\) iteration variables, then values' elements will be unpacked into the iteration variables in each iteration. - Inside the statement block, use of quantum variables declared outside it is restricted to contexts where the variable is initialized and remains initialized (see Quantum Variables)
- values must be a value of type
CArray[CReal]orCArray[CArray[CReal]].
Examples
In the following example, the foreach statement iterates through the elements
of the classical list [0.1, 0.2] and assigns its elements into the iteration
variable i.
This is equivalent to calling RX and Y twice, once with angle 0.1 and
once with 0.2.
from classiq import *
@qfunc
def main(q: Output[QBit]) -> None:
allocate(q)
foreach(
[0.1, 0.2],
lambda i: [
RX(i, q),
Y(q),
],
)
In the following example, the foreach statement iterates through the elements
of the classical list [[0.1, 0.2], [0.3, 0.4]] and assigns its elements into the iteration
variables i and j.
In each iteration, the elements of the nested arrays are "unpacked" into the
iteration variables.
The first iteration assigns 0.1 into i and 0.2 into j, and the second
iteration assigns 0.3 into i and 0.4 into j.
from classiq import *
@qfunc
def main(q: Output[QBit]) -> None:
allocate(q)
foreach(
[[0.1, 0.2], [0.3, 0.4]],
lambda i, j: [
RX(i, q),
RY(j, q),
],
)