Example #1
0
    def _approx_eq_(self, other: Any, atol: float) -> bool:
        """Implementation of `SupportsApproximateEquality` protocol."""
        # HACK: Avoids circular dependencies.
        from cirq.protocols import approx_eq

        if not isinstance(other, type(self)):
            return NotImplemented

        # self.value = value % period in __init__() creates a Mod
        if isinstance(other.value, sympy.Mod):
            return self.value == other.value
        # Periods must be exactly equal to avoid drift of normalized value when
        # original value increases.
        if self.period != other.period:
            return False

        low = min(self.value, other.value)
        high = max(self.value, other.value)

        # Shift lower value outside of normalization interval in case low and
        # high values are at the opposite borders of normalization interval.
        if high - low > self.period / 2:
            low += self.period

        return approx_eq(low, high, atol=atol)
Example #2
0
    def _equal_up_to_global_phase_(self, other, atol):
        if not isinstance(other, EigenGate):
            return NotImplemented

        exponents = (self.exponent, other.exponent)
        exponents_is_parameterized = tuple(
            protocols.is_parameterized(e) for e in exponents)
        if all(exponents_is_parameterized) and exponents[0] != exponents[1]:
            return False
        if any(exponents_is_parameterized):
            return False
        self_without_phase = self._with_exponent(self.exponent)
        self_without_phase._global_shift = 0
        self_without_exp_or_phase = self_without_phase._with_exponent(0)
        self_without_exp_or_phase._global_shift = 0
        other_without_phase = other._with_exponent(other.exponent)
        other_without_phase._global_shift = 0
        other_without_exp_or_phase = other_without_phase._with_exponent(0)
        other_without_exp_or_phase._global_shift = 0
        if not protocols.approx_eq(self_without_exp_or_phase,
                                   other_without_exp_or_phase,
                                   atol=atol):
            return False

        period = self_without_phase._period()
        canonical_diff = (exponents[0] - exponents[1]) % period
        return np.isclose(canonical_diff, 0, atol=atol)
 def __init__(
     self,
     mixture: Iterable[Tuple[float, np.ndarray]],
     key: Union[str, value.MeasurementKey, None] = None,
     validate: bool = False,
 ):
     mixture = list(mixture)
     if not mixture:
         raise ValueError('MixedUnitaryChannel must have at least one unitary.')
     if not protocols.approx_eq(sum(p[0] for p in mixture), 1):
         raise ValueError('Unitary probabilities must sum to 1.')
     m0 = mixture[0][1]
     num_qubits = np.log2(m0.shape[0])
     if not num_qubits.is_integer() or m0.shape[1] != m0.shape[0]:
         raise ValueError(
             f'Input mixture of shape {m0.shape} does not '
             'represent a square operator over qubits.'
         )
     self._num_qubits = int(num_qubits)
     for i, op in enumerate(p[1] for p in mixture):
         if not op.shape == m0.shape:
             raise ValueError(
                 f'Inconsistent unitary shapes: op[0]: {m0.shape}, op[{i}]: {op.shape}'
             )
         if validate and not linalg.is_unitary(op):
             raise ValueError(f'Element {i} of mixture is non-unitary.')
     self._mixture = mixture
     if not isinstance(key, value.MeasurementKey) and key is not None:
         key = value.MeasurementKey(key)
     self._key = key
Example #4
0
 def _approx_eq_(self, other: Any, atol: Union[int, float]) -> bool:
     """See `cirq.protocols.SupportsApproximateEquality`."""
     if not isinstance(other, type(self)):
         return NotImplemented
     return protocols.approx_eq(self.operations,
                                other.operations,
                                atol=atol)
Example #5
0
def partial_trace_of_state_vector_as_mixture(
        state_vector: np.ndarray,
        keep_indices: List[int],
        *,
        atol: Union[int,
                    float] = 1e-8) -> Tuple[Tuple[float, np.ndarray], ...]:
    """Returns a mixture representing a state vector with only some qubits kept.

    The input state vector must have shape `(2,) * n` or `(2 ** n)` where
    `state_vector` is expressed over n qubits. States in the output mixture will
    retain the same type of shape as the input state vector, either `(2 ** k)`
    or `(2,) * k` where k is the number of qubits kept.

    If the state vector cannot be factored into a pure state over `keep_indices`
    then eigendecomposition is used and the output mixture will not be unique.

    Args:
        state_vector: The state vector to take the partial trace over.
        keep_indices: Which indices to take the partial trace of the
            state_vector on.
        atol: The tolerance for determining that a factored state is pure.

    Returns:
        A single-component mixture in which the factored state vector has
        probability '1' if the partially traced state is pure, or else a
        mixture of the default eigendecomposition of the mixed state's
        partial trace.

    Raises:
        ValueError: if the input `state_vector` is not an array of length
        `(2 ** n)` or a tensor with a shape of `(2,) * n`
    """

    # Attempt to do efficient state factoring.
    state = sub_state_vector(state_vector,
                             keep_indices,
                             default=None,
                             atol=atol)
    if state is not None:
        return ((1.0, state), )

    # Fall back to a (non-unique) mixture representation.
    keep_dims = 1 << len(keep_indices)
    ret_shape: Union[Tuple[int], Tuple[int, ...]]
    if state_vector.shape == (state_vector.size, ):
        ret_shape = (keep_dims, )
    elif all(e == 2 for e in state_vector.shape):
        ret_shape = tuple(2 for _ in range(len(keep_indices)))

    rho = np.kron(
        np.conj(state_vector.reshape(-1, 1)).T,
        state_vector.reshape(-1, 1)).reshape(
            (2, 2) * int(np.log2(state_vector.size)))
    keep_rho = partial_trace(rho, keep_indices).reshape((keep_dims, ) * 2)
    eigvals, eigvecs = np.linalg.eigh(keep_rho)
    mixture = tuple(zip(eigvals,
                        [vec.reshape(ret_shape) for vec in eigvecs.T]))
    return tuple([(float(p[0]), p[1]) for p in mixture
                  if not protocols.approx_eq(p[0], 0.0)])
Example #6
0
def partial_trace_of_state_vector_as_mixture(
    state_vector: np.ndarray, keep_indices: List[int], *, atol: Union[int, float] = 1e-8
) -> Tuple[Tuple[float, np.ndarray], ...]:
    """Returns a mixture representing a state vector with only some qubits kept.

    The input state vector can have any shape, but if it is one-dimensional it
    will be interpreted as qubits, since that is the most common case, and fail
    if the dimension is not size `2 ** n`. States in the output mixture will
    retain the same type of shape as the input state vector.

    If the state vector cannot be factored into a pure state over `keep_indices`
    then eigendecomposition is used and the output mixture will not be unique.

    Args:
        state_vector: The state vector to take the partial trace over.
        keep_indices: Which indices to take the partial trace of the
            state_vector on.
        atol: The tolerance for determining that a factored state is pure.

    Returns:
        A single-component mixture in which the factored state vector has
        probability '1' if the partially traced state is pure, or else a
        mixture of the default eigendecomposition of the mixed state's
        partial trace.

    Raises:
        ValueError: If the input `state_vector` is one dimension, but that
            dimension size is not a power of two.
        IndexError: If any indexes are out of range.
    """

    if state_vector.ndim == 1:
        dims = int(np.log2(state_vector.size))
        if 2 ** dims != state_vector.size:
            raise ValueError(f'Cannot infer underlying shape of {state_vector.shape}.')
        state_vector = state_vector.reshape((2,) * dims)
        ret_shape: Tuple[int, ...] = (2 ** len(keep_indices),)
    else:
        ret_shape = tuple(state_vector.shape[i] for i in keep_indices)

    # Attempt to do efficient state factoring.
    try:
        state, _ = factor_state_vector(state_vector, keep_indices, atol=atol)
        return ((1.0, state.reshape(ret_shape)),)
    except EntangledStateError:
        pass

    # Fall back to a (non-unique) mixture representation.
    rho = np.outer(state_vector, np.conj(state_vector)).reshape(state_vector.shape * 2)
    keep_rho = partial_trace(rho, keep_indices).reshape((np.prod(ret_shape),) * 2)
    eigvals, eigvecs = np.linalg.eigh(keep_rho)
    mixture = tuple(zip(eigvals, [vec.reshape(ret_shape) for vec in eigvecs.T]))
    return tuple([(float(p[0]), p[1]) for p in mixture if not protocols.approx_eq(p[0], 0.0)])
def _value_equality_approx_eq(self: _SupportsValueEquality,
                              other: _SupportsValueEquality,
                              atol: float) -> bool:
    # Preserve regular equality type-comparison logic.
    cls_self = self._value_equality_values_cls_()
    if not isinstance(other, cls_self):
        return NotImplemented
    cls_other = other._value_equality_values_cls_()
    if cls_self != cls_other:
        return False

    # Delegate to cirq.approx_eq for approximate equality comparison.
    return protocols.approx_eq(self._value_equality_approximate_values_(),
                               other._value_equality_approximate_values_(),
                               atol=atol)
Example #8
0
def _value_equality_approx_eq(self: _SupportsValueEquality,
                              other: _SupportsValueEquality,
                              atol: float) -> bool:
    cls_self = self._value_equality_values_cls_()
    get_cls_other = getattr(other, '_value_equality_values_cls_', None)
    if get_cls_other is None:
        return NotImplemented
    cls_other = other._value_equality_values_cls_()
    if cls_self != cls_other:
        return False
    # Delegate to cirq.approx_eq for approximate equality comparison.
    return protocols.approx_eq(
        self._value_equality_approximate_values_(),
        other._value_equality_approximate_values_(),
        atol=atol,
    )
Example #9
0
def sub_state_vector(state_vector: np.ndarray,
                     keep_indices: List[int],
                     *,
                     default: TDefault = RaiseValueErrorIfNotProvided,
                     atol: Union[int, float] = 1e-8) -> np.ndarray:
    r"""Attempts to factor a state vector into two parts and return one of them.

    The input `state_vector` must have shape ``(2,) * n`` or ``(2 ** n)`` where
    `state_vector` is expressed over n qubits. The returned array will retain
    the same type of shape as the input state vector, either ``(2 ** k)`` or
    ``(2,) * k`` where k is the number of qubits kept.

    If a state vector $|\psi\rangle$ defined on n qubits is an outer product
    of kets like  $|\psi\rangle$ = $|x\rangle \otimes |y\rangle$, and
    $|x\rangle$ is defined over the subset ``keep_indices`` of k qubits, then
    this method will factor $|\psi\rangle$ into $|x\rangle$ and $|y\rangle$ and
    return $|x\rangle$. Note that $|x\rangle$ is not unique, because scalar
    multiplication may be absorbed by any factor of a tensor product,
    $e^{i \theta} |y\rangle \otimes |x\rangle =
    |y\rangle \otimes e^{i \theta} |x\rangle$

    This method randomizes the global phase of $|x\rangle$ in order to avoid
    accidental reliance on the global phase being some specific value.

    If the provided `state_vector` cannot be factored into a pure state over
    `keep_indices`, the method will fall back to return `default`. If `default`
    is not provided, the method will fail and raise `ValueError`.

    Args:
        state_vector: The target state_vector.
        keep_indices: Which indices to attempt to get the separable part of the
            `state_vector` on.
        default: Determines the fallback behavior when `state_vector` doesn't
            have a pure state factorization. If the factored state is not pure
            and `default` is not set, a ValueError is raised. If default is set
            to a value, that value is returned.
        atol: The minimum tolerance for comparing the output state's coherence
            measure to 1.

    Returns:
        The state vector expressed over the desired subset of qubits.

    Raises:
        ValueError: if the `state_vector` is not of the correct shape or the
        indices are not a valid subset of the input `state_vector`'s indices, or
        the result of factoring is not a pure state.
    """

    if not np.log2(state_vector.size).is_integer():
        raise ValueError("Input state_vector of size {} does not represent a "
                         "state over qubits.".format(state_vector.size))

    n_qubits = int(np.log2(state_vector.size))
    keep_dims = 1 << len(keep_indices)
    ret_shape: Union[Tuple[int], Tuple[int, ...]]
    if state_vector.shape == (state_vector.size, ):
        ret_shape = (keep_dims, )
        state_vector = state_vector.reshape((2, ) * n_qubits)
    elif state_vector.shape == (2, ) * n_qubits:
        ret_shape = tuple(2 for _ in range(len(keep_indices)))
    else:
        raise ValueError(
            "Input state_vector must be shaped like (2 ** n,) or (2,) * n")

    keep_dims = 1 << len(keep_indices)
    if not np.isclose(np.linalg.norm(state_vector), 1):
        raise ValueError("Input state must be normalized.")
    if len(set(keep_indices)) != len(keep_indices):
        raise ValueError(
            "keep_indices were {} but must be unique.".format(keep_indices))
    if any([ind >= n_qubits for ind in keep_indices]):
        raise ValueError(
            "keep_indices {} are an invalid subset of the input state vector.")

    other_qubits = sorted(set(range(n_qubits)) - set(keep_indices))
    candidates = [
        state_vector[predicates.slice_for_qubits_equal_to(
            other_qubits, k)].reshape(keep_dims)
        for k in range(1 << len(other_qubits))
    ]
    # The coherence measure is computed using unnormalized candidates.
    best_candidate = max(candidates, key=lambda c: np.linalg.norm(c, 2))
    best_candidate = best_candidate / np.linalg.norm(best_candidate)
    left = np.conj(best_candidate.reshape((keep_dims, ))).T
    coherence_measure = sum(
        [abs(np.dot(left, c.reshape((keep_dims, ))))**2 for c in candidates])

    if protocols.approx_eq(coherence_measure, 1, atol=atol):
        return np.exp(2j * np.pi *
                      np.random.random()) * best_candidate.reshape(ret_shape)

    # Method did not yield a pure state. Fall back to `default` argument.
    if default is not RaiseValueErrorIfNotProvided:
        return default

    raise ValueError(
        "Input state vector could not be factored into pure state over "
        "indices {}".format(keep_indices))