0
$\begingroup$

I tried constructing a cx gate manually using tensor products and one using QuantumCircuit in qiskit followed by converting it to an Operator. The matrix from Operator does not match the manual construction, I believe this is due to the swap in the ordering of the qubits.

def test1():
    oneone = np.array([[0, 0], [0, 1]])
    zerozero = np.array([[1, 0], [0, 0]])
    eye = np.identity(2)

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

    cx = np.kron(zerozero, eye) + np.kron(oneone, unitary)

    op_auto = UnitaryGate(unitary).control(1, ctrl_state='1')

    qc1 = QuantumCircuit(2)
    # Passes only due to reversed ordering of qubits.
    qc1.append(op_auto, [1, 0])
    op_auto = Operator.from_circuit(qc1)

    op_manual = UnitaryGate(cx)
    qc2 = QuantumCircuit(2)
    qc2.append(op_manual, [0, 1])
    op_manual = Operator.from_circuit(qc2)

    npt.assert_array_almost_equal(op_auto.data, op_manual.data)

Is there a way to force the layout to match the ordering of my choice? I did look into the layout parameter of the Operator.from_circuit method but I do not understand how to use it, there isn't any relevant documentation attached to the interface document either. Any leads would be helpful.

$\endgroup$
2
  • $\begingroup$ What would be the more general situation you'd want to adjust ordering for? In this case just flipping the order of both Kronecker products would serve the same purpose as flipping the 2nd append argument. $\endgroup$ Commented Jul 3, 2023 at 20:59
  • $\begingroup$ I am trying to apply a controlled version of a 3-8 qubit unitary that I am constructing with 1 qubit control. It doesn't match the output I'd get from doing the Kronecker product, which is what I desire. $\endgroup$
    – Zee
    Commented Jul 4, 2023 at 7:48

1 Answer 1

1
$\begingroup$

Instead of using numpy's Kronecker product, you can use Operator's tensor product. (Not better, just another way to do it):

oneone : Operator = Operator(np.array([[0, 0], [0, 1]]))
zerozero : Operator = Operator(np.array([[1, 0], [0, 0]]))
eye : Operator = Operator(np.identity(2))
unitary : Operator = Operator(np.array([[0, 1], [1, 0]]))

cx_UGate : UnitaryGate = zerozero.tensor(eye) + oneone.tensor(unitary)

qc2 = QuantumCircuit(2)
qc2.append(cx_UGate, [0, 1])
op_manual = Operator.from_circuit(qc2)

print(op_manual)

Where the output is correct:

Operator([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
      [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
      [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
      [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j]],
     input_dims=(2, 2), output_dims=(2, 2))

$$\begin{split}CX = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{pmatrix}\end{split}$$

Note that your way is also correct. I think the problem is on op_auto (its output is weird). You can do:

CXGate().to_matrix()

Note: this is in little endian convention, so we need to change the definition of CX:

$$\begin{split}CX\ q_0, q_1 = I \otimes |0\rangle\langle0| + X \otimes |1\rangle\langle1| = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \\ 0 & 1 & 0 & 0 \end{pmatrix}\end{split}$$

And so, our code:

oneone : Operator = Operator(np.array([[0, 0], [0, 1]]))
zerozero : Operator = Operator(np.array([[1, 0], [0, 0]]))
eye : Operator = Operator(np.identity(2))
unitary : Operator = Operator(np.array([[0, 1], [1, 0]]))

cx_UGate : UnitaryGate = eye.tensor(zerozero) + unitary.tensor(oneone)

qc2 = QuantumCircuit(2)
qc2.append(cx_UGate, [0, 1])
op_manual = Operator.from_circuit(qc2)

print(CXGate().to_matrix()==op_manual.data)

And the output:

[[ True  True  True  True]
 [ True  True  True  True]
 [ True  True  True  True]
 [ True  True  True  True]]

I recomend you also to change some name variables, such as unitary (being a Pauli X gate) and so.

So, the final code:

import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info.operators import Operator, Pauli
from qiskit.extensions import CXGate, UnitaryGate

class CX:
    oneone : Operator = Operator(np.array([[0, 0], [0, 1]]))
    zerozero : Operator = Operator(np.array([[1, 0], [0, 0]]))

    def __cx_little_endian(self) -> UnitaryGate:
        return Operator(Pauli('I')).tensor(self.zerozero) + Operator(Pauli('X')).tensor(self.oneone)

    def __cx_big_endian(self) -> UnitaryGate:
        return self.zerozero.tensor(Operator(Pauli('I'))) + self.oneone.tensor(Operator(Pauli('X')))

    def cx(self, is_little_endian = True) -> UnitaryGate:
        return self.__cx_little_endian() if is_little_endian else self.__cx_big_endian()

cx_obj : CX = CX()
qc2 = QuantumCircuit(2)
qc2.append(cx_obj.cx(), [0, 1])
op_manual = Operator.from_circuit(qc2)

print(cx_obj.cx(True))
print(cx_obj.cx(False))
print(CXGate().to_matrix()==op_manual.data)

If, somehow, I do not understand the question, please comment the issue and I'll try to answer again the best I can :) .

Edit

Hi! I just see your comment and here is the edit.

First of all, the UnitaryGate:

cx_gate = UnitaryGate(XGate()).control(1)

Nevertheless, you can use other formats:

cx_gate = XGate().control(1)

If we want to check on big endian notation, we can make use of reverse_bits(), a QuantumCircuit method, so here is the code (with the CX class):

class CX:
    oneone : Operator = Operator(np.array([[0, 0], [0, 1]]))
    zerozero : Operator = Operator(np.array([[1, 0], [0, 0]]))

    def __cx_little_endian(self) -> UnitaryGate:
        return Operator(Pauli('I')).tensor(self.zerozero) + Operator(Pauli('X')).tensor(self.oneone)

    def __cx_big_endian(self) -> UnitaryGate:
        return self.zerozero.tensor(Operator(Pauli('I'))) + self.oneone.tensor(Operator(Pauli('X')))
    #NEW
    def is_cx(self, op_cx: Operator, is_little_endian = True) -> bool:
        return op_cx == self.cx(is_little_endian)
    
    def cx(self, is_little_endian = True) -> UnitaryGate:
        return self.__cx_little_endian() if is_little_endian else self.__cx_big_endian()


# CODE EXAMPLE 
cx_obj : CX = CX()
cx_gate = UnitaryGate(XGate()).control(1)
qc : QuantumCircuit = QuantumCircuit(2)

qc.append(cx_gate, [0,1])
print(cx_obj.is_cx(Operator.from_circuit(qc.reverse_bits()),is_little_endian=False))

Where the output:

True
$\endgroup$
2
  • $\begingroup$ Thanks a lot for the detailed explanation! I was hoping there was a way for me to use the control method on the unitary gate and append it to the circuit AND still be able to get the non-endian operator that you have constructed. Your solution was still helpful, it would be great if you are aware of getting non-endian operator after using control method :D $\endgroup$
    – Zee
    Commented Jul 7, 2023 at 17:46
  • $\begingroup$ @Zeeshanahmed Done :) $\endgroup$ Commented Jul 9, 2023 at 10:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.