Compressing single qubit gates
The KAKCompression
plugin can be used to automatically compress sequences of consecutive single qubit gates
into a fixed universal pattern of your choice. In practice, it can be used after various compilation stages in order to optimally reduce
the number of single qubit gates in a circuit.
Predefined patterns
The plugin comes with predefined universal patterns: - \(RZ-RX-RZ\) (called ZXZ) - \(RX-RZ-RX\) (called XZX) - \(RZ-RY-RZ\) (called ZYZ) - \(RZ - RX(\pi/2) - RZ - RX(\pi/2) - RZ\) (called ions or rx+) - \(U3\) (called u3 or ibm)
One can directly construct a KAKCompression
from one of these patterns using a known pattern (i.e. a pattern
listed by list_decompositions()
):
# declaring a circuit
from qat.pbo.kak import list_decompositions
from qat.plugins import KAKCompression
from qat.lang import H, RX, RZ, CNOT, qrout
@qrout
def circuit():
"Create a quantum circuit"
H(0)
RX(0.232)(0)
CNOT(0, 1)
RZ(1.89)(1)
H(1)
print("== Initial circuit ==")
for op in circuit.iterate_simple():
print(op)
job = circuit.to_job()
for decomposition in list_decompositions():
with KAKCompression(decomposition=decomposition):
compiled_job = job.compile()
print(f"\n== Circuit compiled with {decomposition!r} ==")
for op in compiled_job.circuit.iterate_simple():
print(op)
== Initial circuit ==
('H', [], [0])
('RX', [0.232], [0])
('CNOT', [], [0, 1])
('RZ', [1.89], [1])
('H', [], [1])
== Circuit compiled with 'ions' ==
('RZ', [6.515185307179586], [0])
('RX', [1.5707963267948966], [0])
('RZ', [1.5707963267948966], [0])
('RX', [1.5707963267948966], [0])
('RZ', [0.0], [0])
('CNOT', [], [0, 1])
('RZ', [8.173185307179587], [1])
('RX', [1.5707963267948966], [1])
('RZ', [1.5707963267948966], [1])
('RX', [1.5707963267948966], [1])
('RZ', [0.0], [1])
== Circuit compiled with 'ZXZ' ==
('RZ', [1.8027963267948968], [0])
('RX', [1.5707963267948966], [0])
('RZ', [1.5707963267948966], [0])
('CNOT', [], [0, 1])
('RZ', [3.4607963267948962], [1])
('RX', [1.5707963267948966], [1])
('RZ', [1.5707963267948966], [1])
== Circuit compiled with 'XZX' ==
('RX', [1.5707963267948966], [0])
('RZ', [1.5707963267948966], [0])
('RX', [1.8027963267948968], [0])
('CNOT', [], [0, 1])
('RX', [-4.71238898038469], [1])
('RZ', [1.5707963267948966], [1])
('RX', [3.4607963267948962], [1])
== Circuit compiled with 'ZYZ' ==
('RZ', [3.3735926535897933], [0])
('RY', [1.5707963267948966], [0])
('RZ', [0.0], [0])
('CNOT', [], [0, 1])
('RZ', [5.031592653589793], [1])
('RY', [1.5707963267948966], [1])
('RZ', [0.0], [1])
== Circuit compiled with 'u3' ==
('U3', [1.5707963267948966, 0.0, 3.3735926535897933], [0])
('CNOT', [], [0, 1])
('U3', [1.5707963267948966, 0.0, 5.031592653589793], [1])
== Circuit compiled with 'ibm' ==
('U3', [1.5707963267948966, 0.0, 3.3735926535897933], [0])
('CNOT', [], [0, 1])
('U3', [1.5707963267948966, 0.0, 5.031592653589793], [1])
== Circuit compiled with 'rx+' ==
('RZ', [6.515185307179586], [0])
('RX', [1.5707963267948966], [0])
('RZ', [1.5707963267948966], [0])
('RX', [1.5707963267948966], [0])
('RZ', [0.0], [0])
('CNOT', [], [0, 1])
('RZ', [8.173185307179587], [1])
('RX', [1.5707963267948966], [1])
('RZ', [1.5707963267948966], [1])
('RX', [1.5707963267948966], [1])
('RZ', [0.0], [1])
Declaring custom patterns
It is also possible to declare custom decompositions. In order to do so we need to specify:
the universal pattern to used
a way to compute the angles to feed to this pattern given the coefficient of a \(SU(2)\) matrix
Let us give an example where we would like to use the following patter:
It is easy to see that our pattern is pretty close from a ZXZ decomposition except that we need to flip the sign of the two Z rotations.
We need to know how to compute values for \(\alpha, \beta, \gamma\) given the entries of the matrix to decompose.
Luckily, we can import method qat.pbo.decompositions.decompositions.get_euler_angles()
that, given values for the 4 coefficient of a unitary matrix
returns the value of angles for a \(ZXZ\) decomposition.
from qat.pbo.decompositions.decompositions import get_euler_angles
def my_angles(a, b, c, d): # here a,b,c,d are the coefficient of the matrix to decompose
d, e, f = get_euler_angles(a, b, c, d)
return -d, e, -f
We can now declare our plugin. We need to declare 4 variables that will correspond to the 4 entries of the matrix, and build a pattern that uses these variables (thanks to our function):
from qat.pbo import VAR
matrix = [VAR() for _ in range(4)]
alpha, beta, gamma = my_angles(*matrix)
pattern = [('X', [0]), ('RZ', [0], alpha), ('RX', [0], beta), ('RZ', [0], gamma), ('X', [0])]
We can now safely declare our plugin:
from qat.plugins import KAKCompression
plugin = KAKCompression(variables=matrix, pattern=pattern)
And run it on the previous example:
# declaring a circuit
from qat.plugins import KAKCompression
from qat.lang import H, RX, RZ, CNOT, qrout
@qrout
def circuit():
"Create a quantum circuit"
H(0)
RX(0.232)(0)
CNOT(0, 1)
RZ(1.89)(1)
H(1)
print("== Initial circuit ==")
for op in circuit.iterate_simple():
print(op)
job = circuit.to_job()
with plugin:
compiled_job = job.compile()
print("\n== Compiled circuit ==")
for op in compiled_job.circuit.iterate_simple():
print(op)
== Initial circuit ==
('H', [], [0])
('RX', [0.232], [0])
('CNOT', [], [0, 1])
('RZ', [1.89], [1])
('H', [], [1])
== Compiled circuit ==
('X', [], [0])
('RZ', [-3.3735926535897933], [0])
('RX', [1.5707963267948966], [0])
('RZ', [-0.0], [0])
('X', [], [0])
('CNOT', [], [0, 1])
('X', [], [1])
('RZ', [-5.031592653589793], [1])
('RX', [1.5707963267948966], [1])
('RZ', [-0.0], [1])
('X', [], [1])