Source code for braket.circuits.result_types

# Copyright 2019-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
#     http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from __future__ import annotations

from typing import List

import braket.ir.jaqcd as ir
from braket.circuits import circuit
from braket.circuits.observable import Observable
from braket.circuits.qubit_set import QubitSet, QubitSetInput
from braket.circuits.result_type import ResultType


"""
To add a new result type:
    1. Implement the class and extend `ResultType`
    2. Add a method with the `@circuit.subroutine(register=True)` decorator. Method name
       will be added into the `Circuit` class. This method is the default way
       clients add this result type to a circuit.
    3. Register the class with the `ResultType` class via `ResultType.register_result_type()`.
"""


[docs]class StateVector(ResultType): """The full state vector as a requested result type.""" def __init__(self): super().__init__(ascii_symbol=["StateVector"])
[docs] def to_ir(self) -> ir.StateVector: return ir.StateVector()
[docs] @staticmethod @circuit.subroutine(register=True) 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 __eq__(self, other) -> bool: if isinstance(other, StateVector): return True return False def __copy__(self) -> StateVector: return type(self)()
ResultType.register_result_type(StateVector)
[docs]class Amplitude(ResultType): """The amplitude of specified quantum states as a requested result type.""" def __init__(self, state: List[str]): """ Args: state (List[str]): list of quantum states as strings with "0" and "1" Raises: ValueError: If state is None or an empty list Examples: >>> ResultType.Amplitude(state=['01', '10']) """ super().__init__(ascii_symbol=["Amplitude"]) if not state: raise ValueError("A non-empty list of states must be specified e.g. ['01', '10']") self._state = state @property def state(self) -> List[str]: return self._state
[docs] def to_ir(self) -> ir.Amplitude: return ir.Amplitude(states=self.state)
[docs] @staticmethod @circuit.subroutine(register=True) 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 __eq__(self, other): if isinstance(other, Amplitude): return self.state == other.state return False def __repr__(self): return f"Amplitude(state={self.state})" def __copy__(self): return type(self)(state=self.state)
ResultType.register_result_type(Amplitude)
[docs]class Probability(ResultType): """Probability as the requested result type. It can be the probability of all states if no targets are specified or the marginal probability of a restricted set of states if only a subset of all qubits are specified as target.""" def __init__(self, target: QubitSetInput = None): """ Args: target (int, Qubit, or iterable of int / Qubit, optional): Target qubits that the result type is requested for. Default is None, which means all qubits for the circuit. Examples: >>> ResultType.Probability(target=[0, 1]) """ super().__init__(ascii_symbol=["Prob"]) self._target = QubitSet(target) @property def target(self) -> QubitSet: return self._target @target.setter def target(self, target: QubitSetInput) -> None: self._target = QubitSet(target)
[docs] def to_ir(self) -> ir.Probability: return ir.Probability(targets=list(self.target)) if self.target else ir.Probability()
[docs] @staticmethod @circuit.subroutine(register=True) def probability(target: QubitSetInput = None) -> ResultType: """Registers this function into the circuit class. Args: target (int, Qubit, or iterable of int / Qubit, optional): 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 __eq__(self, other) -> bool: if isinstance(other, Probability): return self.target == other.target return False def __repr__(self) -> str: return f"Probability(target={self.target})" def __copy__(self) -> Probability: return type(self)(target=self.target)
ResultType.register_result_type(Probability)
[docs]class ObservableResultType(ResultType): """ Result types with observables and targets. If no targets are specified, the observable must only operate on 1 qubit and it will be applied to all qubits in parallel. Otherwise, the number of specified targets must be equivalent to the number of qubits the observable can be applied to. See :mod:`braket.circuits.observables` module for all of the supported observables. """ def __init__(self, ascii_symbol: str, observable: Observable, target: QubitSetInput = None): """ 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 Raises: ValueError: If the observable's qubit count and the number of target qubits are not equal. Or, if target=None and the observable's qubit count is not 1. """ super().__init__(ascii_symbol) self._observable = observable self._target = QubitSet(target) if not self._target: if self._observable.qubit_count != 1: raise ValueError( f"Observable {self._observable} must only operate on 1 qubit for target=None" ) elif self._observable.qubit_count != len(self._target): raise ValueError( f"Observable's qubit count and the number of target qubits must be equal" ) @property def observable(self) -> Observable: return self._observable @property def target(self) -> QubitSet: return self._target @target.setter def target(self, target: QubitSetInput) -> None: self._target = QubitSet(target) def __eq__(self, other) -> bool: if isinstance(other, ObservableResultType): return self.target == other.target and self.observable == other.observable return False def __repr__(self) -> str: return f"{self.name}(observable={self.observable}, target={self.target})" def __copy__(self) -> Expectation: return type(self)(observable=self.observable, target=self.target)
[docs]class Expectation(ObservableResultType): """Expectation of specified target qubit set and observable as the requested result type. If no targets are specified, the observable must only operate on 1 qubit and it will be applied to all qubits in parallel. Otherwise, the number of specified targets must be equivalent to the number of qubits the observable can be applied to. See :mod:`braket.circuits.observables` module for all of the supported observables. """ def __init__(self, observable: Observable, target: QubitSetInput = None): """ 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 Raises: ValueError: If the observable's qubit count and the number of target qubits are not equal. Or, if target=None and the observable's qubit count is not 1. Examples: >>> ResultType.Expectation(observable=Observable.Z(), target=0) >>> tensor_product = Observable.Y() @ Observable.Z() >>> ResultType.Expectation(observable=tensor_product, target=[0, 1]) """ super().__init__(ascii_symbol=["Expectation"], observable=observable, target=target)
[docs] def to_ir(self) -> ir.Expectation: if self.target: return ir.Expectation(observable=self.observable.to_ir(), targets=list(self.target)) else: return ir.Expectation(observable=self.observable.to_ir())
[docs] @staticmethod @circuit.subroutine(register=True) def expectation(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: expectation as a requested result type Examples: >>> circ = Circuit().expectation(observable=Observable.Z(), target=0) """ return ResultType.Expectation(observable=observable, target=target)
ResultType.register_result_type(Expectation)
[docs]class Sample(ObservableResultType): """Sample of specified target qubit set and observable as the requested result type. If no targets are specified, the observable must only operate on 1 qubit and it will be applied to all qubits in parallel. Otherwise, the number of specified targets must be equivalent to the number of qubits the observable can be applied to. See :mod:`braket.circuits.observables` module for all of the supported observables. """ def __init__(self, observable: Observable, target: QubitSetInput = None): """ 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 Raises: ValueError: If the observable's qubit count and the number of target qubits are not equal. Or, if target=None and the observable's qubit count is not 1. Examples: >>> ResultType.Sample(observable=Observable.Z(), target=0) >>> tensor_product = Observable.Y() @ Observable.Z() >>> ResultType.Sample(observable=tensor_product, target=[0, 1]) """ super().__init__(ascii_symbol=["Sample"], observable=observable, target=target)
[docs] def to_ir(self) -> ir.Sample: if self.target: return ir.Sample(observable=self.observable.to_ir(), targets=list(self.target)) else: return ir.Sample(observable=self.observable.to_ir())
[docs] @staticmethod @circuit.subroutine(register=True) 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 (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: sample as a requested result type Examples: >>> circ = Circuit().sample(observable=Observable.Z(), target=0) """ return ResultType.Sample(observable=observable, target=target)
ResultType.register_result_type(Sample)
[docs]class Variance(ObservableResultType): """Variance of specified target qubit set and observable as the requested result type. If no targets are specified, the observable must only operate on 1 qubit and it will be applied to all qubits in parallel. Otherwise, the number of specified targets must be equivalent to the number of qubits the observable can be applied to. See :mod:`braket.circuits.observables` module for all of the supported observables. """ def __init__(self, observable: Observable, target: QubitSetInput = None): """ 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 Raises: ValueError: If the observable's qubit count and the number of target qubits are not equal. Or, if target=None and the observable's qubit count is not 1. Examples: >>> ResultType.Variance(observable=Observable.Z(), target=0) >>> tensor_product = Observable.Y() @ Observable.Z() >>> ResultType.Variance(observable=tensor_product, target=[0, 1]) """ super().__init__(ascii_symbol=["Variance"], observable=observable, target=target)
[docs] def to_ir(self) -> ir.Variance: if self.target: return ir.Variance(observable=self.observable.to_ir(), targets=list(self.target)) else: return ir.Variance(observable=self.observable.to_ir())
[docs] @staticmethod @circuit.subroutine(register=True) 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)
ResultType.register_result_type(Variance)