Repository: quantumlib/Cirq

Issue:
Improve error message if on_each gets a list
When you do `cirq.H.on_each([q0, q1])` instead of the unpacked version  `cirq.H.on_each(q0, q1)` for some qubits `q0` and `q1`, the error message you get is **Gate was called with type different than Qid**. 

Maybe either flatten (because most of the time you'll have your qubits in a list or a list of lists), or give a more descriptive error message.


Discussion:
I can take this one.
What would you expect of a return type? Do you want to have the list of lists preserved, if the input is a list of lists? Or do you want to keep the current return type, which is a flattened list?
For me the current return type is the most obvious solution. I don't know any good enough reason to change the return type and potentially risk incompatibility with other existing patterns. The primary purpose of flattening the list is that we can write the intuitive `yield cirq.H.on_each(qubits)` in a generator, no matter if qubits is a list of LineQubits or a list of lists of GridQubits (two cases I have frequently). That's easy to read and does what everyone would intuitively guess.
The usecase I had in mind is method chaining, where you can repeatedly apply gate operations to a list of lists of GridQubits, while keeping the topology intact. But that might not be that obvious, indeed.

I will keep the current return type of a flattened list. Thanks for the reply.

Resolving patch:
diff --git a/cirq/ops/gate_features.py b/cirq/ops/gate_features.py
--- a/cirq/ops/gate_features.py
+++ b/cirq/ops/gate_features.py
@@ -18,8 +18,10 @@
 """
 
 import abc
+import collections
+from typing import Union, Iterable, Any, List
 
-from cirq.ops import op_tree, raw_types
+from cirq.ops import raw_types
 
 
 class InterchangeableQubitsGate(metaclass=abc.ABCMeta):
@@ -35,7 +37,8 @@ class SingleQubitGate(raw_types.Gate, metaclass=abc.ABCMeta):
     def num_qubits(self) -> int:
         return 1
 
-    def on_each(self, *targets: raw_types.Qid) -> op_tree.OP_TREE:
+    def on_each(self, *targets: Union[raw_types.Qid, Iterable[Any]]
+               ) -> List[raw_types.Operation]:
         """Returns a list of operations apply this gate to each of the targets.
 
         Args:
@@ -45,9 +48,20 @@ def on_each(self, *targets: raw_types.Qid) -> op_tree.OP_TREE:
             Operations applying this gate to the target qubits.
 
         Raises:
-            ValueError if targets are not instances of Qid.
+            ValueError if targets are not instances of Qid or List[Qid].
         """
-        return [self.on(target) for target in targets]
+        operations = []  # type: List[raw_types.Operation]
+        for target in targets:
+            if isinstance(target,
+                          collections.Iterable) and not isinstance(target, str):
+                operations.extend(self.on_each(*target))
+            elif isinstance(target, raw_types.Qid):
+                operations.append(self.on(target))
+            else:
+                raise ValueError(
+                    'Gate was called with type different than Qid. Type: {}'.
+                    format(type(target)))
+        return operations
 
 
 class TwoQubitGate(raw_types.Gate, metaclass=abc.ABCMeta):


Test patch:
diff --git a/cirq/ops/gate_features_test.py b/cirq/ops/gate_features_test.py
--- a/cirq/ops/gate_features_test.py
+++ b/cirq/ops/gate_features_test.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from collections import Iterator
 import pytest
 
 import cirq
@@ -44,8 +45,13 @@ def matrix(self):
     test_qubits = [cirq.NamedQubit(str(i)) for i in range(3)]
 
     _ = g.on_each(*test_qubits)
+    _ = g.on_each(test_qubits)
+
+    test_non_qubits = [str(i) for i in range(3)]
+    with pytest.raises(ValueError):
+        _ = g.on_each(*test_non_qubits)
     with pytest.raises(ValueError):
-        _ = g.on_each(test_qubits)
+        _ = g.on_each(test_non_qubits)
 
 
 def test_single_qubit_validates_on():
@@ -139,11 +145,26 @@ class CustomGate(cirq.SingleQubitGate):
     assert c.on_each(a, b) == [c(a), c(b)]
     assert c.on_each(b, a) == [c(b), c(a)]
 
-    with pytest.raises(ValueError):
-        c.on_each([])
+    assert c.on_each([]) == []
+    assert c.on_each([a]) == [c(a)]
+    assert c.on_each([a, b]) == [c(a), c(b)]
+    assert c.on_each([b, a]) == [c(b), c(a)]
+    assert c.on_each([a, [b, a], b]) == [c(a), c(b), c(a), c(b)]
 
     with pytest.raises(ValueError):
-        c.on_each([a])
+        c.on_each('abcd')
+    with pytest.raises(ValueError):
+        c.on_each(['abcd'])
+    with pytest.raises(ValueError):
+        c.on_each([a, 'abcd'])
+
+    def iterator(qubits):
+        for i in range(len(qubits)):
+            yield qubits[i]
+
+    qubit_iterator = iterator([a, b, a, b])
+    assert isinstance(qubit_iterator, Iterator)
+    assert c.on_each(qubit_iterator) == [c(a), c(b), c(a), c(b)]
 
 
 def test_qasm_output_args_validate():