Repository: quantumlib/Cirq

Issue:
Circuit equality with moment order
When I run the following code the assertion fails.
```
d1 = cirq.GridQubit(4,4)
d2 = cirq.GridQubit(5,5)
m1 = cirq.GridQubit(5,4)
m2 =cirq.GridQubit(4,5)

circuit= cirq.Circuit()
circuit.append(cirq.I(m1))
circuit.append(cirq.I(m2))
circuit.append(cirq.Z(d1).controlled_by(m2))
circuit.append(cirq.X(d1).controlled_by(m1))#
circuit.append(cirq.Z(d2).controlled_by(m2))#
circuit.append(cirq.X(d2).controlled_by(m1))
circuit.append(cirq.Circuit.from_ops(cirq.measure(m1), cirq.measure(m2)))

should_be = cirq.Circuit()
should_be.append(cirq.I(m1))
should_be.append(cirq.I(m2))
should_be.append(cirq.X(d1).controlled_by(m1))
should_be.append(cirq.Z(d1).controlled_by(m2))#
should_be.append(cirq.X(d2).controlled_by(m1))#
should_be.append(cirq.Z(d2).controlled_by(m2))
should_be.append(cirq.Circuit.from_ops(cirq.measure(m1), cirq.measure(m2)))

assert circuit == should_be
```
These circuits are the same just that the order of the elements in the moment with the # is different.
```
(4, 4): ───────X───Z───────────
               │   │
(4, 5): ───I───┼───@───@───M───
               │       │
(5, 4): ───I───@───@───┼───M───
                   │   │
(5, 5): ───────────X───Z───────


(4, 4): ───────X───Z───────────
               │   │
(4, 5): ───I───┼───@───@───M───
               │       │
(5, 4): ───I───@───@───┼───M───
                   │   │
(5, 5): ───────────X───Z───────
```
I think this is because the moment is stored as a tuple of operations so the order matters.


Discussion:

Resolving patch:
diff --git a/cirq/ops/moment.py b/cirq/ops/moment.py
--- a/cirq/ops/moment.py
+++ b/cirq/ops/moment.py
@@ -112,19 +112,27 @@ def __bool__(self):
     def __eq__(self, other):
         if not isinstance(other, type(self)):
             return NotImplemented
-        return self.operations == other.operations
+
+        return (sorted(self.operations, key=lambda op: op.qubits) == sorted(
+            other.operations, key=lambda op: op.qubits))
 
     def _approx_eq_(self, other: Any, atol: Union[int, float]) -> bool:
         """See `cirq.protocols.SupportsApproximateEquality`."""
         if not isinstance(other, type(self)):
             return NotImplemented
-        return protocols.approx_eq(self.operations, other.operations, atol=atol)
+
+        return protocols.approx_eq(sorted(self.operations,
+                                          key=lambda op: op.qubits),
+                                   sorted(other.operations,
+                                          key=lambda op: op.qubits),
+                                   atol=atol)
 
     def __ne__(self, other):
         return not self == other
 
     def __hash__(self):
-        return hash((Moment, self.operations))
+        return hash(
+            (Moment, tuple(sorted(self.operations, key=lambda op: op.qubits))))
 
     def __iter__(self):
         return iter(self.operations)


Test patch:
diff --git a/cirq/ops/moment_test.py b/cirq/ops/moment_test.py
--- a/cirq/ops/moment_test.py
+++ b/cirq/ops/moment_test.py
@@ -59,9 +59,9 @@ def test_equality():
     eq.add_equality_group(Moment([cirq.X(b)]))
     eq.add_equality_group(Moment([cirq.Y(a)]))
 
-    # Equality depends on order.
-    eq.add_equality_group(Moment([cirq.X(a), cirq.X(b)]))
-    eq.add_equality_group(Moment([cirq.X(b), cirq.X(a)]))
+    # Equality doesn't depend on order.
+    eq.add_equality_group(Moment([cirq.X(a), cirq.X(b)]),
+                          Moment([cirq.X(a), cirq.X(b)]))
 
     # Two qubit gates.
     eq.make_equality_group(lambda: Moment([cirq.CZ(c, d)]))