Skip to main content

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

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]),
    )

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.

Semantics

  • Invoke the then block if condition evaluates to true and 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),
    )

Classical Foreach

The foreach statement iterates through the elements of a classical array. The use of foreach iteration variables is more restrictive than that of the repeat and consequently its compilation and execution are more efficient.

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.
  • values must be a value of type CArray[CReal] or CArray[CArray[CReal]], and its values must be known at compile-time.
  • If the iteration block accepts a single iteration variable, the elements of values will be bound to it sequentially.
  • In the case that values is a nested array (CArray[CArray[CReal]]), the number of iteration variables must correspond to the length of the inner array. At each iteration, the inner array is unpacked into the iteration variables, with each variable assigned the corresponding scalar value in the specified order.
  • The iteration variables are link-time: They can be used, e.g., as angles in rotation gates, but not in quantum expressions or as array indices.
  • 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).

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], [0.5, 0.6]] 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 to i and 0.2 into j, the second iteration assigns 0.3 into i and 0.4 to j, and the third iteration assigns 0.5 into i and 0.6 to j.
from classiq import *


@qfunc
def main(q: Output[QBit]) -> None:
    allocate(q)
    foreach(
        [[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]],
        lambda i, j: [
            RX(i, q),
            RY(j, q),
        ],
    )