Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize_for_target_gateset does not return equivalent circuit #6517

Open
jiannanWang opened this issue Mar 21, 2024 · 4 comments
Open

optimize_for_target_gateset does not return equivalent circuit #6517

jiannanWang opened this issue Mar 21, 2024 · 4 comments
Labels
kind/bug-report Something doesn't seem to work. triage/accepted there is consensus amongst maintainers that this is a real bug or a reasonable feature to add

Comments

@jiannanWang
Copy link

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
@jiannanWang jiannanWang added the kind/bug-report Something doesn't seem to work. label Mar 21, 2024
@NoureldinYosri
Copy link
Collaborator

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 NoureldinYosri added the triage/discuss Needs decision / discussion, bring these up during Cirq Cynque label Mar 21, 2024
@NoureldinYosri
Copy link
Collaborator

NoureldinYosri commented Mar 21, 2024

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
Copy link
Collaborator

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

if isinstance(self.sub_gate, common_gates.CZPowGate):
z_sub_gate = common_gates.ZPowGate(
exponent=self.sub_gate.exponent, global_shift=self.sub_gate.global_shift
)
num_controls = self.num_controls() + 1
control_values = self.control_values & cv.ProductOfSums(((1,),))
control_qid_shape = self.control_qid_shape + (2,)
controlled_z = (
z_sub_gate.controlled(
num_controls=num_controls,
control_values=control_values,
control_qid_shape=control_qid_shape,
)
if protocols.is_parameterized(self)
else ControlledGate(
z_sub_gate,
num_controls=num_controls,
control_values=control_values,
control_qid_shape=control_qid_shape,
)
)
if self != controlled_z:
return protocols.decompose_once_with_qubits(
controlled_z, qubits, NotImplemented, context=context
)


Suggested fix: Add a correction operation on line

return protocols.decompose_once_with_qubits(
controlled_z, qubits, NotImplemented, context=context
)

@tanujkhattar
Copy link
Collaborator

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.

@pavoljuhas pavoljuhas added triage/accepted there is consensus amongst maintainers that this is a real bug or a reasonable feature to add and removed triage/discuss Needs decision / discussion, bring these up during Cirq Cynque labels Mar 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug-report Something doesn't seem to work. triage/accepted there is consensus amongst maintainers that this is a real bug or a reasonable feature to add
Projects
None yet
Development

No branches or pull requests

4 participants