Repository: quantumlib/Cirq

Issue:
Make ControlledGate work with gates that only provide a decomposition
The following should work:

```
import cirq


class G(cirq.TwoQubitGate):
    def _decompose_(self, qubits):
        a, b = qubits
        yield cirq.X(a)**0.5
        yield cirq.H(b)
        yield cirq.CZ(a, b)


cg = cirq.ControlledGate(G())
x, y, z = cirq.LineQubit.range(3)
c = cirq.Circuit.from_ops(cg(x, y, z))
print(c.to_unitary_matrix())
```

but currently it raises an exception:

```
Operation without a known matrix or decomposition: cirq.ControlledGate(sub_gate=[...G...].on(cirq.LineQubit(0), cirq.LineQubit(1), cirq.LineQubit(2))
```

because `ControlledGate` doesn't have a `_decompose_` method. This issue is to add that method.


Discussion:
This is what i have in  `ControlledGate()`

```
 def _decompose_(self, qubits):
        yield self.sub_gate._decompose_(qubits[1:])
```
But it's not working:
```
class G(cirq.SingleQubitGate):
    def _decompose_(self, qubits):
        a = qubits
        yield cirq.X(a[0])

eq = cirq.testing.EqualsTester()
eq.add_equality_group(cirq.ControlledGate(G()), cirq.CNOT) # Nope
```
What am i missing?
@vtomole - my guess is for your question:
1) the gate you are trying to compare to is not a `CNOT` - it's a "CG" - a controlled `G` gate, where `G` is defined as some generic gate that decomposes to an X gate on the first qubit - but it is not an `X` gate
2) I'm not entirely sure yet, but unless there is some deeper definition of equality is at play - the CNOT gate is not implemented as a `cirq.ControlledGate(cirq.X)` so your equality won't work even like this `    eq.add_equality_group(cirq.ControlledGate(cirq.X), cirq.CNOT)  # Still Nope` - it is `cirq.CNotPowGate()`. 

Besides, unless I'm missing something and this is trivial - it seems to me that there is no easy way to just simply reuse `_decompose_` of the subgate, because in general decomposing a controlled gate would require somehow to understand the structure of the subgate and find/come up with a generic algorithm to create a set of gates that result in an equivalent unitary matrix to `Id (+) Sub` (`linalg.block_diag(np.eye(sub_matrix.shape[0]), sub_matrix)`). 
After a quick search I found [Barenko et al](https://arxiv.org/pdf/quant-ph/9503016.pdf) that is OK for some cases but not in general and [this blogpost](https://qigcbpf.wordpress.com/2013/08/13/how-to-implement-a-controlled-u-gate/) which pointed to [Zhou et al](https://arxiv.org/pdf/1006.2670.pdf) which would be promising - but that uses this (odd?) `X_a` operation that uses an "extended Hilbert space" to swap things out...
It looks like figuring out exactly the decomposition contract would help here too: #930
After talking to @Strilanc I did miss something trivial - the sequence of controlled components controlled by the same qubit does equal to the controlled gate. Also learned the context for this issue is [this stackexchange question](https://quantumcomputing.stackexchange.com/questions/5145/cirq-controlledgate-from-sub-circuit-generator).
I'm happy to work on this. 
@balopat  Go for it :)

Resolving patch:
diff --git a/cirq/ops/controlled_gate.py b/cirq/ops/controlled_gate.py
--- a/cirq/ops/controlled_gate.py
+++ b/cirq/ops/controlled_gate.py
@@ -115,7 +115,18 @@ def _trace_distance_bound_(self):
     def _circuit_diagram_info_(self,
                                args: protocols.CircuitDiagramInfoArgs
                                ) -> protocols.CircuitDiagramInfo:
-        sub_info = protocols.circuit_diagram_info(self.sub_gate, args, None)
+        sub_args = protocols.CircuitDiagramInfoArgs(
+            known_qubit_count=(args.known_qubit_count - 1
+                               if args.known_qubit_count is not None else None),
+            known_qubits=(args.known_qubits[1:]
+                          if args.known_qubits is not None else None),
+            use_unicode_characters=args.use_unicode_characters,
+            precision=args.precision,
+            qubit_map=args.qubit_map
+        )
+        sub_info = protocols.circuit_diagram_info(self.sub_gate,
+                                                  sub_args,
+                                                  None)
         if sub_info is None:
             return NotImplemented
         return protocols.CircuitDiagramInfo(


Test patch:
diff --git a/cirq/ops/controlled_gate_test.py b/cirq/ops/controlled_gate_test.py
--- a/cirq/ops/controlled_gate_test.py
+++ b/cirq/ops/controlled_gate_test.py
@@ -226,6 +226,60 @@ class UndiagrammableGate(cirq.SingleQubitGate):
                                      default=None) is None
 
 
+# A contrived multiqubit Hadamard gate that asserts the consistency of
+# the passed in Args and puts an H on all qubits
+# displays them as 'H(qubit)' on the wire
+class MultiH(cirq.MultiQubitGate):
+
+    def _circuit_diagram_info_(self,
+                               args: cirq.CircuitDiagramInfoArgs
+                               ) -> cirq.CircuitDiagramInfo:
+        assert args.known_qubit_count is not None
+        assert args.known_qubits is not None
+
+        return cirq.CircuitDiagramInfo(
+            wire_symbols=tuple('H({})'.format(q) for q in args.known_qubits),
+            connected=True
+        )
+
+
+def test_circuit_diagram():
+    qubits = cirq.LineQubit.range(3)
+    c = cirq.Circuit()
+    c.append(cirq.ControlledGate(MultiH(2))(*qubits))
+
+    cirq.testing.assert_has_diagram(c, """
+0: ───@──────
+      │
+1: ───H(1)───
+      │
+2: ───H(2)───
+""")
+
+
+class MockGate(cirq.TwoQubitGate):
+
+    def _circuit_diagram_info_(self,
+                               args: cirq.CircuitDiagramInfoArgs
+                               ) -> cirq.CircuitDiagramInfo:
+        self.captured_diagram_args = args
+        return cirq.CircuitDiagramInfo(wire_symbols=tuple(['MOCK']), exponent=1,
+                                       connected=True)
+
+
+def test_uninformed_circuit_diagram_info():
+    qbits = cirq.LineQubit.range(3)
+    mock_gate = MockGate()
+    cgate = cirq.ControlledGate(mock_gate)(*qbits)
+
+    args = cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT
+
+    assert (cirq.circuit_diagram_info(cgate, args) ==
+            cirq.CircuitDiagramInfo(wire_symbols=('@', 'MOCK'), exponent=1,
+                                    connected=True))
+    assert mock_gate.captured_diagram_args == args
+
+
 def test_bounded_effect():
     assert cirq.trace_distance_bound(CY**0.001) < 0.01