Repository: quantumlib/Cirq

Issue:
multiqubit controlled gate decompositions
It would be a nice feature if Cirq would support the multiqubit controlled gate decompositions presented in https://arxiv.org/abs/quant-ph/9503016


Discussion:
I can implement this.
I see this as a function which takes unitary matrix and returns a Circuit.

@viathor , can I work on this (not sure whether Cirq has this already)? What would be best place in repository to put such algorithm (maybe cirq/protocols)?
This would be in `cirq/optimizers`.
Hi. I have several design questions.

1. Result of algorithm.
I imagine that it will be a sequence of gates. In paper result consists of general single-qubit gates and CNOT gates. In Cirq that can be represented as [cirq.MatrixGate](https://cirq.readthedocs.io/en/stable/generated/cirq.MatrixGate.html) and cirq.CNOT.
But maybe it's better to go further and decompose them into gates with 0 or 1 parameter, i.e. Ry, Rz, H, and [ZPowGate](https://cirq.readthedocs.io/en/stable/generated/cirq.ZPowGate.html)?

2. CCNOT gates.
Algorithm produces a lot of intermediary CCNOT gates, and then each of them is replaced with O(1) 1-qubit and CNOT gates. If we allow CCNOT gates in ouput, we can greatly reduce size of output.
Should I allow CCNOT gates in output of my algorithm? Would it be useful to have a parameter specifying this?
For now I will go all the way to CNOT gates and won't allow CCNOTs in output.

3. Special unitary matrices.
If input is special unitary (SU) matrix, it can be decomposed into O(n) gates, while in general case it is decomposed into O(n^2) gates. I will implement both algorithms, but there are 2 ways to express this in API:
a) Define two public functions, one for SU case and one for general case;
b) Have one function, which will use more effective algorithm if input matrix is special unitary.

4. Special cases (Chapter 5.2. in paper).
If input matrix has some special form, number of result gates can be reduced. Should I implement this? (I think yes.)

5. Approximate simulation (chapter 7.3. in paper).
Basically it's terminating recursion which reduces number of gates in output at cost of accuracy.
I'm not going to include it in initial implementation, but would it be useful?

My draft implementation is [here](https://github.com/fedimser/quant_comp/blob/master/Multi-controlled%20gate%20decomposition.ipynb) and when it's ready, I will convert it to a pull request.

@vtomole @viathor @alexandrupaler 
1. What would be the simplest for the moment? MatrixGate?
2. Intermediate Toffoli decomposition would be great to have. Actually that was the main reason I proposed this feature.
3. I am not sure what to answer.
4. Do not think is needed for the first version.
5. It would be useful in the sense of "reducing T count" when decomposing further to Toffoli

Resolving patch:
diff --git a/cirq/__init__.py b/cirq/__init__.py
--- a/cirq/__init__.py
+++ b/cirq/__init__.py
@@ -278,6 +278,8 @@
 
 from cirq.optimizers import (
     ConvertToCzAndSingleGates,
+    decompose_multi_controlled_x,
+    decompose_multi_controlled_rotation,
     decompose_two_qubit_interaction_into_four_fsim_gates_via_b,
     DropEmptyMoments,
     DropNegligible,
diff --git a/cirq/optimizers/__init__.py b/cirq/optimizers/__init__.py
--- a/cirq/optimizers/__init__.py
+++ b/cirq/optimizers/__init__.py
@@ -14,6 +14,9 @@
 
 """Circuit transformation utilities."""
 
+from cirq.optimizers.controlled_gate_decomposition import (
+    decompose_multi_controlled_x, decompose_multi_controlled_rotation)
+
 from cirq.optimizers.drop_empty_moments import (
     DropEmptyMoments,)
 
diff --git a/cirq/optimizers/controlled_gate_decomposition.py b/cirq/optimizers/controlled_gate_decomposition.py
new file mode 100644
--- /dev/null
+++ b/cirq/optimizers/controlled_gate_decomposition.py
@@ -0,0 +1,254 @@
+# Copyright 2020 The Cirq Developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import List, Tuple, TYPE_CHECKING
+
+import numpy as np
+
+from cirq import ops
+from cirq.linalg import is_unitary, is_special_unitary, map_eigenvalues
+from cirq.protocols import unitary
+
+if TYPE_CHECKING:
+    import cirq
+
+
+def _unitary_power(matrix: np.ndarray, power: float) -> np.ndarray:
+    return map_eigenvalues(matrix, lambda e: e**power)
+
+
+def _is_identity(matrix):
+    """Checks whether M is identity."""
+    return np.allclose(matrix, np.eye(matrix.shape[0]))
+
+
+def _flatten(x):
+    return sum(x, [])
+
+
+def _decompose_abc(matrix: np.ndarray
+                  ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, float]:
+    """Decomposes 2x2 unitary matrix.
+
+    Returns 2x2 special unitary matrices A, B, C and phase delta, such that:
+    * ABC = I.
+    * AXBXC * exp(1j*delta) = matrix.
+
+    See [1], chapter 4.
+    """
+    assert matrix.shape == (2, 2)
+    delta = np.angle(np.linalg.det(matrix)) * 0.5
+    alpha = np.angle(matrix[0, 0]) + np.angle(matrix[0, 1]) - 2 * delta
+    beta = np.angle(matrix[0, 0]) - np.angle(matrix[0, 1])
+
+    m00_abs = np.abs(matrix[0, 0])
+    if np.abs(m00_abs - 1.0) < 1e-9:
+        m00_abs = 1
+    theta = 2 * np.arccos(m00_abs)
+
+    a = unitary(ops.rz(-alpha)) @ unitary(ops.ry(-theta / 2))
+    b = unitary(ops.ry(theta / 2)) @ unitary(ops.rz((alpha + beta) / 2))
+    c = unitary(ops.rz((alpha - beta) / 2))
+
+    x = unitary(ops.X)
+    assert np.allclose(a @ b @ c, np.eye(2), atol=1e-2)
+    assert np.allclose((a @ x @ b @ x @ c) * np.exp(1j * delta),
+                       matrix,
+                       atol=1e-2)
+
+    return a, b, c, delta
+
+
+def _decompose_single_ctrl(matrix: np.ndarray, control: 'cirq.Qid',
+                           target: 'cirq.Qid') -> List['cirq.Operation']:
+    """Decomposes controlled gate with one control.
+
+    See [1], chapter 5.1.
+    """
+    a, b, c, delta = _decompose_abc(matrix)
+
+    result = [
+        ops.ZPowGate(exponent=delta / np.pi).on(control),
+        ops.MatrixGate(c).on(target),
+        ops.CNOT.on(control, target),
+        ops.MatrixGate(b).on(target),
+        ops.CNOT.on(control, target),
+        ops.MatrixGate(a).on(target),
+    ]
+
+    # Remove no-ops.
+    result = [g for g in result if not _is_identity(unitary(g))]
+
+    return result
+
+
+def _ccnot_congruent(c0: 'cirq.Qid', c1: 'cirq.Qid',
+                     target: 'cirq.Qid') -> List['cirq.Operation']:
+    """Implements 3-qubit gate 'congruent' to CCNOT.
+
+    Returns sequence of operations which is equivalent to applying
+    CCNOT(c0, c1, target) and multiplying phase of |101> sate by -1.
+    See lemma 6.2 in [1]."""
+    return [
+        ops.ry(-np.pi / 4).on(target),
+        ops.CNOT(c1, target),
+        ops.ry(-np.pi / 4).on(target),
+        ops.CNOT(c0, target),
+        ops.ry(np.pi / 4).on(target),
+        ops.CNOT(c1, target),
+        ops.ry(np.pi / 4).on(target),
+    ]
+
+
+def decompose_multi_controlled_x(controls: List['cirq.Qid'], target: 'cirq.Qid',
+                                 free_qubits: List['cirq.Qid']
+                                ) -> List['cirq.Operation']:
+    """Implements action of multi-controlled Pauli X gate.
+
+    Result is guaranteed to consist exclusively of 1-qubit, CNOT and CCNOT
+    gates.
+    If `free_qubits` has at least 1 element, result has lengts
+    O(len(controls)).
+
+    Args:
+        controls - control qubits.
+        targets - target qubits.
+        free_qubits - qubits which are neither controlled nor target. Can be
+            modified by algorithm, but will end up in their initial state.
+    """
+    m = len(controls)
+    if m == 0:
+        return [ops.X.on(target)]
+    elif m == 1:
+        return [ops.CNOT.on(controls[0], target)]
+    elif m == 2:
+        return [ops.CCNOT.on(controls[0], controls[1], target)]
+
+    m = len(controls)
+    n = m + 1 + len(free_qubits)
+    if (n >= 2 * m - 1) and (m >= 3):
+        # See [1], Lemma 7.2.
+        gates1 = [
+            _ccnot_congruent(controls[m - 2 - i], free_qubits[m - 4 - i],
+                             free_qubits[m - 3 - i]) for i in range(m - 3)
+        ]
+        gates2 = _ccnot_congruent(controls[0], controls[1], free_qubits[0])
+        gates3 = _flatten(gates1) + gates2 + _flatten(gates1[::-1])
+        first_ccnot = ops.CCNOT(controls[m - 1], free_qubits[m - 3], target)
+        return [first_ccnot, *gates3, first_ccnot, *gates3]
+    elif len(free_qubits) >= 1:
+        # See [1], Lemma 7.3.
+        m1 = n // 2
+        free1 = controls[m1:] + [target] + free_qubits[1:]
+        ctrl1 = controls[:m1]
+        part1 = decompose_multi_controlled_x(ctrl1, free_qubits[0], free1)
+        free2 = controls[:m1] + free_qubits[1:]
+        ctrl2 = controls[m1:] + [free_qubits[0]]
+        part2 = decompose_multi_controlled_x(ctrl2, target, free2)
+        return [*part1, *part2, *part1, *part2]
+    else:
+        # No free qubits - must use general algorithm.
+        # This will never happen if called from main algorithm and is added
+        # only for completeness.
+        return decompose_multi_controlled_rotation(unitary(ops.X), controls,
+                                                   target)
+
+
+def _decompose_su(matrix: np.ndarray, controls: List['cirq.Qid'],
+                  target: 'cirq.Qid') -> List['cirq.Operation']:
+    """Decomposes controlled special unitary gate into elementary gates.
+
+    Result has O(len(controls)) operations.
+    See [1], lemma 7.9.
+    """
+    assert matrix.shape == (2, 2)
+    assert is_special_unitary(matrix)
+    assert len(controls) >= 1
+
+    a, b, c, _ = _decompose_abc(matrix)
+
+    cnots = decompose_multi_controlled_x(controls[:-1], target, [controls[-1]])
+    return [
+        *_decompose_single_ctrl(c, controls[-1], target), *cnots,
+        *_decompose_single_ctrl(b, controls[-1], target), *cnots,
+        *_decompose_single_ctrl(a, controls[-1], target)
+    ]
+
+
+def _decompose_recursive(matrix: np.ndarray, power: float,
+                         controls: List['cirq.Qid'], target: 'cirq.Qid',
+                         free_qubits: List['cirq.Qid']
+                        ) -> List['cirq.Operation']:
+    """Decomposes controlled unitary gate into elementary gates.
+
+    Result has O(len(controls)^2) operations.
+    See [1], lemma 7.5.
+    """
+    if len(controls) == 1:
+        return _decompose_single_ctrl(_unitary_power(matrix, power),
+                                      controls[0], target)
+
+    cnots = decompose_multi_controlled_x(controls[:-1], controls[-1],
+                                         free_qubits + [target])
+    return [
+        *_decompose_single_ctrl(_unitary_power(matrix, 0.5 * power),
+                                controls[-1], target), *cnots,
+        *_decompose_single_ctrl(_unitary_power(matrix, -0.5 * power),
+                                controls[-1], target), *cnots,
+        *_decompose_recursive(matrix, 0.5 * power, controls[:-1], target,
+                              [controls[-1]] + free_qubits)
+    ]
+
+
+def decompose_multi_controlled_rotation(matrix: np.ndarray,
+                                        controls: List['cirq.Qid'],
+                                        target: 'cirq.Qid'
+                                       ) -> List['cirq.Operation']:
+    """Implements action of multi-controlled unitary gate.
+
+    Returns a sequence of operations, which is equivalent to applying
+    single-qubit gate with matrix `matrix` on `target`, controlled by
+    `controls`.
+
+    Result is guaranteed to consist exclusively of 1-qubit, CNOT and CCNOT
+    gates.
+
+    If matrix is special unitary, result has length `O(len(controls))`.
+    Otherwise result has length `O(len(controls)**2)`.
+
+    References:
+        [1] Barenco, Bennett et al.
+            Elementary gates for quantum computation. 1995.
+            https://arxiv.org/pdf/quant-ph/9503016.pdf
+
+    Args:
+        matrix - 2x2 numpy unitary matrix (of real or complex dtype).
+        controls - control qubits.
+        targets - target qubits.
+
+    Returns:
+        A list of operations which, applied in a sequence, are equivalent to
+        applying `MatrixGate(matrix).on(target).controlled_by(*controls)`.
+    """
+    assert is_unitary(matrix)
+    assert matrix.shape == (2, 2)
+
+    if len(controls) == 0:
+        return [ops.MatrixGate(matrix).on(target)]
+    elif len(controls) == 1:
+        return _decompose_single_ctrl(matrix, controls[0], target)
+    elif is_special_unitary(matrix):
+        return _decompose_su(matrix, controls, target)
+    else:
+        return _decompose_recursive(matrix, 1.0, controls, target, [])


Test patch:
diff --git a/cirq/optimizers/controlled_gate_decomposition_test.py b/cirq/optimizers/controlled_gate_decomposition_test.py
new file mode 100644
--- /dev/null
+++ b/cirq/optimizers/controlled_gate_decomposition_test.py
@@ -0,0 +1,141 @@
+# Copyright 2020 The Cirq Developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import numpy as np
+import scipy.stats
+
+import cirq
+
+
+def test_decompose_x():
+    """Verifies correctness of multi-controlled X decomposition."""
+    for total_qubits_count in range(1, 8):
+        qubits = cirq.LineQubit.range(total_qubits_count)
+        for controls_count in range(0, total_qubits_count):
+            gates = cirq.decompose_multi_controlled_x(
+                qubits[:controls_count], qubits[controls_count],
+                qubits[controls_count + 1:])
+
+            circuit1 = cirq.Circuit([cirq.I.on(q) for q in qubits])
+            circuit1.append(gates)
+            result_matrix = circuit1.unitary()
+
+            circuit2 = cirq.Circuit([cirq.I.on(q) for q in qubits])
+            circuit2 += cirq.ControlledGate(
+                cirq.X,
+                num_controls=controls_count).on(*qubits[0:controls_count + 1])
+            expected_matrix = circuit2.unitary()
+            assert np.allclose(expected_matrix, result_matrix)
+
+
+def _random_unitary():
+    return scipy.stats.unitary_group.rvs(2)
+
+
+def _random_special_unitary():
+    U = _random_unitary()
+    return U / np.sqrt(np.linalg.det(U))
+
+
+def _count_operations(operations):
+    """Counts single-qubit, CNOT and CCNOT gates.
+
+    Also validates that there are no other gates."""
+    count_2x2 = 0
+    count_cnot = 0
+    count_ccnot = 0
+    for operation in operations:
+        u = cirq.unitary(operation)
+        if u.shape == (2, 2):
+            count_2x2 += 1
+        elif u.shape == (4, 4):
+            assert np.allclose(u, cirq.unitary(cirq.CNOT))
+            count_cnot += 1
+        elif u.shape == (8, 8):
+            assert np.allclose(u, cirq.unitary(cirq.CCNOT))
+            count_ccnot += 1
+    return count_2x2, count_cnot, count_ccnot
+
+
+def _test_decompose(matrix, controls_count):
+    qubits = cirq.LineQubit.range(controls_count + 1)
+    operations = cirq.decompose_multi_controlled_rotation(
+        matrix, qubits[:-1], qubits[-1])
+    _count_operations(operations)
+    result_matrix = cirq.Circuit(operations).unitary()
+
+    expected_matrix = cirq.Circuit([
+        cirq.MatrixGate(matrix).on(qubits[-1]).controlled_by(*qubits[:-1])
+    ]).unitary()
+
+    assert np.allclose(expected_matrix, result_matrix)
+
+
+def test_decompose_specific_matrices():
+    for gate in [cirq.X, cirq.Y, cirq.Z, cirq.H, cirq.I, cirq.T, cirq.S]:
+        for controls_count in range(0, 7):
+            _test_decompose(cirq.unitary(gate), controls_count)
+
+
+def test_decompose_random_unitary():
+    np.random.seed(0)
+    for controls_count in range(0, 5):
+        for _ in range(10):
+            _test_decompose(_random_unitary(), controls_count)
+    for controls_count in range(5, 8):
+        _test_decompose(_random_unitary(), controls_count)
+
+
+def test_decompose_random_special_unitary():
+    np.random.seed(0)
+    for controls_count in range(0, 5):
+        for _ in range(10):
+            _test_decompose(_random_special_unitary(), controls_count)
+    for controls_count in range(5, 8):
+        _test_decompose(_random_special_unitary(), controls_count)
+
+
+def _decomposition_size(U, controls_count):
+    qubits = cirq.LineQubit.range(controls_count + 1)
+    operations = cirq.decompose_multi_controlled_rotation(
+        U, qubits[:controls_count], qubits[-1])
+    return _count_operations(operations)
+
+
+def test_decompose_size_special_unitary():
+    np.random.seed(0)
+    u = _random_special_unitary()
+    assert _decomposition_size(u, 0) == (1, 0, 0)
+    assert _decomposition_size(u, 1) == (3, 2, 0)
+    assert _decomposition_size(u, 2) == (8, 8, 0)
+    assert _decomposition_size(u, 3) == (8, 6, 2)
+    assert _decomposition_size(u, 4) == (24, 18, 4)
+    assert _decomposition_size(u, 5) == (40, 30, 12)
+    for i in range(6, 20):
+        assert _decomposition_size(u, i) == (64 * i - 312, 48 * i - 234, 16)
+
+
+def test_decompose_size_unitary():
+    np.random.seed(0)
+    u = _random_unitary()
+    assert _decomposition_size(u, 0) == (1, 0, 0)
+    assert _decomposition_size(u, 1) == (4, 2, 0)
+    assert _decomposition_size(u, 2) == (12, 8, 0)
+    assert _decomposition_size(u, 3) == (20, 12, 2)
+    assert _decomposition_size(u, 4) == (44, 28, 6)
+    assert _decomposition_size(u, 5) == (84, 56, 18)
+    assert _decomposition_size(u, 6) == (172, 120, 26)
+    assert _decomposition_size(u, 7) == (340, 244, 38)
+    assert _decomposition_size(u, 8) == (524, 380, 46)
+    assert _decomposition_size(u, 9) == (820, 600, 58)