quantumlib / Cirq

A Python framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits.
Apache License 2.0
4.24k stars 1.01k forks source link

Incorrect unitary of controlled QasmUGate? #5959

Open ArfatSalman opened 1 year ago

ArfatSalman commented 1 year ago

Description of the issue It seems like the unitary of controlled QasmUGate is not correct with qiskit's CU3Gate.

How to reproduce the issue

import cirq
from cirq.circuits.qasm_output import QasmUGate
import numpy as np

cu3 = QasmUGate(
    0.9284573101905567 / np.pi, 
    0.45091656513571715 / np.pi, 
    3.783886899929776 / np.pi).controlled(num_controls=1)

qr_cirq = [cirq.NamedQubit('q' + str(i)) for i in range(2)]
circuit = cirq.Circuit(
    cu3(qr_cirq[1], qr_cirq[0])
)

cirq_unitary = cirq.unitary(circuit)

import qiskit
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit.circuit.library.standard_gates import CU3Gate

qr = QuantumRegister(2, name='qr')
cr = ClassicalRegister(2, name='cr')
qc = QuantumCircuit(qr, cr, name='qc')

qc.append(CU3Gate(0.9284573101905567,0.45091656513571715,3.783886899929776), qargs=[qr[1], qr[0]], cargs=[])

from qiskit import Aer, transpile, execute
backend = Aer.get_backend('unitary_simulator')
qiskit_unitary = execute(qc, backend).result().get_unitary(qc)

print(cirq.equal_up_to_global_phase(qiskit_unitary, cirq_unitary)) 
# False

As per the docs,

Screenshot 2022-12-11 at 19 38 53

U3Gate corresponds to QasmUGate with half turns. So I am expecting the controlled version to correspond to CU3Gate. However, the unitaries are different for some reason.

Cirq version 1.0.0

viathor commented 1 year ago

Thank you for a detailed bug report! This is helpful!

Related: #5928

tanujkhattar commented 1 year ago

If two unitaries U1 and U2 are equal upto global phase but not identical, then their controlled versions would not be equal upto global phase because the global phase of U1/U2 becomes local phase of CU1 and CU2.

A classic example would X gate and Rx gate, both of which differ only by a global phase. For example:

>>> import cirq
>>> import numpy as np
>>> g1, g2 = cirq.X, cirq.Rx(rads=np.pi)
>>> cirq.equal_up_to_global_phase(g1, g2)
True
>>> cg1, cg2 = g1.controlled(), g2.controlled()
>>> cirq.equal_up_to_global_phase(cg1, cg2)
False
>>> cirq.unitary(cg1) # CX gate swaps 0/1 based when control is 1.
array([[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]])

>>> cirq.unitary(cg2) # CRx gate swaps 0/1 and also adds a -1 phase when control is 1. 
Out[14]:
array([[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, 0.-1.j],
       [0.+0.j, 0.+0.j, 0.-1.j, 0.+0.j]])

Note that the two controlled gates shown in the example above are still equivalent upto single qubit rotations (eg: prepend a Z rotation on qubit 0).

ArfatSalman commented 1 year ago

Thank you for the detailed comment, @tanujkhattar!

viathor commented 1 year ago

I think @tanujkhattar is correct about the root cause. Still, the fact that qiskit's CU3Gate(...) and cirq's QasmUGate(...).controlled_by(...) are inequivalent gates sounds like a problem.