Qiskit Transpiler for quantum circuits

Viewed 3658

Okay, so, in Qiskit there is the transpile() function (see documentation). My understanding of a transpiler is best described as a way of converting one set of gate operations to another set of gate operations, with the intent of running an algorithm on different backends (since the qubit connectivity, i.e. the geometry of the architecture, varies from one quantum computer to the another). There are ways to optimize circuits by reducing redundancies and rewriting things in terms of equivalent gates like changing a CNOT conjugated by Hadamard gates to a CZ to reduce gate count. One might also want to do exactly the opposite and go from a CZ to a CNOT conjugated by Hadamards if there is not a native CZ on the hardware backend. In general this kind of transpiling in order to optimize an arbitrary circuit for some fixed hardware backend is a QMA-complete problem (see here for example...perhaps someone has used LEAN for something similar?). Sometimes variational methods are used to approximate an optimal circuit as well, but I digress.

A slightly different but related problem: Using the Kitaev-Solovay Theorem in Appendix 3 of Nielsen & Chuang, and material in 4.5, we know we can always approximate arbitrary unitary gates with a universal gate set "efficiently" (which is made precise in the book and isn't necessarily important here). Just how efficiently is an open question I think, but there is some upper and lower bound at least.

Given this, one would expect the Qiskit transpile() function to approximate unitary gates and refactor the gate set to an optimal (minimized) gate set in order to optimize circuits, but perhaps to have issues with large circuits with many nonstandard gates. However, I'm having issues getting it to work even for simple examples. For example, a permutation matrix on three qubits seems like a reasonable unitary to approximate (some could even be easily converted into SWAP-gates and X-gates by hand). But Qiskit doesn't seem to like doing this, and I could use some help understanding why. As an example, we can define a unitary (permutation) operator in Qiskit and turn it into a gate in a quantum circuit as follows:

from qiskit import *
from qiskit.quantum_info import Operator
from qiskit.compiler import transpile
%matplotlib inline

permute = Operator([[0, 0, 1, 0, 0, 0, 0, 0],
                    [1, 0, 0, 0, 0, 0, 0, 0],
                    [0, 1, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 1, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 1],
                    [0, 0, 0, 0, 0, 1, 0, 0],
                    [0, 0, 0, 0, 0, 0, 1, 0],
                    [0, 0, 0, 0, 1, 0, 0, 0]])

qc = QuantumCircuit(3)
qc.unitary(permute, [0,1,2], label='P')
qc.draw(output='mpl')

Now, if I try using the transpile() function in Qiskit as follows:

result = transpile(qc, basis_gates=['u1', 'u2', 'u3', 'cx'], optimization_level=3)
result.draw(output='mpl')

I get some huge long horrible error that ends with,

NotImplementedError: Not able to generate a subcircuit for a 3-qubit unitary

So, I guess my question is...WHY. LOL. No but seriously, am I just using this in the wrong way? are my expectations too high? Is there a reasonable way to do what I want? Would a different set of basis gates work? If so, how does one decide on the basis gates? I have also tried the decompose() function (see documentation) which seems to have a more limited functionality to the transpile() function when looking at the source code of each. The function decompose() is implemented (as can be seen in this video as well as this one). The transpile() function is also implemented (see this video at approximately 10:35 cell In[18], and in his discussion). You can also try running the commands using his

from qiskit import transpile

and it doesn't work with that import either. If anyone understands why the transpile() function (or the decompose() function for that matter) isn't working the way I would expect it to, I would love an explanation. Thanks!!!

---------Update----------

I have found yet another issue. If we use

from qiskit.circuit.random import random_circuit
rcirc2 = random_circuit(3, 4)
rcirc2.draw(output='mpl')

to generate random circuits, then run

from qiskit.compiler import transpile
result2 = transpile(rcirc2, basis_gates=['u1', 'u2', 'u3', 'cx'], optimization_level=3)
result2.draw(output='mpl')

some of the results (without 3-qubit gates) do not transpile, and some do. So this seems to go beyond just not having an implementation of 3-qubit or more gates for transpile(). Note: You may have to try this code several times to get a circuit without 3-qubit gates since the circuits are "random". For example, the following circuit will not transpile,

circ = QuantumCircuit(3)
circ.i(0)
circ.ch(2,1)
circ.cx(0,1)
circ.t(2)
circ.cx(2,0)
circ.x(0)
circ.u1(3.41, 1)
circ.ch(2,1)
circ.draw(output='mpl')

when we run

result3 = transpile(circ, basis_gates=['u1', 'u2', 'u3', 'cx'], optimization_level=3)
result3.draw(output='mpl')

Adding barriers to separate all independent gates into distinct steps doesn't seem to help either. So it isn't an issue of running parallel but independent gate operations.

----------Update 2----------

If we generate random 3-qubit unitaries using Qiskit, these also do not work.

from qiskit.quantum_info import Operator, random_unitary
U = random_unitary(8, seed=None)

qc = QuantumCircuit(4, 4)
qc.unitary(U, [0,1,3], label='P')
qc.draw(output='mpl')

You can redefine which qubits the unitary operates on, or reduce the circuit to a 3-qubit circuit, or generate larger n-qubit unitaries in this way. None of these examples will work.

2 Answers

It's possible I don't know enough about quantum computing, but I'll try to offer some assistance.

It appears transpile() doesn't yet have a way to unroll a 3 or more qubit unitary operator. It does seem to work fine for a 2-qubit unitary though.

from qiskit import *
from qiskit.quantum_info import Operator

# a subsection of your original permute operator
#   with input dimensions (2,2), and output dimensions (2,2)
#   instead of the original (2,2,2) -> (2,2,2)
perm2 = Operator(
    [[0, 0, 1, 0],
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 0, 1]
    ])

# a quantum circuit containing a 2-qubit unitary
q = QuantumCircuit(3,3)
q.unitary(perm2, [0,1], label='P')
q.h([0,1,2])
q.h([0])
q.barrier()
q.cx(1,0)
q.z(2)
q.barrier()
q.h([0])
q.barrier()
q.h([0,1,2])

# select a backend
backend = BasicAer.get_backend('qasm_simulator')

result = transpile(q, backend=backend, basis_gates=['u1', 'u2', 'u3', 'cx'], optimization_level=3)

result.draw(output='mpl')

Alternatively, you could choose to not unroll the 3-qubit unitary by removing basis_gates=['u1', 'u2', 'u3', 'cx'] from your code. That also runs fine.

The first issue about decomposing multi-qubit unitaries is resolved in Qiskit 0.19. Try updating your installation.

The second issue is that an identity gate in Qiskit is not quite identity. It is similar to a delay instruction, used to insert idle times into the circuit. The transpiler will not attempt to remove them, and so unrolling a circuit to basis_gates=['u1', 'u2', 'u3', 'cx'] will not work. You should augment the basis gates with id.

Related