# Source code for cirq.protocols.mixture

```
# Copyright 2019 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License 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.
"""Protocol for objects that are mixtures (probabilistic combinations)."""
from typing import Any, Sequence, Tuple, Union
import numpy as np
from typing_extensions import Protocol
from cirq.protocols.unitary import has_unitary
from cirq.type_workarounds import NotImplementedType
# This is a special indicator value used by the inverse method to determine
# whether or not the caller provided a 'default' argument.
RaiseTypeErrorIfNotProvided = ((0.0, []),) # type: Sequence[Tuple[float, Any]]
[docs]class SupportsMixture(Protocol):
"""An object that may be describable as a probabilistic combination.
"""
def _mixture_(self) -> Union[
Sequence[Tuple[float, Any]], NotImplementedType]:
"""Return the probabilistic mixture.
A mixture is described by an iterable of tuples of the form
(probability of object, object)
The probability components of the tuples must sum to 1.0 and be between
0 and 1 (inclusive).
Returns:
A tuple of (probability of object, object)
"""
def _has_mixture_(self) -> bool:
"""Whether this value has a mixture representation.
This method is used by the global `cirq.has_mixture` method. If this
method is not present, or returns NotImplemented, it will fallback
to using _mixture_ with a default value, or False if neither exist.
Returns:
True if the value has a mixture representation, Falseotherwise.
"""
[docs]def mixture(
val: Any,
default: Any = RaiseTypeErrorIfNotProvided) -> Sequence[Tuple[float, Any]]:
"""Return a sequence of tuples representing a probabilistic combination.
A mixture is described by an iterable of tuples of the form
(probability of object, object)
The probability components of the tuples must sum to 1.0 and be between
0 and 1 (inclusive).
Args:
val: The value whose mixture is being computed.
default: A default value if val does not support mixture.
Returns:
An iterable of tuples of size 2. The first element of the tuple is a
probability (between 0 and 1) and the second is the object that occurs
with that probability in the mixture. The probabilities will sum to 1.0.
"""
getter = getattr(val, '_mixture_', None)
result = NotImplemented if getter is None else getter()
if result is not NotImplemented:
return result
if default is not RaiseTypeErrorIfNotProvided:
return default
if getter is None:
raise TypeError(
"object of type '{}' has no _mixture_ method.".format(type(val)))
raise TypeError("object of type '{}' does have a _mixture_ method, "
"but it returned NotImplemented.".format(type(val)))
[docs]def has_mixture(val: Any) -> bool:
"""Returns whether the value has a mixture representation.
Returns:
If `val` has a `_has_mixture_` method and its result is not
NotImplemented, that result is returned. Otherwise, if the value
has a `_mixture_` method return True if that has a non-default value.
Returns False if neither function exists.
"""
getter = getattr(val, '_has_mixture_', None)
result = NotImplemented if getter is None else getter()
if result is not NotImplemented:
return result
# No _has_mixture_ function, use _mixture_ instead
return mixture(val, None) is not None
[docs]def mixture_channel(
val: Any,
default: Any = RaiseTypeErrorIfNotProvided) -> Sequence[
Tuple[float, np.ndarray]]:
"""Return a sequence of tuples for a channel that is a mixture of unitaries.
In contrast to `mixture` this method falls back to `unitary` if `_mixture_`
is not implemented.
A mixture channel is described by an iterable of tuples of the form
(probability of unitary, unitary)
The probability components of the tuples must sum to 1.0 and be between
0 and 1 (inclusive) and the `unitary` must be a unitary matrix.
Args:
val: The value whose mixture_channel is being computed.
default: A default value if val does not support mixture.
Returns:
An iterable of tuples of size 2. The first element of the tuple is a
probability (between 0 and 1) and the second is the unitary that occurs
with that probability. The probabilities will sum to 1.0.
"""
mixture_getter = getattr(val, '_mixture_', None)
result = NotImplemented if mixture_getter is None else mixture_getter()
if result is not NotImplemented:
return result
unitary_getter = getattr(val, '_unitary_', None)
result = NotImplemented if unitary_getter is None else unitary_getter()
if result is not NotImplemented:
return ((1.0, result),)
if default is not RaiseTypeErrorIfNotProvided:
return default
if mixture_getter is None and unitary_getter is None:
raise TypeError(
"object of type '{}' has no _mixture_ or _unitary_ method."
.format(type(val)))
raise TypeError("object of type '{}' does have a _mixture_ or _unitary_ "
"method, but it returned NotImplemented.".format(type(val)))
[docs]def has_mixture_channel(val: Any) -> bool:
"""Returns whether the value has a mixture channel representation.
In contrast to `has_mixture` this method falls back to checking whether
the value has a unitary representation via `has_channel`.
Returns:
If `val` has a `_has_mixture_` method and its result is not
NotImplemented, that result is returned. Otherwise, if `val` has a
`_has_unitary_` method and its results is not NotImplemented, that
result is returned. Otherwise, if the value has a `_mixture_` method
that is not a non-default value, True is returned. Returns False if none
of these functions.
"""
mixture_getter = getattr(val, '_has_mixture_', None)
result = NotImplemented if mixture_getter is None else mixture_getter()
if result is not NotImplemented:
return result
result = has_unitary(val)
if result is not NotImplemented and result:
return result
# No _has_mixture_ or _has_unitary_ function, use _mixture_ instead.
return mixture_channel(val, None) is not None
[docs]def validate_mixture(supports_mixture: SupportsMixture):
"""Validates that the mixture's tuple are valid probabilities."""
mixture_tuple = mixture(supports_mixture, None)
if mixture_tuple is None:
raise TypeError('{}_mixture did not have a _mixture_ method'.format(
supports_mixture))
def validate_probability(p, p_str):
if p < 0:
raise ValueError('{} was less than 0.'.format(p_str))
elif p > 1:
raise ValueError('{} was greater than 1.'.format(p_str))
total = 0.0
for p, val in mixture_tuple:
validate_probability(p, '{}\'s probability'.format(str(val)))
total += p
if not np.isclose(total, 1.0):
raise ValueError('Sum of probabilities of a mixture was not 1.0')
```