quantumlib / Cirq

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

`optimize_for_target_gateset` does not return equivalent circuit #6517

Open jiannanWang opened 5 months ago

jiannanWang commented 5 months ago

Description of the issue

The document says optimize_for_target_gateset returns "An equivalent circuit containing gates accepted by gateset." However, applying optimize_for_target_gateset to the below circuit generates a new circuit with a different final state vector.

Note that the final state vectors have a large difference (atol >= 1e-3).

How to reproduce the issue

import cirq
from cirq import *

qr = cirq.LineQubit.range(5)
qc = cirq.Circuit()
qc.append(XXPowGate(exponent=0.05914855949794997, global_shift=4.257271900551728).on(qr[0], qr[1]))
qc.append(Rz(rads=3.342979007376063).on(qr[2]))
qc.append(XXPowGate(exponent=2.3059912502413593, global_shift=5.787646153894631).on(qr[3], qr[4]).controlled_by(qr[1]))

new_qc = cirq.optimize_for_target_gateset(qc)

simulator = cirq.Simulator()
st1 = simulator.simulate(qc, qubit_order=sorted(qc.all_qubits())).final_state_vector
print(st1)

st2 = simulator.simulate(new_qc, qubit_order=sorted(new_qc.all_qubits())).final_state_vector
print(st2)
print(cirq.equal_up_to_global_phase(st1, st2, atol=1e-3))
``` [ 0.70258266-0.70552826j 0. -0.j 0. -0.j 0. -0.j -0. +0.j -0. +0.j -0. +0.j -0. +0.j 0. -0.j 0. -0.j 0. -0.j 0. -0.j -0. +0.j -0. +0.j -0. +0.j -0. +0.j 0. -0.j 0. -0.j 0. -0.j 0. -0.j -0. +0.j -0. +0.j -0. +0.j -0. +0.j -0.05791184+0.05842676j 0. -0.j 0. -0.j 0.03046589+0.03019739j 0. +0.j -0. +0.j -0. +0.j 0. -0.j ] [ 7.0258260e-01-7.0552832e-01j -2.4170467e-07-2.0510472e-08j 3.4080244e-08+6.8633639e-08j 2.8088609e-08+9.3309067e-09j -0.0000000e+00+0.0000000e+00j 0.0000000e+00+0.0000000e+00j 0.0000000e+00-0.0000000e+00j 0.0000000e+00-0.0000000e+00j 2.6692208e-09-1.9153942e-08j 2.3014948e-09-1.6515171e-08j 3.1672400e-08+4.4137511e-09j -9.9875805e-09-1.3918358e-09j -0.0000000e+00+0.0000000e+00j -0.0000000e+00+0.0000000e+00j 0.0000000e+00-0.0000000e+00j 0.0000000e+00+0.0000000e+00j 2.6655547e-08-3.2647190e-08j 8.8367956e-16-8.9282454e-17j -6.2901604e-16+1.1079911e-15j 5.5332231e-23+2.1014054e-23j -0.0000000e+00+0.0000000e+00j -0.0000000e+00+0.0000000e+00j 0.0000000e+00+0.0000000e+00j 0.0000000e+00-0.0000000e+00j -4.1379835e-02-9.9977581e-03j -3.5679083e-02-8.6203888e-03j 1.6532000e-02-6.8424508e-02j -5.2132001e-03+2.1576982e-02j 0.0000000e+00+0.0000000e+00j 0.0000000e+00+0.0000000e+00j -0.0000000e+00+0.0000000e+00j 0.0000000e+00+0.0000000e+00j] False ```

Cirq version You can get the cirq version by printing cirq.__version__. From the command line:

1.3.0
NoureldinYosri commented 5 months ago

doesn't looklike an issue in the optimize_for_target_gateset but in the cirq.decompose protocol which optimize_for_target_gateset uses

specifically decomposing the third operation doesn't yield a correct result

>> op = XXPowGate(exponent=2.3059912502413593, global_shift=5.787646153894631).on(qr[3], qr[4]).controlled_by(qr[1])
>> c = cirq.Circuit(cirq.decompose(op))
>> print(cirq.equal_up_to_global_phase(cirq.unitary(c), cirq.unitary(op)))
False
NoureldinYosri commented 5 months ago

diving deeper the decomposition reaches a controlled CZPowGate but the decomposition of it wrong. so this issue isn't in the cirq.decompose but the in _decompose_ method of the ControlledGate class

>> op = cirq.CZPowGate(exponent=-4.611982500482719, global_shift=-2.8938230769473154).on(cirq.LineQubit(3), cirq.LineQubit(4)).controlled_by(cirq.LineQubit(1))
>> c = cirq.Circuit(op._decompose_())
>> print(cirq.equal_up_to_global_phase(cirq.unitary(c), cirq.unitary(op)))
False
NoureldinYosri commented 5 months ago

the root cause is the hardcoded decomposition for controlled gate when the subgate is CZPowGate, which turns a CZPowGate into a Controlled - ZPowGate. this transformation misses at least a phase which becomes problematic for the controlled version

https://github.com/quantumlib/Cirq/blob/decf16ddf5b14b0fe46c5dbbb299c53c89d49945/cirq-core/cirq/ops/controlled_gate.py#L178-L202


Suggested fix: Add a correction operation on line https://github.com/quantumlib/Cirq/blob/decf16ddf5b14b0fe46c5dbbb299c53c89d49945/cirq-core/cirq/ops/controlled_gate.py#L200-L202

tanujkhattar commented 5 months ago

We should change the if isinstance(self.sub_gate, common_gates.CZPowGate): check to also check that self.sub_gate.global_shift == 0 and only then delegate to cirq.Z.controlled()

In hindsight, we probably shouldn't have added so many special cases to the decomposition of Controlled.