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.
14
15import string
16from typing import TYPE_CHECKING, Union, Any, Tuple, TypeVar, Optional, Dict, Iterable
17
18from typing_extensions import Protocol
19
20from cirq import ops
21from cirq._doc import doc_private
22from cirq.type_workarounds import NotImplementedType
23
24if TYPE_CHECKING:
25    import cirq
26
27TDefault = TypeVar('TDefault')
28
29RaiseTypeErrorIfNotProvided = ([],)  # type: Any
30
31
32class QasmArgs(string.Formatter):
33    def __init__(
34        self,
35        precision: int = 10,
36        version: str = '2.0',
37        qubit_id_map: Dict['cirq.Qid', str] = None,
38        meas_key_id_map: Dict[str, str] = None,
39    ) -> None:
40        """Inits QasmArgs.
41
42        Args:
43            precision: The number of digits after the decimal to show for
44                numbers in the qasm code.
45            version: The QASM version to target. Objects may return different
46                qasm depending on version.
47            qubit_id_map: A dictionary mapping qubits to qreg QASM identifiers.
48            meas_key_id_map: A dictionary mapping measurement keys to creg QASM
49                identifiers.
50        """
51        self.precision = precision
52        self.version = version
53        self.qubit_id_map = {} if qubit_id_map is None else qubit_id_map
54        self.meas_key_id_map = {} if meas_key_id_map is None else meas_key_id_map
55
56    def format_field(self, value: Any, spec: str) -> str:
57        """Method of string.Formatter that specifies the output of format()."""
58        if isinstance(value, (float, int)):
59            if isinstance(value, float):
60                value = round(value, self.precision)
61            if spec == 'half_turns':
62                value = f'pi*{value}' if value != 0 else '0'
63                spec = ''
64        elif isinstance(value, ops.Qid):
65            value = self.qubit_id_map[value]
66        elif isinstance(value, str) and spec == 'meas':
67            value = self.meas_key_id_map[value]
68            spec = ''
69        return super().format_field(value, spec)
70
71    def validate_version(self, *supported_versions: str) -> None:
72        if self.version not in supported_versions:
73            raise ValueError(f'QASM version {self.version} output is not supported.')
74
75
76class SupportsQasm(Protocol):
77    """An object that can be turned into QASM code.
78
79    Returning `NotImplemented` or `None` means "don't know how to turn into
80    QASM". In that case fallbacks based on decomposition and known unitaries
81    will be used instead.
82    """
83
84    @doc_private
85    def _qasm_(self) -> Union[None, NotImplementedType, str]:
86        pass
87
88
89class SupportsQasmWithArgs(Protocol):
90    """An object that can be turned into QASM code.
91
92    Returning `NotImplemented` or `None` means "don't know how to turn into
93    QASM". In that case fallbacks based on decomposition and known unitaries
94    will be used instead.
95    """
96
97    @doc_private
98    def _qasm_(self, args: QasmArgs) -> Union[None, NotImplementedType, str]:
99        pass
100
101
102class SupportsQasmWithArgsAndQubits(Protocol):
103    """An object that can be turned into QASM code if it knows its qubits.
104
105    Returning `NotImplemented` or `None` means "don't know how to turn into
106    QASM". In that case fallbacks based on decomposition and known unitaries
107    will be used instead.
108    """
109
110    @doc_private
111    def _qasm_(
112        self, qubits: Tuple['cirq.Qid'], args: QasmArgs
113    ) -> Union[None, NotImplementedType, str]:
114        pass
115
116
117# pylint: disable=function-redefined
118def qasm(
119    val: Any,
120    *,
121    args: Optional[QasmArgs] = None,
122    qubits: Optional[Iterable['cirq.Qid']] = None,
123    default: TDefault = RaiseTypeErrorIfNotProvided,
124) -> Union[str, TDefault]:
125    """Returns QASM code for the given value, if possible.
126
127    Different values require different sets of arguments. The general rule of
128    thumb is that circuits don't need any, operations need a `QasmArgs`, and
129    gates need both a `QasmArgs` and `qubits`.
130
131    Args:
132        val: The value to turn into QASM code.
133        args: A `QasmArgs` object to pass into the value's `_qasm_` method.
134            This is for needed for objects that only have a local idea of what's
135            going on, e.g. a `cirq.Operation` in a bigger `cirq.Circuit`
136            involving qubits that the operation wouldn't otherwise know about.
137        qubits: A list of qubits that the value is being applied to. This is
138            needed for `cirq.Gate` values, which otherwise wouldn't know what
139            qubits to talk about.
140        default: A default result to use if the value doesn't have a
141            `_qasm_` method or that method returns `NotImplemented` or `None`.
142            If not specified, non-decomposable values cause a `TypeError`.
143
144    Returns:
145        The result of `val._qasm_(...)`, if `val` has a `_qasm_`
146        method and it didn't return `NotImplemented` or `None`. Otherwise
147        `default` is returned, if it was specified. Otherwise an error is
148        raised.
149
150    Raises:
151        TypeError: `val` didn't have a `_qasm_` method (or that method returned
152            `NotImplemented` or `None`) and `default` wasn't set.
153    """
154    method = getattr(val, '_qasm_', None)
155    result = NotImplemented
156    if method is not None:
157        kwargs = {}  # type: Dict[str, Any]
158        if args is not None:
159            kwargs['args'] = args
160        if qubits is not None:
161            kwargs['qubits'] = tuple(qubits)
162        result = method(**kwargs)
163    if result is not None and result is not NotImplemented:
164        return result
165
166    if default is not RaiseTypeErrorIfNotProvided:
167        return default
168    if method is None:
169        raise TypeError(f"object of type '{type(val)}' has no _qasm_ method.")
170    raise TypeError(
171        "object of type '{}' does have a _qasm_ method, "
172        "but it returned NotImplemented or None.".format(type(val))
173    )
174
175
176# pylint: enable=function-redefined
177