Conjugations
Conjugations are common in quantum computations. In mathematical terms, they are patterns of the form U†VU for two unitary transformations U and V. That pattern is relevant due to the particularities of quantum memory: computations build up quantum correlations, or entanglement, to leverage the unique assets of quantum. However, that also means that once a subroutine no longer needs its qubits, those qubits cannot easily be reset and released since observing their state would impact the rest of the system. For that reason, the effect of a previous computation usually needs to be reversed before releasing and reusing quantum memory.
Q# hence has a dedicated statement for expressing computations that require such a cleanup. The statement consists of two code blocks, one containing the implementation of U and one containing the implementation of V. The uncomputation (that is, the application of U†) is done automatically as part of the statement.
The statement takes the form
within {
<statements>
}
apply {
<statements>
}
where <statements>
is replaced with any number of statements defining the implementation of U and V respectively.
Both blocks may contain arbitrary classical computations, aside from the usual restrictions for automatically generating adjoint versions that apply to the within
block. Mutably bound variables used as part of the within
block may not be reassigned as part of the apply
block.
The example of the ApplyXOrIfGreater
operation defined in the arithmetic library illustrates the usage of such a conjugation:
The operation maps |lhs⟩|rhs⟩|res⟩ → |lhs⟩|rhs⟩|res ⊕ (lhs>rhs)⟩, that is, it coherently applies an XOR to a given qubit res
if the quantum integer represented by lhs
is greater than the one in rhs
. The two integers are represented in little-endian encoding, as indicated by the usage of the corresponding data type.
operation ApplyXOrIfGreater(
lhs : LittleEndian,
rhs : LittleEndian,
res : Qubit
) : Unit is Adj + Ctl {
let (x, y) = (lhs!, rhs!);
let shuffled = Zip3(Most(x), Rest(y), Rest(x));
use anc = Qubit();
within {
ApplyToEachCA(X, x + [anc]);
ApplyMajorityInPlace(x[0], [y[0], anc]);
ApplyToEachCA(MAJ, shuffled);
}
apply {
X(res);
CNOT(Tail(x), res);
}
}
The temporary storage qubit anc
is automatically cleaned up before it is released at the end of the operation. The statements in the within
block are applied first, followed by the statements in the apply
block, and finally, the automatically generated adjoint of the within
block is applied to clean up the temporary qubit anc
.
Note
Returning control from within the apply
block is not yet supported. However, it may be supported in the future. The expected behavior, in this case, is to evaluate the returned value before the adjoint of the within
block is run, then release any qubits going out of scope (anc
in this case), and finally, return control to the callee. In short, the statement should behave similarly to a try-finally
pattern in C#.
Feedback
Submit and view feedback for