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