Coverage for src/braket/tasks/gate_model_quantum_task_result.py : 100%

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.
14from __future__ import annotations
16import json
17from dataclasses import dataclass
18from typing import Any, Callable, Counter, Dict, List, Optional, TypeVar, Union
20import numpy as np
21from braket.circuits import Observable, ResultType, StandardObservable
22from braket.circuits.observables import observable_from_ir
24T = TypeVar("T")
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.
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 """
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
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.
88 Args:
89 result_type (ResultType): result type requested
91 Returns:
92 Any: value of the result corresponding to the result type
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 )
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
112 @staticmethod
113 def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter:
114 """
115 Creates measurement counts from measurements
117 Args:
118 measurements (numpy.ndarray): 2d array - row is shot, column is qubit.
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)
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
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.
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())
148 for key, count in measurement_counts.items():
149 measurement_probabilities[key] = count / shots
150 return measurement_probabilities
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
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
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)
179 @staticmethod
180 def from_dict(result: Dict[str, Any]):
181 """
182 Create GateModelQuantumTaskResult from dict.
184 Args:
185 result (Dict[str, Any]): Results dict with GateModelQuantumTaskResult attributes as keys
187 Returns:
188 GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given dict
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)
196 @staticmethod
197 def from_string(result: str) -> GateModelQuantumTaskResult:
198 """
199 Create GateModelQuantumTaskResult from string.
201 Args:
202 result (str): JSON object string, with GateModelQuantumTaskResult attributes as keys.
204 Returns:
205 GateModelQuantumTaskResult: A GateModelQuantumTaskResult based on the given string
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)
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)
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 )
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)
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
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
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 ]
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)
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)
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
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)
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)
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