3

Hi all: Cirq offers a way to create a unitary gate from an array. I tried doing the same in Qiskit but have not been able to quite make it. Here is a sample code with what I could put together so far. Also, is there a way to apply the controlled operation with this unitary from q[0] to q[1] say? Or create a certain labeled gate for that purpose to be used in the circuit? If so, how? Thanks a lot!

from qiskit.extensions import *
U2x2 = np.array([[0.998762, -0.049745], [-0.049745, -0.998762]])
# Still not sure how to use this, though it compiles
gate2x2 = UnitaryGate(U2x2)

# The best I could do so far was this:
# Create the quantum circuit
q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)

qc.unitary(U2x2, range(1))
qc.measure(q[0], c[0])
Vox
  • 143
  • 8
  • So you're main question is if it is possible/how to make a controlled unity gate from q[0] to q[1]? Or it the question pertaining to the code you posted. Because the code you posted compiles and runs fine for me – Matthew Stypulkoski Dec 27 '19 at 21:08
  • Right, the code does compile and run. But how do you get the controlled gate for the same matrix? And how to extend to 4x4 matrices, say? Is that possible? Also, is what I posted the only way to go? I seem to understand that one can give this gate a name and use that name later, but not sure how to do all that. – Vox Dec 28 '19 at 22:08

2 Answers2

2

I believe your implementation with qc.unitary(U2x2, range(1)) is correct if you just want a regular unitary gate from an array. Instantiating a UnitaryGate seems to be already done within the qc.unitary() call, so just calling qc.unitary() should be fine.

However, if you want a controlled version of this unitary gate, I found that instantiating the UnitaryGate manually and then adding a control to that gate works fine. Something similar to this should work:

from qiskit.circuit.add_control import add_control

gate2x2 = UnitaryGate(U2x2)
gate2x2_ctrl = add_control(gate2x2, 1)

qc.append(gate2x2_ctrl, [q[0], q[1]])

Here is the source code for add_control() as well in case you wanted more information.

  • This is extremely useful. Thanks a lot! – Vox Dec 31 '19 at 15:57
  • Just one more question, is there a way to define gates from matrices larger than 2x2? For example, a 4x4 matrix acting on two qubits? With what you showed me I can already do that for a Kronecker product of two 2x2 matrices, but what if a Kronecker product decomposition is not known? – Vox Dec 31 '19 at 16:00
  • 1
    I think it should be possible to use a 4x4 matrix, but you would your first method of adding the unitary gate. So you would create `U4x4` which would store a 4x4 numpy array, and then call `qc.unitary(U4x4, [q[0], q[1]])` if you wanted to attach it to qubits 0 and 1 – Matthew Stypulkoski Dec 31 '19 at 18:37
  • 1
    Funny, I just updated my Qiskit code and although add_control() documentation says a third argument is optional, my code won't run unless I pass a string, the name of the gate, as a third argument, like so: `add_control(gate2x2, 1,'CU2x2')`. – Vox Jan 04 '20 at 01:29
  • The `4x4` works as well, but at least the drawing is strange, when looking at the circuit the following shows two controls (0 and 1) instead of just one control: `q = QuantumRegister(3, 'q') qc = QuantumCircuit(q) gate4x4 = UnitaryGate(U4x4) gate4x4_ctrl = add_control(gate4x4, 1,'CU4x4') qc.append(gate4x4, [q[0], q[1]] ) qc.append(gate4x4_ctrl, [ q[0], q[1], q[2] ] )` – Vox Jan 04 '20 at 02:03
1

It turns out the following works, but the gate is applied in a counter-intuitive way. This may be a result of the bit ordering in Qiskit, which seems to give rise here to a very non-standard implementation, so beware! Specifically, test the following code (you can turn the qubits to |1> using the commented-out x() gates):

q = QuantumRegister(2, 'q')
c = ClassicalRegister(2, 'c')

U4x4 = np.array( [[0, 1, 0, 0], [1, 0, 0, 0], 
                  [0, 0, 0, 1], [0, 0, 1, 0]] )

qc = QuantumCircuit(q,c)
qc.x(q[0])
#qc.x(q[1])

gate4x4 = UnitaryGate(U4x4)
qc.append(gate4x4, [q[0], q[1]] )
qc.measure(q[0],c[0])
qc.measure(q[1],c[1])
qc.draw()

Just by looking at the matrix, you can see that this is supposed to have the following output: |00> -> |01>, |01> -> |00>, |10> -> |11> and |11> -> |10>, where the first bit, i.e. a in |ab> denotes the value measured on q[0]. In other words, if input is q[0]=|1> and q[1]=|0>, one would expect the input state in the standard basis to be (the column vector) (0;0;1;0) so the output would be (0;0;0;1). But try it out by simulating it on Aer and you'll find out that's not the case. With qc.x(q[0]) as shown, the output is (0;0;0;0). To get the expected output you would need to append on [q[1], q[0]] instead. While this can definitely be handled by someone who was made aware, I think this is utterly confusing.

Here is a controlled version of this gate. Again, notice the (very unintuitive) inverse order of the qubits needed in the append instruction in order for the first qubit q[0] to act as the control.

q = QuantumRegister(3, 'q')
c = ClassicalRegister(3, 'c')

U4x4 = np.array( [[0, 1, 0, 0], [1, 0, 0, 0], 
                  [0, 0, 0, 1], [0, 0, 1, 0]] )
k = 4
# This makes a controlled variant of the matrix
C_U = np.vstack([np.hstack([np.eye(k),       np.zeros((k,k))]), 
                 np.hstack([np.zeros((k,k)), U4x4])])

qc = QuantumCircuit(q,c)
#qc.x(q[0])

gate3Q = UnitaryGate(C_U)
qc.x(q[0])
qc.append(gate3Q, [q[2], q[1], q[0]] )
qc.measure(q[0],c[0])
qc.measure(q[1],c[1])
qc.measure(q[2],c[2])
qc.draw()

One can easily confirm for themselves what happens by running this code (and turning x(q[0]) on and off, etc.) with

backend = BasicAer.get_backend('qasm_simulator')
shots = 2048
results = execute(qc, backend=backend, shots=shots).result()
answer = results.get_counts()
print(answer)
plot_histogram(answer)

By looking at the definition of C_U and the append, it would seem that the first qubit, q[2], should be the control. But no, it is q[0]. For further reference, here is the Qiskit version I'm running:

{'qiskit-terra': '0.11.0',
 'qiskit-aer': '0.3.4',
 'qiskit-ignis': '0.2.0',
 'qiskit-ibmq-provider': '0.4.4',
 'qiskit-aqua': '0.6.1',
 'qiskit': '0.14.0'}
Vox
  • 143
  • 8