def get_amplitudes( self, circuit: Circuit, states: List[str], valid_check: bool = True, **kwargs: KwargTypes, ) -> Dict[str, complex]: """ Compute the complex coefficients of the final state. Supported `kwargs` are as for `BraketBackend.process_circuits`. :param states: classical states of interest, as binary strings of '0' and '1' :returns: final complex amplitudes if initial state is all-zeros """ if not self.supports_amplitude: raise RuntimeError("Backend does not support amplitude") if valid_check: self._check_all_circuits([circuit], nomeasure_warn=False) bkcirc = self._to_bkcirc(circuit) restype = ResultType.Amplitude(states) bkcirc.add_result_type(restype) task = self._run(bkcirc, n_shots=0, **kwargs) res = task.result() amplitudes = res.get_value_by_result_type(restype) cdict = {} for k, v in amplitudes.items(): # The amazon/sv1 simulator gives us 2-element lists [re, im]. # The local simulator gives us numpy.complex128. cdict[k] = complex(*v) if type(v) is list else complex(v) return cdict
def state_vector() -> ResultType: """Registers this function into the circuit class. Returns: ResultType: state vector as a requested result type Examples: >>> circ = Circuit().state_vector() """ return ResultType.StateVector()
def _get_result(completed_task: Union[AwsQuantumTask, LocalQuantumTask], want_state: bool) -> Dict[str, BackendResult]: result = completed_task.result() kwargs = {} if want_state: kwargs["state"] = result.get_value_by_result_type( ResultType.StateVector()) else: kwargs["shots"] = OutcomeArray.from_readouts(result.measurements) return {"result": BackendResult(**kwargs)}
def amplitude(state: List[str]) -> ResultType: """Registers this function into the circuit class. Args: state (List[str]): list of quantum states as strings with "0" and "1" Returns: ResultType: state vector as a requested result type Examples: >>> circ = Circuit().amplitude(state=["01", "10"]) """ return ResultType.Amplitude(state=state)
def process_circuits( self, circuits: Iterable[Circuit], n_shots: Optional[int] = None, valid_check: bool = True, **kwargs: KwargTypes, ) -> List[ResultHandle]: """ Supported `kwargs`: none """ if not self.supports_shots and not self.supports_state: raise RuntimeError("Backend does not support shots or state") if n_shots is None: n_shots = 0 want_state = n_shots == 0 if (not want_state) and (n_shots < self._sample_min_shots or n_shots > self._sample_max_shots): raise ValueError( "For sampling, n_shots must be between " f"{self._sample_min_shots} and {self._sample_max_shots}. " "For statevector simulation, omit this parameter.") if valid_check: self._check_all_circuits(circuits, nomeasure_warn=False) handles = [] for circ in circuits: bkcirc = self._to_bkcirc(circ) if want_state: bkcirc.add_result_type(ResultType.StateVector()) if not bkcirc.instructions and len(circ.bits) == 0: task = None else: task = self._run(bkcirc, n_shots=n_shots) if self._device_type == _DeviceType.LOCAL: # Results are available now. Put them in the cache. if task is not None: assert task.state() == "COMPLETED" results = _get_result(task, want_state) else: results = { "result": self.empty_result(circ, n_shots=n_shots) } else: # Task is asynchronous. Must wait for results. results = {} if task is not None: handle = ResultHandle(task.id, want_state) else: handle = ResultHandle(str(uuid4()), False) self._cache[handle] = results handles.append(handle) return handles
def density_matrix(target: QubitSetInput = None) -> ResultType: """Registers this function into the circuit class. Args: target (int, Qubit, or iterable of int / Qubit, optional): The target qubits of the reduced density matrix. Default is `None`, and the full density matrix is returned. Returns: ResultType: density matrix as a requested result type Examples: >>> circ = Circuit().density_matrix(target=[0, 1]) """ return ResultType.DensityMatrix(target=target)
def probability(target: QubitSetInput = None) -> ResultType: """Registers this function into the circuit class. Args: target (int, Qubit, or iterable of int / Qubit, optional): The target qubits that the result type is requested for. Default is `None`, which means all qubits for the circuit. Returns: ResultType: probability as a requested result type Examples: >>> circ = Circuit().probability(target=[0, 1]) """ return ResultType.Probability(target=target)
def variance(observable: Observable, target: QubitSetInput = None) -> ResultType: """Registers this function into the circuit class. Args: observable (Observable): the observable for the result type target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the result type is requested for. Default is `None`, which means the observable must only operate on 1 qubit and it will be applied to all qubits in parallel Returns: ResultType: variance as a requested result type Examples: >>> circ = Circuit().variance(observable=Observable.Z(), target=0) """ return ResultType.Variance(observable=observable, target=target)
def sample(observable: Observable, target: QubitSetInput = None) -> ResultType: """Registers this function into the circuit class. Args: observable (Observable): the observable for the result type target (QubitSetInput): Target qubits that the result type is requested for. Default is `None`, which means the observable must operate only on 1 qubit and it is applied to all qubits in parallel. Returns: ResultType: sample as a requested result type Examples: >>> circ = Circuit().sample(observable=Observable.Z(), target=0) """ return ResultType.Sample(observable=observable, target=target)
def _get_variance( self, bkcirc: braket.circuits.Circuit, observable: Observable, target: QubitSet, n_shots: int, **kwargs: KwargTypes, ) -> np.float64: if not self.supports_variance: raise RuntimeError("Backend does not support variance") if n_shots < self._variance_min_shots or n_shots > self._variance_max_shots: raise ValueError( f"n_shots must be between {self._variance_min_shots} and " f"{self._variance_max_shots}") restype = ResultType.Variance(observable, target=target) bkcirc.add_result_type(restype) task = self._run(bkcirc, n_shots=n_shots, **kwargs) res = task.result() return res.get_value_by_result_type(restype) # type: ignore
def get_probabilities( self, circuit: Circuit, qubits: Union[Iterable[int], None] = None, n_shots: int = 0, valid_check: bool = True, **kwargs: KwargTypes, ) -> np.ndarray: """ Compute the (exact or empirical) probability distribution of outcomes. If `n_shots > 0` the probabilities are calculated empirically by measurements. If `n_shots = 0` (if supported) they are calculated exactly by simulation. Supported `kwargs` are as for `BraketBackend.process_circuits`. The order is big-endian with respect to the order of qubits in the argument. For example, if qubits=[0,1] then the order of probabilities is [p(0,0), p(0,1), p(1,0), p(1,1)], while if qubits=[1,0] the order is [p(0,0), p(1,0), p(0,1), p(1,1)], where p(i,j) is the probability of qubit 0 being in state i and qubit 1 being in state j. :param qubits: qubits of interest :returns: list of probabilities of outcomes if initial state is all-zeros """ if not self.supports_probability: raise RuntimeError("Backend does not support probability") if (n_shots < self._probability_min_shots or n_shots > self._probability_max_shots): raise ValueError( f"n_shots must be between {self._probability_min_shots} and " f"{self._probability_max_shots}") if valid_check: self._check_all_circuits([circuit], nomeasure_warn=False) bkcirc = self._to_bkcirc(circuit) restype = ResultType.Probability(target=qubits) bkcirc.add_result_type(restype) task = self._run(bkcirc, n_shots=n_shots, **kwargs) res = task.result() return res.get_value_by_result_type(restype) # type: ignore
def add_result_type( self, result_type: ResultType, target: QubitSetInput = None, target_mapping: Dict[QubitInput, QubitInput] = {}, ) -> Circuit: """ Add a requested result type to `self`, returns `self` for chaining ability. Args: result_type (ResultType): `ResultType` to add into `self`. target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the `result_type`. Default = `None`. target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the `result_type.target`. Key is the qubit in `result_type.target` and the value is what the key will be changed to. Default = `{}`. Note: target and target_mapping will only be applied to those requested result types with the attribute `target`. The result_type will be appended to the end of the list of `circuit.result_types` only if it does not already exist in `circuit.result_types` Returns: Circuit: self Raises: TypeError: If both `target_mapping` and `target` are supplied. ValueError: If the observable specified for a qubit is different from what is specified by the result types already added to the circuit. Only one observable is allowed for a qubit. Examples: >>> result_type = ResultType.Probability(target=[0, 1]) >>> circ = Circuit().add_result_type(result_type) >>> print(circ.result_types[0]) Probability(target=QubitSet([Qubit(0), Qubit(1)])) >>> result_type = ResultType.Probability(target=[0, 1]) >>> circ = Circuit().add_result_type(result_type, target_mapping={0: 10, 1: 11}) >>> print(circ.result_types[0]) Probability(target=QubitSet([Qubit(10), Qubit(11)])) >>> result_type = ResultType.Probability(target=[0, 1]) >>> circ = Circuit().add_result_type(result_type, target=[10, 11]) >>> print(circ.result_types[0]) Probability(target=QubitSet([Qubit(10), Qubit(11)])) >>> result_type = ResultType.StateVector() >>> circ = Circuit().add_result_type(result_type) >>> print(circ.result_types[0]) StateVector() """ if target_mapping and target is not None: raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") if not target_mapping and not target: # Nothing has been supplied, add result_type result_type_to_add = result_type elif target_mapping: # Target mapping has been supplied, copy result_type result_type_to_add = result_type.copy(target_mapping=target_mapping) else: # ResultType with target result_type_to_add = result_type.copy(target=target) if result_type_to_add not in self._result_types: self._add_to_qubit_observable_mapping(result_type_to_add) self._add_to_qubit_observable_set(result_type_to_add) self._result_types.append(result_type_to_add) return self
def __eq__(self, other) -> bool: if isinstance(other, StateVector): return True return False def __copy__(self) -> StateVector: return type(self)() # must redefine __hash__ since __eq__ is overwritten # https://docs.python.org/3/reference/datamodel.html#object.__hash__ def __hash__(self) -> int: return super().__hash__() ResultType.register_result_type(StateVector) class DensityMatrix(ResultType): """ The full density matrix as a requested result type. This is available on simulators only when `shots=0`. """ def __init__(self, target: QubitSetInput = None): """ Args: target (int, Qubit, or iterable of int / Qubit, optional): The target qubits of the reduced density matrix. Default is `None`, and the full density matrix is returned.
def add_result_type( self, result_type: ResultType, target: QubitSetInput = None, target_mapping: Dict[QubitInput, QubitInput] = None, ) -> Circuit: """ Add a requested result type to `self`, returns `self` for chaining ability. Args: result_type (ResultType): `ResultType` to add into `self`. target (int, Qubit, or iterable of int / Qubit, optional): Target qubits for the `result_type`. Default = `None`. target_mapping (dictionary[int or Qubit, int or Qubit], optional): A dictionary of qubit mappings to apply to the `result_type.target`. Key is the qubit in `result_type.target` and the value is what the key will be changed to. Default = `None`. Note: target and target_mapping will only be applied to those requested result types with the attribute `target`. The result_type will be appended to the end of the dict keys of `circuit.result_types` only if it does not already exist in `circuit.result_types` Returns: Circuit: self Raises: TypeError: If both `target_mapping` and `target` are supplied. Examples: >>> result_type = ResultType.Probability(target=[0, 1]) >>> circ = Circuit().add_result_type(result_type) >>> print(circ.result_types[0]) Probability(target=QubitSet([Qubit(0), Qubit(1)])) >>> result_type = ResultType.Probability(target=[0, 1]) >>> circ = Circuit().add_result_type(result_type, target_mapping={0: 10, 1: 11}) >>> print(circ.result_types[0]) Probability(target=QubitSet([Qubit(10), Qubit(11)])) >>> result_type = ResultType.Probability(target=[0, 1]) >>> circ = Circuit().add_result_type(result_type, target=[10, 11]) >>> print(circ.result_types[0]) Probability(target=QubitSet([Qubit(10), Qubit(11)])) >>> result_type = ResultType.StateVector() >>> circ = Circuit().add_result_type(result_type) >>> print(circ.result_types[0]) StateVector() """ if target_mapping and target is not None: raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") if not target_mapping and not target: # Nothing has been supplied, add result_type result_type_to_add = result_type elif target_mapping: # Target mapping has been supplied, copy result_type result_type_to_add = result_type.copy(target_mapping=target_mapping) else: # ResultType with target result_type_to_add = result_type.copy(target=target) if result_type_to_add not in self._result_types: observable = Circuit._extract_observable(result_type_to_add) if observable and self._observables_simultaneously_measurable: # Only check if all observables can be simultaneously measured self._add_to_qubit_observable_mapping(observable, result_type_to_add.target) self._add_to_qubit_observable_set(result_type_to_add) # using dict as an ordered set, value is arbitrary self._result_types[result_type_to_add] = None return self