Control
The control statement applies a unitary operation conditionally, depending on a quantum state. The unitary operation is specified as a nested statement block. The objects used in the statement block become entangled with the object used in the condition, so that any superposition in the state of the condition carries over to the operations in the statement block. The control statement could be viewed as the quantum equivalent of the classical if statement.
The condition can be specified in one of two forms: as a single quantum variable, and as a quantum logical expression.
Syntax
control ( ctrl-var ) { statements }
control ( ctrl-expression ) { statements }
def control(
ctrl: Union[SymbolicExpr, QBit, QArray[QBit]],
stmt_block: Union[QCallable, Callable[[], None]],
) -> None:
pass
Semantics
- ctrl-var (in the first variant) is a quantum variable of type
qbit
orqbit[]
, and ctrl-expression (in the second variant) is a logical expression over a quantum variable. - The statement block is applied if all the qubits in ctrl are in state \(|1\rangle\) (in the
first variant), or if the ctrl-expression evaluates to
true
(in the second variant). - Currently, there exists a single limitation on the expression: if ctrl-expression is an equality between a single
variable and a classical expression, it is restricted to integers, meaning:
In a ctrl-expression of the form
<var> == <classical-expression>
,<var>
should be aqnum
with zero fraction places or aqbit
, and<classical-expression>
should evaluate to an integer. - The same variable cannot occur both in the condition and inside the statement block. This restriction applies also to mutually exclusive slices of the same variable.
- Quantum variables declared outside the control statement and used in its condition or inside its nested block must be initialized prior to it and remain initialized subsequently.
Examples
Example 1: Single-qubit control
In the following example, control
statement applies function X
on the variable target
conditioned on a single qubit variable qb
. Note that this is equivalent to using the
built-in gate-level function CX
.
qfunc main(output target: qbit) {
qb: qbit;
allocate(1, qb);
H(qb);
allocate(1, target);
control (qb) {
X(target);
}
}
from classiq import H, Output, QBit, X, allocate, control, qfunc
@qfunc
def main(target: Output[QBit]) -> None:
qb = QBit("qb")
allocate(1, qb)
H(qb)
allocate(1, target)
control(qb, lambda: X(target))
Synthesizing this model creates the quantum program shown below.
Example 2: Multi-qubit control
The next example shows how control
can be similarly used with multi-qubit control
variable. In this case target
is rotated when the state of qba
is the bit string 111.
qfunc main(output target: qbit) {
qba: qbit[];
allocate(3, qba);
hadamard_transform(qba);
allocate(1, target);
control (qba) {
RX(pi / 2, target);
}
}
from sympy import pi
from classiq import (
Output,
QBit,
QArray,
allocate,
control,
qfunc,
hadamard_transform,
RX,
)
@qfunc
def main(target: Output[QBit]) -> None:
qba = QArray("qba")
allocate(3, qba)
hadamard_transform(qba)
allocate(1, target)
control(qba, lambda: RX(pi / 2, target))
Synthesizing this model creates the quantum program shown below.
Example 3: Numeric equality condition
The following example demonstrates the use of control
to rotate the state of a qubit
by an angle determined by another quantum variable. In this case the condition compares
the quantum variable with the repeat index.
qfunc switch_rx(x: qnum, target: qbit) {
repeat (i: 4) {
control (x == i) {
RX(pi / (2 ** i), target);
}
}
}
qfunc main(output res: qbit) {
allocate(1, res);
x: qnum;
prepare_int(2, x);
switch_rx(x, res);
}
from classiq import (
qfunc,
QNum,
allocate,
control,
QBit,
repeat,
RX,
Output,
prepare_int,
)
from classiq.qmod.symbolic import pi
@qfunc
def switch_rx(x: QNum, target: QBit) -> None:
repeat(4, lambda i: control(x == i, lambda: RX(pi / 2**i, target)))
@qfunc
def main(res: Output[QBit]):
allocate(1, res)
x = QNum("x")
prepare_int(2, x)
switch_rx(x, res)
Synthesizing this model creates the following quantum program. Note how control
is implemented as positive and negative controls in the respective numeric qubits.
Example 4: Arithmetic condition
The following example demonstrates the use of control
to rotate the state of a qubit
according to a condition imposed by another two quantum variables, x
and y
. Here, the condition
filters quantum states such that y <= x[0] + x[1] + x[2]
.
qfunc switch_rx(x: qbit[], y: qnum, target: qbit) {
control (y <= x[0] + x[1] + x[2]) {
RX(pi / (2 ** y.size), target);
}
}
qfunc main(output res: qbit) {
x: qbit[3];
y: qnum<3, UNSIGNED, 0>;
allocate(3, x);
allocate(3, y);
hadamard_transform(x);
hadamard_transform(y);
switch_rx(x, y, res);
}
from classiq import (
RX,
Output,
QArray,
QBit,
QNum,
UNSIGNED,
allocate,
control,
hadamard_transform,
qfunc,
)
from classiq.qmod.symbolic import pi
@qfunc
def switch_rx(x: QArray[QBit], y: QNum, target: QBit) -> None:
control(x[0] + x[1] + x[2] >= y, lambda: RX(pi / (2**y.size), target))
@qfunc
def main(res: Output[QBit]) -> None:
x: QArray = QArray("x", QBit, 3)
y: QNum = QNum("y", 3, UNSIGNED, 0)
allocate(3, x)
allocate(3, y)
hadamard_transform(x)
hadamard_transform(y)
switch_rx(x, y, res)
Synthesizing this model creates the following quantum program. Note how control
is implemented as a result of an arithmetic computation. After applying the control
operation,
the arithmetic operation is un-computed.