1# Copyright 2021 The Cirq Developers
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6#     https://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11# See the License for the specific language governing permissions and
12# limitations under the License.
13"""Devices for IonQ hardware."""
14
15from typing import AbstractSet, Sequence, Union
16
17import numpy as np
18
19import cirq
20
21
22class IonQAPIDevice(cirq.Device):
23    """A device that uses the gates exposed by the IonQ API.
24
25    When using this device in constructing a circuit, it will convert one and two qubit gates
26    that are not supported by the API into those supported by the API if they have a unitary
27    matrix (support the unitary protocol).
28
29    Note that this device does not do any compression of the resulting circuit, i.e. it may
30    result in a series of single qubit gates that could be executed using far fewer elements.
31
32    The gates supported by the API are
33        * `cirq.XPowGate`, `cirq.YPowGate`, `cirq.ZPowGate`
34        * `cirq.XXPowGate`, `cirq.YYPowGate`, `cirq.ZZPowGate`
35        * `cirq.CNOT`, `cirq.H`, `cirq.SWAP`
36        * `cirq.MeasurementGate`
37    """
38
39    def __init__(self, qubits: Union[Sequence[cirq.LineQubit], int], atol=1e-8):
40        """Construct the device.
41
42        Args:
43            qubits: The qubits upon which this device acts or the number of qubits. If the number
44                of qubits, then the qubits will be `cirq.LineQubit`s from 0 to this number minus
45                one.
46            atol: The absolute tolerance used for gate calculations and decompositions.
47        """
48        if isinstance(qubits, int):
49            self.qubits = frozenset(cirq.LineQubit.range(qubits))
50        else:
51            self.qubits = frozenset(qubits)
52        self.atol = atol
53        self.gateset = cirq.Gateset(
54            cirq.H,
55            cirq.CNOT,
56            cirq.SWAP,
57            cirq.XPowGate,
58            cirq.YPowGate,
59            cirq.ZPowGate,
60            cirq.XXPowGate,
61            cirq.YYPowGate,
62            cirq.ZZPowGate,
63            cirq.MeasurementGate,
64            unroll_circuit_op=False,
65            accept_global_phase_op=False,
66        )
67
68    def qubit_set(self) -> AbstractSet['cirq.Qid']:
69        return self.qubits
70
71    def validate_operation(self, operation: cirq.Operation):
72        if operation.gate is None:
73            raise ValueError(
74                f'IonQAPIDevice does not support operations with no gates {operation}.'
75            )
76        if not self.is_api_gate(operation):
77            raise ValueError(f'IonQAPIDevice has unsupported gate {operation.gate}.')
78        if not set(operation.qubits).intersection(self.qubit_set()):
79            raise ValueError(f'Operation with qubits not on the device. Qubits: {operation.qubits}')
80
81    def is_api_gate(self, operation: cirq.Operation) -> bool:
82        return operation in self.gateset
83
84    def decompose_operation(self, operation: cirq.Operation) -> cirq.OP_TREE:
85        if self.is_api_gate(operation):
86            return operation
87        assert cirq.has_unitary(operation), (
88            f'Operation {operation} that is not available on the IonQ API nor does it have a '
89            'unitary matrix to use to decompose it to the API.'
90        )
91        num_qubits = len(operation.qubits)
92        if num_qubits == 1:
93            return self._decompose_single_qubit(operation)
94        if num_qubits == 2:
95            return self._decompose_two_qubit(operation)
96        raise ValueError(f'Operation {operation} not supported by IonQ API.')
97
98    def _decompose_single_qubit(self, operation: cirq.Operation) -> cirq.OP_TREE:
99        qubit = operation.qubits[0]
100        mat = cirq.unitary(operation)
101        for gate in cirq.single_qubit_matrix_to_gates(mat, self.atol):
102            yield gate(qubit)
103
104    def _decompose_two_qubit(self, operation: cirq.Operation) -> cirq.OP_TREE:
105        """Decomposes a two qubit gate into XXPow, YYPow, and ZZPow plus single qubit gates."""
106        mat = cirq.unitary(operation)
107        kak = cirq.kak_decomposition(mat, check_preconditions=False)
108
109        for qubit, mat in zip(operation.qubits, kak.single_qubit_operations_before):
110            gates = cirq.single_qubit_matrix_to_gates(mat, self.atol)
111            for gate in gates:
112                yield gate(qubit)
113
114        two_qubit_gates = [cirq.XX, cirq.YY, cirq.ZZ]
115        for two_qubit_gate, coefficient in zip(two_qubit_gates, kak.interaction_coefficients):
116            yield (two_qubit_gate ** (-coefficient * 2 / np.pi))(*operation.qubits)
117
118        for qubit, mat in zip(operation.qubits, kak.single_qubit_operations_after):
119            for gate in cirq.single_qubit_matrix_to_gates(mat, self.atol):
120                yield gate(qubit)
121