Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"). You 

4# may not use this file except in compliance with the License. A copy of 

5# the License is located at 

6# 

7# http://aws.amazon.com/apache2.0/ 

8# 

9# or in the "license" file accompanying this file. This file is 

10# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 

11# ANY KIND, either express or implied. See the License for the specific 

12# language governing permissions and limitations under the License. 

13 

14from __future__ import annotations 

15 

16import json 

17from dataclasses import dataclass 

18from typing import Any, Callable, Counter, Dict, List, Optional, TypeVar, Union 

19 

20import numpy as np 

21from braket.circuits import Observable, ResultType, StandardObservable 

22from braket.circuits.observables import observable_from_ir 

23 

24T = TypeVar("T") 

25 

26 

27@dataclass 

28class GateModelQuantumTaskResult: 

29 """ 

30 Result of a gate model quantum task execution. This class is intended 

31 to be initialized by a QuantumTask class. 

32 

33 Args: 

34 task_metadata (Dict[str, Any]): Dictionary of task metadata. task_metadata must have 

35 keys 'Id', 'Shots', 'Ir', and 'IrType'. 

36 result_types (List[Dict[str, Any]]): List of dictionaries where each dictionary 

37 has two keys: 'Type' (the result type in IR JSON form) and 

38 'Value' (the result value for this result type). 

39 This can be an empty list if no result types are specified in the IR. 

40 This is calculated from `measurements` and 

41 the IR of the circuit program when `shots>0`. 

42 values (List[Any]): The values for result types requested in the circuit. 

43 This can be an empty list if no result types are specified in the IR. 

44 This is calculated from `measurements` and 

45 the IR of the circuit program when `shots>0`. 

46 measurements (numpy.ndarray, optional): 2d array - row is shot, column is qubit. 

47 Default is None. Only available when shots > 0. The qubits in `measurements` 

48 are the ones in `GateModelQuantumTaskResult.measured_qubits`. 

49 measured_qubits (List[int], optional): The indices of the measured qubits. Default 

50 is None. Only available when shots > 0. Indicates which qubits are in 

51 `measurements`. 

52 measurement_counts (Counter, optional): A Counter of measurements. Key is the measurements 

53 in a big endian binary string. Value is the number of times that measurement occurred. 

54 Default is None. Only available when shots > 0. 

55 measurement_probabilities (Dict[str, float], optional): 

56 A dictionary of probabilistic results. 

57 Key is the measurements in a big endian binary string. 

58 Value is the probability the measurement occurred. 

59 Default is None. Only available when shots > 0. 

60 measurements_copied_from_device (bool, optional): flag whether `measurements` 

61 were copied from device. If false, `measurements` are calculated from device data. 

62 Default is None. Only available when shots > 0. 

63 measurement_counts_copied_from_device (bool, optional): flag whether `measurement_counts` 

64 were copied from device. If False, `measurement_counts` are calculated from device data. 

65 Default is None. Only available when shots > 0. 

66 measurement_probabilities_copied_from_device (bool, optional): flag whether 

67 `measurement_probabilities` were copied from device. If false, 

68 `measurement_probabilities` are calculated from device data. Default is None. 

69 Only available when shots > 0. 

70 """ 

71 

72 task_metadata: Dict[str, Any] 

73 result_types: List[Dict[str, str]] 

74 values: List[Any] 

75 measurements: np.ndarray = None 

76 measured_qubits: List[int] = None 

77 measurement_counts: Counter = None 

78 measurement_probabilities: Dict[str, float] = None 

79 measurements_copied_from_device: bool = None 

80 measurement_counts_copied_from_device: bool = None 

81 measurement_probabilities_copied_from_device: bool = None 

82 

83 def get_value_by_result_type(self, result_type: ResultType) -> Any: 

84 """ 

85 Get value by result type. The result type must have already been 

86 requested in the circuit sent to the device for this task result. 

87 

88 Args: 

89 result_type (ResultType): result type requested 

90 

91 Returns: 

92 Any: value of the result corresponding to the result type 

93 

94 Raises: 

95 ValueError: If result type not found in result. 

96 Result types must be added to circuit before circuit is run on device. 

97 """ 

98 rt_json = result_type.to_ir().json() 

99 for rt in self.result_types: 

100 if rt_json == json.dumps(rt["Type"]): 

101 return rt["Value"] 

102 raise ValueError( 

103 "Result type not found in result. " 

104 + "Result types must be added to circuit before circuit is run on device." 

105 ) 

106 

107 def __eq__(self, other) -> bool: 

108 if isinstance(other, GateModelQuantumTaskResult) and self.task_metadata.get("Id"): 

109 return self.task_metadata["Id"] == other.task_metadata["Id"] 

110 return NotImplemented 

111 

112 @staticmethod 

113 def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: 

114 """ 

115 Creates measurement counts from measurements 

116 

117 Args: 

118 measurements (numpy.ndarray): 2d array - row is shot, column is qubit. 

119 

120 Returns: 

121 Counter: A Counter of measurements. Key is the measurements in a big endian binary 

122 string. Value is the number of times that measurement occurred. 

123 """ 

124 bitstrings = [] 

125 for j in range(len(measurements)): 

126 bitstrings.append("".join([str(element) for element in measurements[j]])) 

127 return Counter(bitstrings) 

128 

129 @staticmethod 

130 def measurement_probabilities_from_measurement_counts( 

131 measurement_counts: Counter, 

132 ) -> Dict[str, float]: 

133 """ 

134 Creates measurement probabilities from measurement counts 

135 

136 Args: 

137 measurement_counts (Counter): A Counter of measurements. Key is the measurements 

138 in a big endian binary string. Value is the number of times that measurement 

139 occurred. 

140 

141 Returns: 

142 Dict[str, float]: A dictionary of probabilistic results. Key is the measurements 

143 in a big endian binary string. Value is the probability the measurement occurred. 

144 """ 

145 measurement_probabilities = {} 

146 shots = sum(measurement_counts.values()) 

147 

148 for key, count in measurement_counts.items(): 

149 measurement_probabilities[key] = count / shots 

150 return measurement_probabilities 

151 

152 @staticmethod 

153 def measurements_from_measurement_probabilities( 

154 measurement_probabilities: Dict[str, float], shots: int 

155 ) -> np.ndarray: 

156 """ 

157 Creates measurements from measurement probabilities 

158 

159 Args: 

160 measurement_probabilities (Dict[str, float]): A dictionary of probabilistic results. 

161 Key is the measurements in a big endian binary string. 

162 Value is the probability the measurement occurred. 

163 shots (int): number of iterations on device 

164 

165 Returns: 

166 Dict[str, float]: A dictionary of probabilistic results. 

167 Key is the measurements in a big endian binary string. 

168 Value is the probability the measurement occurred. 

169 """ 

170 measurements_list = [] 

171 for bitstring in measurement_probabilities: 

172 measurement = list(bitstring) 

173 individual_measurement_list = [measurement] * int( 

174 round(measurement_probabilities[bitstring] * shots) 

175 ) 

176 measurements_list.extend(individual_measurement_list) 

177 return np.asarray(measurements_list, dtype=int) 

178 

179 @staticmethod 

180 def from_dict(result: Dict[str, Any]): 

181 """ 

182 Create GateModelQuantumTaskResult from dict. 

183 

184 Args: 

185 result (Dict[str, Any]): Results dict with GateModelQuantumTaskResult attributes as keys 

186 

187 Returns: 

188 GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given dict 

189 

190 Raises: 

191 ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key 

192 in the result dict 

193 """ 

194 return GateModelQuantumTaskResult._from_dict_internal(result) 

195 

196 @staticmethod 

197 def from_string(result: str) -> GateModelQuantumTaskResult: 

198 """ 

199 Create GateModelQuantumTaskResult from string. 

200 

201 Args: 

202 result (str): JSON object string, with GateModelQuantumTaskResult attributes as keys. 

203 

204 Returns: 

205 GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given string 

206 

207 Raises: 

208 ValueError: If neither "Measurements" nor "MeasurementProbabilities" is a key 

209 in the result dict 

210 """ 

211 json_obj = json.loads(result) 

212 for result_type in json_obj.get("ResultTypes", []): 

213 type = result_type["Type"]["type"] 

214 if type == "probability": 

215 result_type["Value"] = np.array(result_type["Value"]) 

216 elif type == "statevector": 

217 result_type["Value"] = np.array([complex(*value) for value in result_type["Value"]]) 

218 elif type == "amplitude": 

219 for state in result_type["Value"]: 

220 result_type["Value"][state] = complex(*result_type["Value"][state]) 

221 return GateModelQuantumTaskResult._from_dict_internal(json_obj) 

222 

223 @classmethod 

224 def _from_dict_internal(cls, result: Dict[str, Any]): 

225 if result["TaskMetadata"]["Shots"] > 0: 

226 return GateModelQuantumTaskResult._from_dict_internal_computational_basis_sampling( 

227 result 

228 ) 

229 else: 

230 return GateModelQuantumTaskResult._from_dict_internal_simulator_only(result) 

231 

232 @classmethod 

233 def _from_dict_internal_computational_basis_sampling(cls, result: Dict[str, Any]): 

234 task_metadata = result["TaskMetadata"] 

235 if "Measurements" in result: 

236 measurements = np.asarray(result["Measurements"], dtype=int) 

237 m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) 

238 m_probs = GateModelQuantumTaskResult.measurement_probabilities_from_measurement_counts( 

239 m_counts 

240 ) 

241 measurements_copied_from_device = True 

242 m_counts_copied_from_device = False 

243 m_probabilities_copied_from_device = False 

244 elif "MeasurementProbabilities" in result: 

245 shots = task_metadata["Shots"] 

246 m_probs = result["MeasurementProbabilities"] 

247 measurements = GateModelQuantumTaskResult.measurements_from_measurement_probabilities( 

248 m_probs, shots 

249 ) 

250 m_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements(measurements) 

251 measurements_copied_from_device = False 

252 m_counts_copied_from_device = False 

253 m_probabilities_copied_from_device = True 

254 else: 

255 raise ValueError( 

256 'One of "Measurements" or "MeasurementProbabilities" must be in the result dict' 

257 ) 

258 measured_qubits = result["MeasuredQubits"] 

259 if len(measured_qubits) != measurements.shape[1]: 

260 raise ValueError( 

261 f"Measured qubits {measured_qubits} is not equivalent to number of qubits " 

262 + f"{measurements.shape[1]} in measurements" 

263 ) 

264 result_types = GateModelQuantumTaskResult._calculate_result_types( 

265 result["TaskMetadata"]["Ir"], measurements, measured_qubits 

266 ) 

267 values = [rt["Value"] for rt in result_types] 

268 return cls( 

269 task_metadata=task_metadata, 

270 result_types=result_types, 

271 values=values, 

272 measurements=measurements, 

273 measured_qubits=measured_qubits, 

274 measurement_counts=m_counts, 

275 measurement_probabilities=m_probs, 

276 measurements_copied_from_device=measurements_copied_from_device, 

277 measurement_counts_copied_from_device=m_counts_copied_from_device, 

278 measurement_probabilities_copied_from_device=m_probabilities_copied_from_device, 

279 ) 

280 

281 @classmethod 

282 def _from_dict_internal_simulator_only(cls, result: Dict[str, Any]): 

283 task_metadata = result["TaskMetadata"] 

284 result_types = result["ResultTypes"] 

285 values = [rt["Value"] for rt in result_types] 

286 return cls(task_metadata=task_metadata, result_types=result_types, values=values) 

287 

288 @staticmethod 

289 def _calculate_result_types( 

290 ir_string: str, measurements: np.ndarray, measured_qubits: List[int] 

291 ) -> List[Dict[str, Any]]: 

292 ir = json.loads(ir_string) 

293 result_types = [] 

294 if not ir.get("results"): 

295 return result_types 

296 for result_type in ir["results"]: 

297 ir_observable = result_type.get("observable") 

298 observable = observable_from_ir(ir_observable) if ir_observable else None 

299 targets = result_type.get("targets") 

300 rt_type = result_type["type"] 

301 if rt_type == "probability": 

302 value = GateModelQuantumTaskResult._probability_from_measurements( 

303 measurements, measured_qubits, targets 

304 ) 

305 elif rt_type == "sample": 

306 value = GateModelQuantumTaskResult._calculate_for_targets( 

307 GateModelQuantumTaskResult._samples_from_measurements, 

308 measurements, 

309 measured_qubits, 

310 observable, 

311 targets, 

312 ) 

313 elif rt_type == "variance": 

314 value = GateModelQuantumTaskResult._calculate_for_targets( 

315 GateModelQuantumTaskResult._variance_from_measurements, 

316 measurements, 

317 measured_qubits, 

318 observable, 

319 targets, 

320 ) 

321 elif rt_type == "expectation": 

322 value = GateModelQuantumTaskResult._calculate_for_targets( 

323 GateModelQuantumTaskResult._expectation_from_measurements, 

324 measurements, 

325 measured_qubits, 

326 observable, 

327 targets, 

328 ) 

329 else: 

330 raise ValueError(f"Unknown result type {rt_type}") 

331 result_types.append({"Type": result_type, "Value": value}) 

332 return result_types 

333 

334 @staticmethod 

335 def _selected_measurements( 

336 measurements: np.ndarray, measured_qubits: List[int], targets: Optional[List[int]] 

337 ) -> np.ndarray: 

338 if targets is not None and targets != measured_qubits: 

339 # Only some qubits targeted 

340 columns = [measured_qubits.index(t) for t in targets] 

341 measurements = measurements[:, columns] 

342 return measurements 

343 

344 @staticmethod 

345 def _calculate_for_targets( 

346 calculate_function: Callable[[np.ndarray, List[int], Observable, List[int]], T], 

347 measurements: np.ndarray, 

348 measured_qubits: List[int], 

349 observable: Observable, 

350 targets: List[int], 

351 ) -> Union[T, List[T]]: 

352 if targets: 

353 return calculate_function(measurements, measured_qubits, observable, targets) 

354 else: 

355 return [ 

356 calculate_function(measurements, measured_qubits, observable, [i]) 

357 for i in measured_qubits 

358 ] 

359 

360 @staticmethod 

361 def _measurements_base_10(measurements: np.ndarray) -> np.ndarray: 

362 # convert samples from a list of 0, 1 integers, to base 10 representation 

363 shots, num_measured_qubits = measurements.shape 

364 unraveled_indices = [2] * num_measured_qubits 

365 return np.ravel_multi_index(measurements.T, unraveled_indices) 

366 

367 @staticmethod 

368 def _probability_from_measurements( 

369 measurements: np.ndarray, measured_qubits: List[int], targets: Optional[List[int]] 

370 ) -> np.ndarray: 

371 measurements = GateModelQuantumTaskResult._selected_measurements( 

372 measurements, measured_qubits, targets 

373 ) 

374 shots, num_measured_qubits = measurements.shape 

375 # convert measurements from a list of 0, 1 integers, to base 10 representation 

376 indices = GateModelQuantumTaskResult._measurements_base_10(measurements) 

377 

378 # count the basis state occurrences, and construct the probability vector 

379 basis_states, counts = np.unique(indices, return_counts=True) 

380 probabilities = np.zeros([2 ** num_measured_qubits], dtype=np.float64) 

381 probabilities[basis_states] = counts / shots 

382 return probabilities 

383 

384 @staticmethod 

385 def _variance_from_measurements( 

386 measurements: np.ndarray, 

387 measured_qubits: List[int], 

388 observable: Observable, 

389 targets: List[int], 

390 ) -> float: 

391 samples = GateModelQuantumTaskResult._samples_from_measurements( 

392 measurements, measured_qubits, observable, targets 

393 ) 

394 return np.var(samples) 

395 

396 @staticmethod 

397 def _expectation_from_measurements( 

398 measurements: np.ndarray, 

399 measured_qubits: List[int], 

400 observable: Observable, 

401 targets: List[int], 

402 ) -> float: 

403 samples = GateModelQuantumTaskResult._samples_from_measurements( 

404 measurements, measured_qubits, observable, targets 

405 ) 

406 return np.mean(samples) 

407 

408 @staticmethod 

409 def _samples_from_measurements( 

410 measurements: np.ndarray, 

411 measured_qubits: List[int], 

412 observable: Observable, 

413 targets: List[int], 

414 ) -> np.ndarray: 

415 measurements = GateModelQuantumTaskResult._selected_measurements( 

416 measurements, measured_qubits, targets 

417 ) 

418 if isinstance(observable, StandardObservable): 

419 # Process samples for observables with eigenvalues {1, -1} 

420 return 1 - 2 * measurements.flatten() 

421 # Replace the basis state in the computational basis with the correct eigenvalue. 

422 # Extract only the columns of the basis samples required based on ``targets``. 

423 indices = GateModelQuantumTaskResult._measurements_base_10(measurements) 

424 return observable.eigenvalues[indices].real