Skip to content

TwoQubitGateTabulation returns an actual_gate inconsistent with its local_unitaries up to a global phase #8362

Description

@WindOctober

Describe the issue

TwoQubitGateTabulation.compile_two_qubit_gate returns an actual_gate that differs by a global phase from the product reconstructed from its local_unitaries and base gate. The result documentation defines actual_gate as that product, so the two representations should agree.

This is a return-value consistency issue, not a claim that success=True requires preservation of the target's global phase. The difference does not affect ordinary circuit execution, but can become observable if the compiled block is subsequently placed under coherent quantum control.

Explain how to reproduce the bug or problem

import cirq
import numpy as np

base = cirq.unitary(cirq.CZ)
tabulation = cirq.two_qubit_gate_product_tabulation(base, 0.1, random_state=42)
target = np.exp(1j * np.pi / 4) * base
result = tabulation.compile_two_qubit_gate(target)

built = np.kron(*result.local_unitaries[0])
for pair in result.local_unitaries[1:]:
    built = np.kron(*pair) @ base @ built

print("success:", result.success)
print("built matches actual_gate:", np.allclose(built, result.actual_gate))
print("built matches target up to phase:",
      cirq.linalg.allclose_up_to_global_phase(built, target))

# Illustrate the phase difference using coherent control and interference.
q0, q1, q2 = cirq.LineQubit.range(3)
for label, block in [("target", target), ("built", built)]:
    circuit = cirq.Circuit(
        cirq.X(q1), cirq.X(q2), cirq.H(q0),
        cirq.MatrixGate(block).controlled().on(q0, q1, q2),
        cirq.H(q0),
    )
    state = cirq.Simulator(dtype=np.complex128).simulate(
        circuit, qubit_order=[q0, q1, q2]
    ).final_state_vector
    print(f"{label} P(control=0): {np.sum(np.abs(state[:4])**2):.6f}")

Observed output:

success: True
built matches actual_gate: False
built matches target up to phase: True
target P(control=0): 0.146447
built P(control=0): 0.500000

The coherent-control example illustrates a consequence of reusing the compiled block; it does not demonstrate a failure in Cirq's standard compilation pipelines.

Tell us the version of Cirq where this happens

Cirq 1.7.0, Python 3.11.4, Linux.

Root cause

In _outer_locals_for_unitary, the reconstructed matrix is multiplied by np.conj(target_decomp.global_phase), but the returned local gates are unchanged. Keep actual_gate consistent with their product, or explicitly document the differing phase conventions.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bug-reportSomething doesn't seem to work.skill/experiencedNeeds experience with Cirq developmenttriage/discussNeeds decision / discussion, bring these up during Cirq Cynque

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions