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.