Creating and adding quantum gates to a circuit

Native gates, controls and daggers

The qat.lang library provides a basic gate set to write your circuits. Their names are rather self-explanatory:

  • Constant gates: X, Y, Z, H, S, T, CNOT, CCNOT, CSIGN, SWAP, SQRTSWAP, ISWAP

  • Parametrized gates: RX, RY, RZ, PH (phase shift)

(The mathematical definition of these gates is given in Atos Quantum Assembler (AQASM).)

Quantum gates can be applied on any qubits composing a program. Qubits need to be allocated first:

from qat.lang import Program

my_program = Program()
qbits_reg = my_program.qalloc(10)  # Allocate 10 qubits

Gates can be applied to the qubits from the qubit register:

import numpy as np
from qat.lang import CNOT, H, RZ

CNOT(qbits_reg[0], qbits_reg[1])
H(qbits_reg[2])
RZ(np.pi/2.)(qbits_reg[0])

From this initial gate set, one can also build new gates using control and dagger operations:

PH(np.pi/2).ctrl()(qbits_reg[0], qbits_reg[1])
RX(np.pi/4).dag()(qbits_reg[0])

Of course, controls can be stacked:

PH(np.pi/2).ctrl().ctrl().ctrl()(qbits_reg[0:4])

By convention, the outermost control qubit is always the first argument when calling a gate.

# Here qbits_reg[0] is the control qubit:
H.ctrl()(qbits_reg[0], qbits_reg[2])
# And here, qbits_reg[0] and qbits_reg[2] are the controls:
H.ctrl().ctrl()(qbits_reg[0], qbits_reg[2], qbits_reg[9])

Quantum gates can be applied on any qubits composing a routine. Gates can be applied to the qubits from its index:

import numpy as np
from qat.lang import qrout, CNOT, H, RZ

@qrout
def my_routine():
    CNOT(1)
    H(2)
    RZ(np.pi/2.)(0)

From this initial gate set, one can also build new gates using control and dagger operations:

@qrout
def my_routine():
    PH(np.pi/2).ctrl()(0, 1)
    RX(np.pi/4).dag()(0)

Of course, controls can be stacked:

@qrout
def my_routine():
    PH(np.pi/2).ctrl().ctrl().ctrl()(*[idx for idx in range(4)])

By convention, the outermost control qubit is always the first argument when calling a gate.

@qrout
def my_routine():
    # Here "0" is the control qubit:
    H.ctrl()(0, 2)
    # And here, "0" and "2" are the controls:
    H.ctrl().ctrl()(0, 2, 9)

User defined parametrized gates (AbstractGate)

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 (for simplicity a numpy array)

  • has the same input signature as the gate

import numpy as np
from qat.lang 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 import QRoutine, CNOT, PH, AbstractGate

def c_phase(theta):
    routine = QRoutine()
    wires = routine.new_wires(2)
    CNOT(wires)
    PH(-theta/2)(wires[1])
    CNOT(wires)
    PH(theta/2)(wires[0])
    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 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.

Deprecated custom gates

Warning

Custom gates are deprecated. Consider using constant valued AbstractGate instead (defined in the previous section).

PyAQASM offers the possibility to define your own gates from a matrix. This is done via the CustomGate class.

import numpy as np
from qat.lang import CustomGate

mat = np.array([[-1, 0], [0, 1]])
my_minus_z = CustomGate(mat)
my_minus_z(qbits_reg[0])