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)
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
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)
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)])
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)
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, )
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))