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

`cirq.approx_eq` is inconsistent for sets #6376

Open richrines1 opened 9 months ago

richrines1 commented 9 months ago

Description of the issue

cirq.approx_eq(val1, val2) delegates to _approx_eq_iterables if the two values are iterable, which in turn compares the elements of each value in order. This is a problem if the values are sets, which are not guaranteed to be iterated in a fixed order

a downstream effect of this is that cirq.approx_eq is inconsistent for operations with interchangeable qubits, due to the comparison of the frozensets returned by GateOperation._group_interchangeable_qubits()

How to reproduce the issue

unfortunately this is difficult to reproduce consistently because the iteration order of sets is inconsistent. But some variation of the following will probably fail:

for n in range(10, 20):
    assert cirq.approx_eq(frozenset(cirq.LineQubit.range(n)), frozenset({*cirq.LineQubit.range(n)}))

Cirq version

1.4.0.dev20231205195308
pavoljuhas commented 8 months ago

csynkque discussion - options

(a) sort when comparing two sets (b) raise exception on comparison of two unordered sequences

Needs investigation what are impacts of these choices on the rest of cirq functionality; would there be backwards incompatible change?

richrines1 commented 8 months ago

(b) raise exception on comparison of two unordered sequences

wouldn't this break approximate comparison of operations constructed from InterchangeableQubitGates (because their _value_equality_approximate_values_ contains a frozenset)?

the downstream effect of this issue (which is perhaps more important than the example i posted) is that cirq.approx_eq is also inconsistent for these operations, e.g. when i run the following:

for i in range(10):
    op1 = cirq.CZ(cirq.q(i), cirq.q(10))
    op2 = cirq.CZ(cirq.q(10), cirq.q(i))
    if cirq.approx_eq(op1, op2):
        print(f"{op1} == {op2}")
    else:
        print(f"{op1} != {op2}")

i get

CZ(q(0), q(10)) == CZ(q(10), q(0))
CZ(q(1), q(10)) == CZ(q(10), q(1))
CZ(q(2), q(10)) == CZ(q(10), q(2))
CZ(q(3), q(10)) == CZ(q(10), q(3))
CZ(q(4), q(10)) != CZ(q(10), q(4))
CZ(q(5), q(10)) == CZ(q(10), q(5))
CZ(q(6), q(10)) == CZ(q(10), q(6))
CZ(q(7), q(10)) != CZ(q(10), q(7))
CZ(q(8), q(10)) == CZ(q(10), q(8))
CZ(q(9), q(10)) == CZ(q(10), q(9))

it does seem like sorting makes sense though, or even just falling back on exact (==) equality instead of _approx_eq_iterables when comparing unordered sequences?

shef4 commented 8 months ago

I'm interested in looking into this issue. Will start getting more context on what's wrong.