Ancillae management and compute scopes

Automated ancillae management

The QRoutine object comes with a system of ancillae management. In practice, one can allocate fresh wires and declare them as ancillae:

from qat.lang import QRoutine

routine = QRoutine()
wires = routine.new_wires(1)
ancilla = routine.new_wires(1)
routine.set_ancillae(ancilla)

This routine will have arity 1, its second wire being declared as an ancilla. Upon calling the to_circ() method of a program containing this routine, additional qubits will be dynamically allocated and passed to the routine. Of course this allocation is made recursively across the call tree.

The only thing you have to ensure is that the ancillae are freed before leaving the routine (i.e are in product \(|0\rangle\) state).

As a consequence, it is possible to link subcircuit implementations of a gate using different number of ancillae:

from qat.lang import Program
from qat.lang.AQASM.arithmetic import add
import qat.lang.AQASM.qftarith as qftarith
import qat.lang.AQASM.classarith as classarith

prog = Program()
qbits = prog.qalloc(20)
add(10, 10)(qbits)

# No ancillae
circuit = prog.to_circ(link=[qftarith])

# 9 ancillae
circuit = prog.to_circ(link=[classarith])

Compute/uncompute scopes

A quite common programming scheme in reversible computation in general, and quantum computation in particular, is the compute/uncompute scheme.

Usually, one has to compute some function, use the result of this computation, and then uncompute the first part of the circuit to free up some ancilla resources.

This scheme is natively supported inside the QRoutine and Program objects using a with statement:

from qat.lang import QRoutine, CNOT, PH

routine = QRoutine()

# Allocating 2 wires to apply gates on
wires = routine.new_wires(2)

# Opening a computation scope
with routine.compute():
    CNOT(wires[0], wires[1])
# The scope is now closed and stored internally

PH(1.)(wires[1])

# Here we 'pop' the last closed scope and apply its dagger
routine.uncompute()
# The routine now contains 3 gates

or, using programs:

from qat.lang import Program, CNOT, PH

prog = Program()

# Allocating 2 wires to apply gates on
wires = prog.qalloc(2)

# Opening a computation scope
with prog.compute():
    CNOT(wires[0], wires[1])
# The scope is now closed and stored internally

PH(1.)(wires[1])

# Here we 'pop' the last closed scope and apply its dagger
prog.uncompute()
# The routine now contains 3 gates

Of course computation scopes can be nested:

from qat.lang import QRoutine, CNOT, PH

routine = QRoutine()
wires = routine.new_wires(3)
with routine.compute():
    CNOT(wires[0], wires[1])
    with routine.compute():
        CNOT(wires[1], wires[2])
    PH(1.)(wires[2])
    routine.uncompute()
PH(2.)(wires[1])
routine.uncompute()
# The routine now contains 9 gates