Within-apply
The within-apply statement performs the common quantum pattern \(U^{\dagger} V U\). It operates on two nested statement blocks, the compute block and the action blocks, and evaluates the sequence - compute, action, and invert(compute). Under conditions described below, quantum objects that are allocated and prepared by the compute block are subsequently un-computed and released.
Syntax
within { compute-statements } apply { action-statements }
def within_apply(within: Callable, apply: Callable) -> None:
pass
Semantics
- Unlike the case with other statements, the nested blocks of within-apply may use outer context variables as output-only arguments.
- To the extent that the compute block does not introduce superposition into the quantum objects it operates on (i.e. is quantum-free), and the action block does not modify the state of these objects, they are un-computed and returned to their state prior to the within-apply statement.
- Variables used as output-only arguments in the compute block are reset to their uninitialized state after the within-apply statement, and the qubits utilized by them return to the pool of qubits available for subsequent auxiliary allocations.
- The application of the compute block and its inverse are not subjected to redundant control logic in the case where the within-apply statement as a whole is subject to control.
- The application of the compute block and its inverse are guaranteed to be strictly equivalent, including
in the case where
compute
involves non-deterministic implementation decisions by the synthesis engine.
Examples
Example 1
The following example demonstrates how auxiliary qubits get used, un-computed, and reused at different steps of a computation, when scoped inside a within-apply statement. Actual reuse is a decision the synthesis engine takes to satisfy width constraints.
qfunc main(output res: qbit) {
allocate(1, res);
ctrl: qbit[];
within {
ctrl = 3;
} apply {
CCX(ctrl, res);
}
within {
ctrl = 2;
} apply {
CCX(ctrl, res);
}
}
from classiq import *
@qfunc
def main(res: Output[QBit]) -> None:
allocate(1, res)
ctrl = QArray("ctrl")
within_apply(lambda: assign(3, ctrl), lambda: CCX(ctrl, res))
within_apply(lambda: assign(2, ctrl), lambda: CCX(ctrl, res))
Note how variable ctrl
is used as an output-only argument and initialized in the
compute block, prior to being used for the CCX
in the action block.
Outside the within-apply statement the variable is reset to its uninitialized state,
and used again in the same way.
Visualizing the resulting quantum program, you can see how the same two auxiliary qubits are reused across the two steps of the circuit, because of width optimization.
Example 2
The code snippet below demonstrates the preparation and release of an auxiliary qubit to achieve phase kickback.
qfunc prep_minus(output out: qbit) {
allocate(1, out);
X(out);
H(out);
}
qfunc my_phase_oracle(predicate: qfunc (vars: qbit[], res: qbit), vars: qbit[]) {
aux: qbit;
within {
prep_minus(aux);
} apply {
predicate(vars, aux);
}
}
from classiq import allocate, Output, QBit, qfunc, QCallable, QArray, within_apply, X, H
@qfunc
def prep_minus(out: Output[QBit]) -> None:
allocate(1, out)
X(out)
H(out)
@qfunc
def my_phase_oracle(
predicate: QCallable[QArray[QBit], QBit], vars: QArray[QBit]
) -> None:
aux = QBit("aux")
within_apply(lambda: prep_minus(aux), lambda: predicate(vars, aux))
Example 3
The code snippet below demonstrates the use of within_apply
to define the Grover operator,
avoiding redundant control logic when called in higher-level contexts (for example, when
used as the unitary operand in a phase-estimation flow):
qfunc my_grover_operator(oracle: qfunc (qbit[]), space_transform: qfunc (qbit[]), target: qbit[]) {
oracle(target);
within {
invert {
space_transform(target);
}
} apply {
reflect_about_zero(target);
}
}
from classiq import *
@qfunc
def my_grover_operator(
oracle: QCallable[QArray[QBit]],
space_transform: QCallable[QArray[QBit]],
target: QArray[QBit],
) -> None:
oracle(target)
within_apply(
lambda: invert(lambda: space_transform(target)),
lambda: reflect_about_zero(target),
)