1# Copyright 2018 The Cirq Developers
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14from typing import List, Optional
15
16import cirq
17
18
19class ConvertToXmonGates(cirq.PointOptimizer):
20    """Attempts to convert strange gates into XmonGates.
21
22    First, checks if the given operation is already a native xmon operation.
23
24    Second, checks if the operation has a known unitary. If so, and the gate
25        is a 1-qubit or 2-qubit gate, then performs circuit synthesis of the
26        operation.
27
28    Third, attempts to `cirq.decompose` to the operation.
29
30    Fourth, if ignore_failures is set, gives up and returns the gate unchanged.
31        Otherwise raises a TypeError.
32    """
33
34    def __init__(self, ignore_failures=False) -> None:
35        """Inits ConvertToXmonGates.
36
37        Args:
38            ignore_failures: If set, gates that fail to convert are forwarded
39                unchanged. If not set, conversion failures raise a TypeError.
40        """
41        super().__init__()
42        self.ignore_failures = ignore_failures
43
44    def _convert_one(self, op: cirq.Operation) -> cirq.OP_TREE:
45        # Known matrix?
46        mat = cirq.unitary(op, None) if len(op.qubits) <= 2 else None
47        if mat is not None and len(op.qubits) == 1:
48            gates = cirq.single_qubit_matrix_to_phased_x_z(mat)
49            return [g.on(op.qubits[0]) for g in gates]
50        if mat is not None and len(op.qubits) == 2:
51            return cirq.two_qubit_matrix_to_operations(
52                op.qubits[0], op.qubits[1], mat, allow_partial_czs=True, clean_operations=False
53            )
54
55        return NotImplemented
56
57    def _is_native_xmon_op(self, op: cirq.Operation) -> bool:
58        """Check if the gate within an operation is a native xmon gate.
59
60        Args:
61            op: Input operation.
62
63        Returns:
64            True if the operation is native to the xmon, false otherwise.
65        """
66        from cirq_google.devices import XmonDevice
67
68        return op.gate is not None and XmonDevice.is_supported_gate(op.gate)
69
70    def convert(self, op: cirq.Operation) -> List[cirq.Operation]:
71        def on_stuck_raise(bad):
72            return TypeError(
73                "Don't know how to work with {!r}. "
74                "It isn't a native xmon operation, "
75                "a 1 or 2 qubit gate with a known unitary, "
76                "or composite.".format(bad)
77            )
78
79        return cirq.decompose(
80            op,
81            keep=self._is_native_xmon_op,
82            intercepting_decomposer=self._convert_one,
83            on_stuck_raise=None if self.ignore_failures else on_stuck_raise,
84        )
85
86    def optimization_at(
87        self, circuit: cirq.Circuit, index: int, op: cirq.Operation
88    ) -> Optional[cirq.PointOptimizationSummary]:
89        if op.gate is None:
90            return None
91
92        converted = self.convert(op)
93        if len(converted) == 1 and converted[0] is op:
94            return None
95
96        return cirq.PointOptimizationSummary(
97            clear_span=1, new_operations=converted, clear_qubits=op.qubits
98        )
99