1# Copyright 2021 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 15"""Data structures for programs executable on a quantum runtime.""" 16 17import abc 18import dataclasses 19from dataclasses import dataclass 20from typing import Union, Tuple, Optional, Sequence, cast, Iterable, Dict, Any, List 21 22from cirq import _compat, study 23import cirq 24 25 26class ExecutableSpec(metaclass=abc.ABCMeta): 27 """Specification metadata about an executable. 28 29 Subclasses should add problem-specific fields. 30 """ 31 32 executable_family: str = NotImplemented 33 """A unique name to group executables.""" 34 35 36@dataclass(frozen=True) 37class KeyValueExecutableSpec(ExecutableSpec): 38 """A generic executable spec whose metadata is a list of key-value pairs. 39 40 The key-value pairs define an implicit data schema. Consider defining a problem-specific 41 subclass of `ExecutableSpec` instead of using this class to realize the benefits of having 42 an explicit schema. 43 44 See Also: 45 `KeyValueExecutableSpec.from_dict` will use a dictionary to populate `key_value_pairs`. 46 47 Args: 48 executable_family: A unique name to group executables. 49 key_value_pairs: A tuple of key-value pairs. The keys should be strings but the values 50 can be any immutable object. 51 """ 52 53 executable_family: str 54 key_value_pairs: Tuple[Tuple[str, Any], ...] = () 55 56 def _json_dict_(self) -> Dict[str, Any]: 57 return cirq.dataclass_json_dict(self, namespace='cirq.google') 58 59 @classmethod 60 def from_dict(cls, d: Dict[str, Any], *, executable_family: str) -> 'KeyValueExecutableSpec': 61 return cls( 62 executable_family=executable_family, 63 key_value_pairs=tuple((k, v) for k, v in d.items()), 64 ) 65 66 @classmethod 67 def _from_json_dict_( 68 cls, executable_family: str, key_value_pairs: List[List[Union[str, Any]]], **kwargs 69 ) -> 'KeyValueExecutableSpec': 70 return cls( 71 executable_family=executable_family, 72 key_value_pairs=tuple((k, v) for k, v in key_value_pairs), 73 ) 74 75 def __repr__(self) -> str: 76 return cirq._compat.dataclass_repr(self, namespace='cirq_google') 77 78 79@dataclass(frozen=True) 80class BitstringsMeasurement: 81 """Use in-circuit MeasurementGate to collect many repetitions of strings of bits. 82 83 This is the lowest-level measurement type allowed in `QuantumExecutable` and behaves 84 identically to the `cirq.Sampler.run` function. The executable's circuit must contain 85 explicit measurement gates. 86 87 Args: 88 n_repeitions: The number of repetitions to execute the circuit. 89 """ 90 91 n_repetitions: int 92 93 def _json_dict_(self): 94 return cirq.dataclass_json_dict(self, namespace='cirq.google') 95 96 def __repr__(self): 97 return cirq._compat.dataclass_repr(self, namespace='cirq_google') 98 99 100TParamPair = Tuple[cirq.TParamKey, cirq.TParamVal] 101 102 103@dataclass(frozen=True) 104class QuantumExecutable: 105 """An executable quantum program. 106 107 This serves a similar purpose to `cirq.Circuit` with some key differences. First, a quantum 108 executable contains all the relevant context for execution including parameters as well as 109 the desired number of repetitions. Second, this object is immutable. Finally, there are 110 optional fields enabling a higher level of abstraction for certain aspects of the executable. 111 112 Attributes: 113 circuit: A `cirq.Circuit` describing the quantum operations to execute. 114 measurement: A description of the measurement properties or process. 115 params: An immutable `cirq.ParamResolver` (or similar type). It's representation is 116 normalized to a tuple of key value pairs. 117 spec: Optional `cg.ExecutableSpec` containing metadata about this executable that is not 118 used by the quantum runtime, but will be forwarded to all downstream result objects. 119 problem_topology: Optional `cirq.NamedTopology` instance specifying the topology of the 120 circuit. This is useful when optimizing on-device layout. If none is provided we 121 assume `circuit` already has a valid on-device layout. 122 initial_state: A `cirq.ProductState` specifying the desired initial state before executing 123 `circuit`. If not specified, default to the all-zeros state. 124 """ 125 126 circuit: cirq.FrozenCircuit 127 measurement: BitstringsMeasurement 128 params: Optional[Tuple[TParamPair, ...]] = None 129 spec: Optional[ExecutableSpec] = None 130 problem_topology: Optional[cirq.NamedTopology] = None 131 initial_state: Optional[cirq.ProductState] = None 132 133 # pylint: disable=missing-raises-doc 134 def __init__( 135 self, 136 circuit: cirq.AbstractCircuit, 137 measurement: BitstringsMeasurement, 138 params: Union[Sequence[TParamPair], cirq.ParamResolverOrSimilarType] = None, 139 spec: Optional[ExecutableSpec] = None, 140 problem_topology: Optional[cirq.NamedTopology] = None, 141 initial_state: Optional[cirq.ProductState] = None, 142 ): 143 """Initialize the quantum executable. 144 145 The actual fields in this class are immutable, but we allow more liberal input types 146 which will be frozen in this __init__ method. 147 148 Args: 149 circuit: The circuit. This will be frozen before being set as an attribute. 150 measurement: A description of the measurement properties or process. 151 params: A cirq.ParamResolverOrSimilarType which will be frozen into a tuple of 152 key value pairs. 153 spec: Specification metadata about this executable that is not used by the quantum 154 runtime, but is persisted in result objects to associate executables with results. 155 problem_topology: Description of the multiqubit gate topology present in the circuit. 156 If not specified, the circuit must be compatible with the device topology. 157 initial_state: How to initialize the quantum system before running `circuit`. If not 158 specified, the device will be initialized into the all-zeros state. 159 """ 160 161 # We care a lot about mutability in this class. No object is truly immutable in Python, 162 # but we can get pretty close by following the example of dataclass(frozen=True), which 163 # deletes this class's __setattr__ magic method. To set values ever, we use 164 # object.__setattr__ in this __init__ function. 165 # 166 # We write our own __init__ function to be able to accept a wider range of input formats 167 # that can be easily converted to our native, immutable format. 168 object.__setattr__(self, 'circuit', circuit.freeze()) 169 object.__setattr__(self, 'measurement', measurement) 170 171 if isinstance(params, tuple) and all( 172 isinstance(param_kv, tuple) and len(param_kv) == 2 for param_kv in params 173 ): 174 frozen_params = params 175 elif isinstance(params, Sequence) and all( 176 isinstance(param_kv, Sequence) and len(param_kv) == 2 for param_kv in params 177 ): 178 frozen_params = tuple((k, v) for k, v in params) 179 elif study.resolver._is_param_resolver_or_similar_type(params): 180 param_resolver = cirq.ParamResolver(cast(cirq.ParamResolverOrSimilarType, params)) 181 frozen_params = tuple(param_resolver.param_dict.items()) 182 else: 183 raise ValueError(f"`params` should be a ParamResolverOrSimilarType, not {params}.") 184 object.__setattr__(self, 'params', frozen_params) 185 186 object.__setattr__(self, 'spec', spec) 187 object.__setattr__(self, 'problem_topology', problem_topology) 188 object.__setattr__(self, 'initial_state', initial_state) 189 190 # Hash may be expensive to compute, especially for large circuits. 191 # This should be safe since this class should be immutable. This line will 192 # also check for hashibility of members at construction time. 193 object.__setattr__(self, '_hash', hash(dataclasses.astuple(self))) 194 195 def __str__(self): 196 return f'QuantumExecutable(spec={self.spec})' 197 198 def __repr__(self): 199 return _compat.dataclass_repr(self, namespace='cirq_google') 200 201 def _json_dict_(self): 202 return cirq.dataclass_json_dict(self, namespace='cirq.google') 203 204 205@dataclass(frozen=True) 206class QuantumExecutableGroup: 207 """A collection of `QuantumExecutable`s. 208 209 Attributes: 210 executables: A tuple of `cg.QuantumExecutable`. 211 """ 212 213 executables: Tuple[QuantumExecutable, ...] 214 215 def __init__( 216 self, 217 executables: Sequence[QuantumExecutable], 218 ): 219 """Initialize and normalize the quantum executable group. 220 221 Args: 222 executables: A sequence of `cg.QuantumExecutable` which will be frozen into a 223 tuple. 224 """ 225 226 if not isinstance(executables, tuple): 227 executables = tuple(executables) 228 object.__setattr__(self, 'executables', executables) 229 230 object.__setattr__(self, '_hash', hash(dataclasses.astuple(self))) 231 232 def __len__(self) -> int: 233 return len(self.executables) 234 235 def __iter__(self) -> Iterable[QuantumExecutable]: 236 yield from self.executables 237 238 def __str__(self) -> str: 239 exe_str = ', '.join(str(exe) for exe in self.executables[:2]) 240 if len(self.executables) > 2: 241 exe_str += ', ...' 242 243 return f'QuantumExecutableGroup(executables=[{exe_str}])' 244 245 def __repr__(self) -> str: 246 return _compat.dataclass_repr(self, namespace='cirq_google') 247 248 def __hash__(self) -> int: 249 return self._hash # type: ignore 250 251 def _json_dict_(self) -> Dict[str, Any]: 252 return cirq.dataclass_json_dict(self, namespace='cirq.google') 253