PyAQASM offers advanced functionalities that allow for very efficient synthesis of large circuits.

## AbstractGates, circuit and matrix implementation

The AbstractGate class allows you to define any parameterized gate you can think of. This can be quite convenient to describe, for instance, hardware dependent gate set or to wrap quantum routines inside a “black box”. However, when the time comes to simulate or properly execute the quantum circuits, one might need to provide additional information to these black boxes in order to either:

• be able to simulate it (e.g. can we attach a matrix definition to the gate?) or

• be able to link it using a sub-circuit library (e.g. can we attach a sub-circuit implementation to the gate?)

### Matrix definition

To provide a matrix implementation for an AbstractGate, one first needs to define a Python function that returns a matrix such that:

• the function returns a matrix (for simplicity a numpy array)

• the function has the same input signature as the gate

import numpy as np
from qat.lang.AQASM import AbstractGate
def phase_matrix(theta):
return np.diag([1, np.exp(1j * theta)])

phase_gate = AbstractGate("PHASE", [float], matrix_generator=phase_matrix, arity=1)
# Or alternatively
phase_gate = AbstractGate("PHASE", [float], arity=1)
phase_gate.set_matrix_generator(phase_matrix)


### Subcircuit definition

Similarly, to provide a subcircuit implementation, one attaches to the definition of the gate a function returning a QRoutine.

from qat.lang.AQASM import QRoutine, CNOT, PH, AbstractGate

def c_phase(theta):
routine = QRoutine()
wires = routine.new_wires(2)
routine.apply(CNOT, wires)
routine.apply(PH(-theta/2), wires[1])
routine.apply(CNOT, wires)
routine.apply(PH(theta/2), wires[0])
routine.apply(PH(theta/2), wires[1])
return routine

c_phase_gate = AbstractGate("C_PHASE", [float], arity=2, circuit_generator=c_phase)
# Or alternatively
c_phase_gate = AbstractGate("C_PHASE", [float], arity=2)
c_phase_gate.set_circuit_generator(c_phase)


### Arity generator

Finally, it is possible for abstract gates to have a variable arity that depends on its parameters.

from qat.lang.AQASM import AbstractGate
# A QFT has arity equals to its sole parameter
qft = AbstractGate("QFT", [int], arity=lambda n : n)


This way, pyAQASM is able to statically check that the gate is applied on the right number of qubits.

## Lifting Python functions into quantum gates

Even though the above mechanics are very expressive when designing a library of quantum routines, it might still seem a bit clunky to have to define circuit generators and abstract gates separately.

The build_gate decorator removes a lot of this clunkyness by allowing you to turn any Python function returning a QRoutine into an AbstractGate.

from qat.lang.AQASM import QRoutine, H, build_gate, Program

def wht(nbqbits):
routine = QRoutine()
wires = routine.new_wires(nbqbits)
for wire in wires:
routine.apply(H, wire)
return routine

prog = Program()
qbits = prog.qalloc(4)
prog.apply(wht(4), qbits)
# This circuit will contain a gate containing a subcircuit
# of length 4
circuit = prog.to_circ()


In pyAQASM, it is possible to construct a Program object using AbstractGate objects representing subroutines, without specifying any implementation of the underlying subcircuit.

Consider for instance the following program:

from qat.lang.AQASM import Program, AbstractGate

prog = Program()
qbits = prog.qalloc(2 * 10)


This code will run without error. The generated circuit (the result of to_circ()) will contain a single gate without any subcircuit implementation. Therefore, attempting to execute this circuit on most QPUs will fail.

Now, let’s imagine that we have our very own implementation of an adder, lying in some Python namespace foo. Its definition will look like this:

from qat.lang.AQASM import QRoutine, H, build_gate

@build_gate("ADD", [int, int], arity=lambda n1, n2: n1 + n2)
routine = QRoutine()
wires = routine.new_wires(length1 + length2)

## Here a proper addition is implemented

return routine


What pyAQASM allows us to do is to use this definition of an adder and link it to the program above in order to attach a proper subcircuit implementation to the ADD gate. The linking is done inside the to_circ() method of the Program via the link keyword. Lets update the first piece of code to link the implementation foo.my_adder to the ADD gate.

# This part stays the same
from qat.lang.AQASM import Program, AbstractGate

prog = Program()
qbits = prog.qalloc(2 * 10)

import foo


Now the circuit variable contains a circuit that will probably be executable (depending on your implementation of my_adder). Equivalently one could have linked the full namespace foo, if we had for instance, many definitions of subcircuits inside it:

# This part stays the same
from qat.lang.AQASM import Program, AbstractGate

prog = Program()
qbits = prog.qalloc(2 * 10)

import foo


## 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.AQASM 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():
routine.apply(CNOT, wires[0], wires[1])
# The scope is now closed and stored internally

routine.apply(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.AQASM 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.AQASM import QRoutine, CNOT, PH

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


## 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.AQASM 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 accros the call tree.

The only thing you have to ensure is that 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.AQASM import Program
import qat.lang.AQASM.qftarith as qftarith
import qat.lang.AQASM.classarith as classarith

prog = Program()
qbits = prog.qalloc(20)

# No ancillae

# 9 ancillae


## Oracles and quantum types

When writing an oracle based quantum algorithm, such as Grover’s aglorithm, it is sometimes hard to translate a classical function implementing the oracle into a proper quantum circuit. Usually, this translation requires management of temporary resources and intermediate computations that can quickly become overwhelming (and, to be fair, not necessarily interesting).

PyAQASM comes with a nice feature that can help you efficiently describe complicated quantum oracles: high(er)-level quantum types.

It is possible, for instance, to declare a quantum register as a QInt. Quantum integers can then be used to directly perform arithmetic operations, or comparisons.

For instance, the following piece of code allocates two quantum integers and adds them:

from qat.lang.AQASM import Program
from qat.lang.AQASM.qint import QInt

prog = Program()
qint1 = prog.qalloc(5, QInt)
qint2 = prog.qalloc(5, QInt)
qint1 += qint2
# Abstract circuit with some unimplemented adder
circuit_abs = prog.to_circ()

import qat.lang.AQASM.qftarith as qftarith
import qat.lang.AQASM.classarith as classarith



In the following subsections, we detail the two quantum types implemented in pyAQASM: QBool and QInt.

### Quantum booleans, quantum conditionals, and quantum oracles

The simplest quantum type is the QBool type (or QBoolArray for registers).

#### Allocation

Allocation can be done, as for any other type, by adding the corresponding type to the qalloc() or new_wires() method. Since registers are arrays of qubits, they can only be typed using the QBoolArray class.

from qat.lang.AQASM import Program, QRoutine
from qat.lang.AQASM.qbool import QBoolArray

prog = Program()
qbools = prog.qalloc(3, QBoolArray)
print(type(qbools))
print(type(qbools[0]))

# or

rout = QRoutine()
qbools = rout.new_wires(3, QBoolArray)
print(type(qbools))
print(type(qbools[0]))

<class 'qat.lang.AQASM.qbool.QBoolArray'>
<class 'qat.lang.AQASM.qbool.QBool'>
<class 'qat.lang.AQASM.qbool.QBoolArray'>
<class 'qat.lang.AQASM.qbool.QBool'>


#### Logical expressions

QBool can be composed using Python’s logical operators (and, or, not, xor) to form boolean expressions:

from qat.lang.AQASM import QRoutine
from qat.lang.AQASM.qbool import QBoolArray

rout = QRoutine()
qbools = rout.new_wires(3, QBoolArray)

and_expr = qbools[0] & qbools[1] & qbools[2]
print(and_expr)

expr1 = qbools[0] & qbools[1] | qbools[2]
print(expr1)

expr2 = qbools[0] ^ qbools[1] | ~qbools[2]
print(expr2)

(q[0]&q[1]&q[2])
((q[0]&q[1])|q[2])
((q[0]^q[1])|(~ q[2]))


#### Evaluating expressions

How good is it to be able to construct expressions if we can’t use them? Boolean expressions can be evaluated. Evaluating an expression will append a sequence of gates to the current scope (i.e the Program or the QRoutine in which the QBool were declared). This sequence of gates will evaluate the expression and output the result in a temporary qubit.

For instance the following code:

from qat.lang.AQASM import QRoutine
from qat.lang.AQASM.qbool import QBoolArray

rout = QRoutine()
qbools = rout.new_wires(3, QBoolArray)

expr2 = qbools[0] ^ qbools[1] | ~qbools[2]
output = expr2.evaluate()

print(rout.arity)

3


will produce the following circuit:

In this circuit, we have:

• two CNOTs to compute the XOR of the first two qubits in some ancilla $$q_4$$, (corresponding to the qbools[0] ^ qbools[1] term)

• a CNOT and a X gate to compute the ~qbools[2] term in another ancilla $$q_5$$

• 5 X gates and a Toffoli gate to compute the final OR operator in $$q_3$$, the output qubit. Here we use the de Morgan law to turn our OR into NOTS and a AND operator.

Note

Notice that, in this example, the produced routine has arity 3. Indeed, the qubit used in the .evaluate method are automatically flagged as ancillae. Keep that in mind when using the .evaluate method.

It is also possible to specify an output qubit for the evaluation procedure:

from qat.lang.AQASM import QRoutine
from qat.lang.AQASM.qbool import QBoolArray

rout = QRoutine()
qbools = rout.new_wires(3, QBoolArray)

output = rout.new_wires(1)

expr2 = qbools[0] ^ qbools[1] | ~qbools[2]
expr2.evaluate(output=output)

print(rout.arity)

4


will produce the same circuit, but this time the routine will have arity 4 since output was declared as a proper input of the routine.

#### Quantum conditionals and with statements

Expressions can also be combined with a with statement to produce a context in which the expression is evaluated. For instance, the following piece of code evaluates an expression in a with statement and copies the result of the evaluation in an output qubit.

from qat.lang.AQASM import QRoutine, CNOT
from qat.lang.AQASM.qbool import QBoolArray

rout = QRoutine()
qbools = rout.new_wires(3, QBoolArray)

output = rout.new_wires(1)

expr2 = qbools[0] ^ qbools[1] | ~qbools[2]

with expr2 as condition:
CNOT(condition, output)

print(output[0], condition[0])

q[3] q[4]


This will produce the following circuit:

Notice that the boolean expression was first evaluated in a temporary qubit (qubit 4), then we copied the result in an output qubit (qubit 3), finally, the expression was uncomputed upon exiting the with block.

The with statement is in fact just a syntactic sugar for the following code:

from qat.lang.AQASM import QRoutine, CNOT
from qat.lang.AQASM.qbool import QBoolArray

rout = QRoutine()
qbools = rout.new_wires(3, QBoolArray)

output = rout.new_wires(1)

expr2 = qbools[0] ^ qbools[1] | ~qbools[2]

# starting a compute scope
with rout.compute():
# evaluating the expression
condition = expr2.evaluate()

# copying the result
CNOT(condition, output)

# uncomputing the evaluation
rout.uncompute()

# releasing the ancilla condition for later use
rout.free_ancillae(condition)


Warning

Quantum conditionals cannot be called on an expression whose underlying qubits were allocated in a Program class. Therefore, it should only be used inside a QRoutine first.

#### Building phase oracles

Last but not least, it is possible to use a boolean expression to automatically generate a quantum circuit that will flip the phase of basis states that verify the expression. This is done by calling the method phase on an expression.

from qat.lang.AQASM import Program, CNOT, H
from qat.lang.AQASM.qbool import QBoolArray

prog = Program()
qbools = prog.qalloc(2, QBoolArray)
for qbit in qbools:
H(qbit)
expr = qbools[0] & qbools[1]
expr.phase()

job = prog.to_circ().to_job()

from qat.qpus import get_default_qpu

result = get_default_qpu().submit(job)
for sample in result:
print(sample.state, sample.amplitude)

|[False, False]> (0.4999999999999999+0j)
|[False, True]> (0.4999999999999999+0j)
|[True, False]> (0.4999999999999999+0j)
|[True, True]> (-0.4999999999999999+0j)


Since our expression evaluates at True if and only if the two qubits are set to True only state $$|11\rangle$$ had its phase flipped.

Warning

The phase method cannot be called on an expression whose underlying qubits were allocated in a Program class. Therefore, it should only be used inside a QRoutine first.

### Quantum integers

The second type allows to type registers as integers.

#### Allocation

Similarly to QBool, QInt is allocated using the qalloc() or new_wires() methods.

from qat.lang.AQASM import Program, QRoutine
from qat.lang.AQASM.qint import QInt

prog = Program()
qint = prog.qalloc(5, QInt)
print(qint)

rout = QRoutine()
qint = rout.new_wires(5, QInt)
print(qint)

QInt(q[0]..q[4])
QInt(q[0]..q[4])


#### Setting a classical value

QInt have a method called set_value() that xors the content of the QInt with a classical value.

from qat.lang.AQASM import QRoutine
from qat.lang.AQASM.qint import QInt

rout = QRoutine()
qint = rout.new_wires(2, QInt)
qint.set_value(3)


produces:

#### Arithmetic expressions

Instances of QInt can be combined via addition and multiplication. Combining them forms an arithmetic expression without adding any gate to the underlying circuit.

The only operation that triggers a circuit generation is the += operator.

from qat.lang.AQASM import Program, QRoutine
from qat.lang.AQASM.qint import QInt

prog = Program()
qint1 = prog.qalloc(5, QInt)
qint2 = prog.qalloc(5, QInt)

qint1 + qint2
circuit1 = prog.to_circ()

qint1 += qint2
circuit2 = prog.to_circ()

print("First circuit")
for op in circuit1.iterate_simple():
print(op)

print("Second circuit")
for op in circuit2.iterate_simple():
print(op)

First circuit
Second circuit
('ADD', [5, 5], [4, 3, 2, 1, 0, 9, 8, 7, 6, 5])


Notice that the first circuit is empty: the statement qint1 + qint2 did not produce any gate. The second statement, qint1 += qint2 did produce an adder.

The same holds for multiplication:

from qat.lang.AQASM import Program, QRoutine
from qat.lang.AQASM.qint import QInt

prog = Program()
qint1 = prog.qalloc(5, QInt)
qint2 = prog.qalloc(5, QInt)
qint3 = prog.qalloc(5, QInt)

qint1 * qint2
circuit1 = prog.to_circ()

qint3 += qint1 * qint2
circuit2 = prog.to_circ()

print("First circuit")
for op in circuit1.iterate_simple():
print(op)

print("Second circuit")
for op in circuit2.iterate_simple(depth=0):
print(op)

First circuit
Second circuit
('MULT', [5, 5, 5], [4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 14, 13, 12, 11, 10])


#### Conditionals on quantum integers

It is possible to compare quantum integers with one another or to some classical values. These comparisons produce QBool objects and can thus be used to produce oracles or conditional statements.

For instance, one can write a program that:

• compares two quantum integers

• and increments a quantum integer if and only if the comparison fails

from qat.lang.AQASM import QRoutine
from qat.lang.AQASM.qint import QInt

rout = QRoutine()
qint1 = rout.new_wires(5, QInt)
qint2 = rout.new_wires(5, QInt)
qint3 = rout.new_wires(5, QInt)

with qint1 != qint2 as condition:
qint3 += condition.cast_to(QInt)


Notice the usage of the cast_to method that allows us to cast any type to a new type (by re-wrapping the quantum register).

Of course, one can also use the .evaluate and .phase methods. For instance, the following piece of code flips the phase of all classical states that represent integers below some constant, say 5:

from qat.lang.AQASM import Program, QRoutine, H
from qat.lang.AQASM.qint import QInt
import qat.lang.AQASM.qftarith as qftarith

rout = QRoutine()
qint = rout.new_wires(3, QInt)

for qbit in qint:
H(qbit)
(qint < 5).phase()

prog = Program()
qbits = prog.qalloc(3, QInt)

rout(qbits)
job = circuit.to_job()

from qat.qpus import get_default_qpu

result = get_default_qpu().submit(job)

for sample in result:
print(sample.state, sample.amplitude)

|0>|Anc:0> (-0.35355339059327323+3.560838358596747e-17j)
|1>|Anc:0> (-0.35355339059327323+3.284126769102014e-17j)
|2>|Anc:0> (-0.3535533905932733+3.392966268662476e-17j)
|3>|Anc:0> (-0.35355339059327323-1.0610875721556842e-17j)
|4>|Anc:0> (-0.3535533905932733-1.0679583853003736e-18j)
|5>|Anc:0> (0.3535533905932731-1.0016313363825637e-16j)
|6>|Anc:0> (0.3535533905932731-6.539164195928566e-17j)
|7>|Anc:0> (0.3535533905932732-1.7389835079820265e-17j)


### Example: Grover oracle for graph coloring

Using these tools, it is quite straightforward to write simple phase oracles for Grover-like applications.

For instance, one can write in a few lines of code a routine that will act as an oracle for a graph coloring problem.

We will construct a Python function that will take a graph (networkx.Graph) and a number of bits to use to store each color. It will return a QRoutine that flips the phase of a basis state if and only if it describes a clean coloration.

from functools import reduce
from qat.lang.AQASM import QRoutine
from qat.lang.AQASM.qint import QInt

def coloring_oracle(graph, bitlength):
rout = QRoutine()
colors = [rout.new_wires(bitlength, QInt) for _ in graph.nodes()]
reduce(
lambda a, b: a & b,
(colors[a] != colors[b] for a, b in graph.edges())
).phase()
return rout

import networkx as nx

graph = nx.generators.path_graph(20)
bitlength = 1 # looking for 2 colorings, so colors are stored on a single bit

oracle = coloring_oracle(graph, bitlength)
print("Oracle has arity:", oracle.arity)
print("Oracle uses {} ancillae".format(len(oracle.ancillae)))

Oracle has arity: 20
Oracle uses 19 ancillae


In this function, we:

• declare an array of quantum integers, one for each vertex of our graph

• build a formula that computeс the logical and of $$c_i \neq c_j$$ for each edge $$i$$ $$j$$ of our graph

• use this formula to perform a phase flip

This implementation uses a lot of qubits. For each $$c_i \neq c_j$$ in our for loop, pyAQASM allocates a temporary qubit. Then a generalized controlled $$Z$$ is used to flip the phase of the states that have all these ancillae set to 1. Finally, all the temporary qubits are freed by uncomputing the $$c_i \neq c_j$$ statements.

Asymptotically, this oracle uses $$b|V| + |E|$$ qubits, without considering the fact that the implementation of the generalized controlled $$Z$$ might require additional qubits.

It is possible to save up some qubits, at the cost of an increased number of gates. The following code snippet implements the same oracle, but a bit differently:

• it allocates a counter large enough to count up to $$|E|$$

• for each edge, if $$c_i \neq c_j$$, it increments the counter by 1

• it flips the phase of the basis state if and only if the counter contains exactly $$|E|$$

• finally, it uncomputes all the counter increments

from qat.lang.AQASM import QRoutine
from qat.lang.AQASM.qint import QInt

def coloring_oracle(graph, bitlength):
rout = QRoutine()
colors = [rout.new_wires(bitlength, QInt) for _ in graph.nodes()]
counter = rout.new_wires(graph.number_of_edges().bit_length(), QInt)
with rout.compute():
for a, b in graph.edges():
with colors[a] != colors[b] as condition:
counter += condition.cast_to(QInt)
(counter == graph.number_of_edges()).phase()
rout.uncompute()
rout.set_ancillae(counter)
return rout

import networkx as nx

graph = nx.generators.path_graph(20)
bitlength = 1 # looking for 2 colorings, so colors are stored on a single bit

oracle = coloring_oracle(graph, bitlength)
print("Oracle has arity:", oracle.arity)
print("Oracle uses {} ancillae".format(len(oracle.ancillae)))

Oracle has arity: 20
Oracle uses 6 ancillae


Asymptotically, this oracle is far more frugal since it uses only $$b|V| + log(|E|)$$ qubits.

Notice that in both examples, we didn’t have to directly mention any quantum gate. Everything is handled by the quantum types.