示例#1
0
    def subspace_index(
            self,
            little_endian_bits_int: int,
    ) -> Tuple[Union[slice, int, 'ellipsis'], ...]:
        """An index for the subspace where the target axes equal a value.

        Args:
            little_endian_bits_int: The desired value of the qubits at the
                targeted `axes`, packed into an integer. The least significant
                bit of the integer is the desired bit for the first axis, and
                so forth in increasing order.
            value_tuple: The desired value of the qids at the targeted `axes`,
                packed into a tuple.  Specify either `little_endian_bits_int` or
                `value_tuple`.

        Returns:
            A value that can be used to index into `target_tensor` and
            `available_buffer`, and manipulate only the part of Hilbert space
            corresponding to a given bit assignment.

        Example:
            If `target_tensor` is a 4 qubit tensor and `axes` is `[1, 3]` and
            then this method will return the following when given
            `little_endian_bits=0b01`:

                `(slice(None), 0, slice(None), 1, Ellipsis)`

            Therefore the following two lines would be equivalent:

                args.target_tensor[args.subspace_index(0b01)] += 1

                args.target_tensor[:, 0, :, 1] += 1
        """
        return linalg.slice_for_qubits_equal_to(self.axes,
                                                little_endian_bits_int)
示例#2
0
    def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray:
        n = len(self.controls)
        control_axes = args.axes[:n]
        sub_axes = args.axes[n:]
        active = linalg.slice_for_qubits_equal_to(control_axes, -1)
        view_axes = _positions_after_removals_at(initial_positions=sub_axes,
                                                 removals=control_axes)
        target_view = args.target_tensor[active]
        buffer_view = args.available_buffer[active]
        result = protocols.apply_unitary(self.sub_operation,
                                         protocols.ApplyUnitaryArgs(
                                             target_view, buffer_view,
                                             view_axes),
                                         default=NotImplemented)

        if result is NotImplemented:
            return NotImplemented

        if result is target_view:
            return args.target_tensor

        # HACK: assume they didn't somehow escape the slice view and edit the
        # rest of target_tensor.
        args.target_tensor[active] = result
        return args.target_tensor
示例#3
0
def _probs(state: np.ndarray, indices: Sequence[int], qid_shape: Tuple[int, ...]) -> np.ndarray:
    """Returns the probabilities for a measurement on the given indices."""
    tensor = np.reshape(state, qid_shape)
    # Calculate the probabilities for measuring the particular results.
    if len(indices) == len(qid_shape):
        # We're measuring every qudit, so no need for fancy indexing
        probs = np.abs(tensor) ** 2
        probs = np.transpose(probs, indices)
        probs = np.reshape(probs, np.prod(probs.shape))
    else:
        # Fancy indexing required
        meas_shape = tuple(qid_shape[i] for i in indices)
        probs = (
            np.abs(
                [
                    tensor[
                        linalg.slice_for_qubits_equal_to(
                            indices, big_endian_qureg_value=b, qid_shape=qid_shape
                        )
                    ]
                    for b in range(np.prod(meas_shape, dtype=int))
                ]
            )
            ** 2
        )
        probs = np.sum(probs, axis=tuple(range(1, len(probs.shape))))

    # To deal with rounding issues, ensure that the probabilities sum to 1.
    probs /= np.sum(probs)
    return probs
示例#4
0
    def _apply_unitary_to_tensor_(
        self,
        target_tensor: np.ndarray,
        available_buffer: np.ndarray,
        axes: Sequence[int],
    ) -> Union[np.ndarray, type(NotImplemented)]:
        if self.exponent != 1:
            return NotImplemented

        zo = linalg.slice_for_qubits_equal_to(axes, 0b01)
        oz = linalg.slice_for_qubits_equal_to(axes, 0b10)
        available_buffer[zo] = target_tensor[zo]
        target_tensor[zo] = target_tensor[oz]
        target_tensor[oz] = available_buffer[zo]
        target_tensor[zo] *= 1j
        target_tensor[oz] *= 1j
        return target_tensor
示例#5
0
    def _apply_unitary_to_tensor_(self,
                                  target_tensor: np.ndarray,
                                  available_buffer: np.ndarray,
                                  axes: Sequence[int],
                                  ) -> Union[np.ndarray, NotImplementedType]:
        if self._exponent != 1:
            return NotImplemented

        zo = linalg.slice_for_qubits_equal_to(axes, 0b01)
        oz = linalg.slice_for_qubits_equal_to(axes, 0b10)
        available_buffer[zo] = target_tensor[zo]
        target_tensor[zo] = target_tensor[oz]
        target_tensor[oz] = available_buffer[zo]
        p = 1j**(2 * self._exponent * self._global_shift)
        if p != 1:
            target_tensor *= p
        return target_tensor
示例#6
0
    def subspace_index(
        self,
        axes: Sequence[int],
        little_endian_bits_int: int = 0,
        *,
        big_endian_bits_int: int = 0
    ) -> Tuple[Union[slice, int, 'ellipsis'], ...]:
        """An index for the subspace where the target axes equal a value.

        Args:
            axes: The qubits that are specified by the index bits.
            little_endian_bits_int: The desired value of the qubits at the
                targeted `axes`, packed into an integer. The least significant
                bit of the integer is the desired bit for the first axis, and
                so forth in increasing order. Can't be specified at the same
                time as `big_endian_bits_int`.

                When operating on qudits instead of qubits, the same basic logic
                applies but in a different basis. For example, if the target
                axes have dimension [a:2, b:3, c:2] then the integer 10
                decomposes into [a=0, b=2, c=1] via 7 = 1*(3*2) +  2*(2) + 0.
            big_endian_bits_int: The desired value of the qubits at the
                targeted `axes`, packed into an integer. The most significant
                bit of the integer is the desired bit for the first axis, and
                so forth in decreasing order. Can't be specified at the same
                time as `little_endian_bits_int`.

                When operating on qudits instead of qubits, the same basic logic
                applies but in a different basis. For example, if the target
                axes have dimension [a:2, b:3, c:2] then the integer 10
                decomposes into [a=1, b=2, c=0] via 7 = 1*(3*2) +  2*(2) + 0.

        Returns:
            A value that can be used to index into `target_tensor` and
            `available_buffer`, and manipulate only the part of Hilbert space
            corresponding to a given bit assignment.

        Example:
            If `target_tensor` is a 4 qubit tensor and `axes` is `[1, 3]` and
            then this method will return the following when given
            `little_endian_bits=0b01`:

                `(slice(None), 0, slice(None), 1, Ellipsis)`

            Therefore the following two lines would be equivalent:

                args.target_tensor[args.subspace_index(0b01)] += 1

                args.target_tensor[:, 0, :, 1] += 1
        """
        return linalg.slice_for_qubits_equal_to(
            axes,
            little_endian_qureg_value=little_endian_bits_int,
            big_endian_qureg_value=big_endian_bits_int,
            qid_shape=self.target_tensor.shape,
        )
示例#7
0
 def _apply_unitary_to_tensor_(self,
                               target_tensor: np.ndarray,
                               available_buffer: np.ndarray,
                               axes: Sequence[int],
                               ) -> np.ndarray:
     if protocols.is_parameterized(self):
         return NotImplemented
     ooo = linalg.slice_for_qubits_equal_to(axes, 0b111)
     target_tensor[ooo] *= np.exp(1j * self.exponent * np.pi)
     return target_tensor
示例#8
0
    def _apply_unitary_to_tensor_(
        self,
        target_tensor: np.ndarray,
        available_buffer: np.ndarray,
        axes: Sequence[int],
    ) -> Union[np.ndarray, NotImplementedType]:
        if protocols.is_parameterized(self):
            return NotImplemented

        global_phase = 1j**(2 * self._exponent * self._global_shift)
        if global_phase != 1:
            target_tensor *= global_phase

        relative_phase = 1j**(2 * self.exponent)
        zo = linalg.slice_for_qubits_equal_to(axes, 0b01)
        oz = linalg.slice_for_qubits_equal_to(axes, 0b10)
        target_tensor[oz] *= relative_phase
        target_tensor[zo] *= relative_phase

        return target_tensor
示例#9
0
def _apply_kraus_single_qubit(kraus: Union[Tuple[Any], Sequence[Any]],
                              args: 'ApplyChannelArgs') -> np.ndarray:
    """Use slicing to apply single qubit channel.  Only for two-level qubits."""
    zero_left = linalg.slice_for_qubits_equal_to(args.left_axes, 0)
    one_left = linalg.slice_for_qubits_equal_to(args.left_axes, 1)
    zero_right = linalg.slice_for_qubits_equal_to(args.right_axes, 0)
    one_right = linalg.slice_for_qubits_equal_to(args.right_axes, 1)
    for kraus_op in kraus:
        np.copyto(dst=args.target_tensor, src=args.auxiliary_buffer0)
        linalg.apply_matrix_to_slices(args.target_tensor,
                                      kraus_op, [zero_left, one_left],
                                      out=args.auxiliary_buffer1)
        # No need to transpose as we are acting on the tensor
        # representation of matrix, so transpose is done for us.
        linalg.apply_matrix_to_slices(args.auxiliary_buffer1,
                                      np.conjugate(kraus_op),
                                      [zero_right, one_right],
                                      out=args.target_tensor)
        args.out_buffer += args.target_tensor
    return args.out_buffer
示例#10
0
    def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs
                        ) -> Union[np.ndarray, NotImplementedType]:
        if protocols.is_parameterized(self):
            return NotImplemented

        c = 1j**(2 * self._exponent)
        one_one = linalg.slice_for_qubits_equal_to(args.axes, 0b11)
        args.target_tensor[one_one] *= c
        p = 1j**(2 * self._exponent * self._global_shift)
        if p != 1:
            args.target_tensor *= p
        return args.target_tensor
示例#11
0
    def _apply_unitary_to_tensor_(
        self,
        target_tensor: np.ndarray,
        available_buffer: np.ndarray,
        axes: Sequence[int],
    ) -> Union[np.ndarray, NotImplementedType]:
        if protocols.is_parameterized(self):
            return NotImplemented

        one = linalg.slice_for_qubits_equal_to(axes, 1)
        c = np.exp(1j * np.pi * self.half_turns)
        target_tensor[one] *= c
        return target_tensor
示例#12
0
    def _apply_unitary_to_tensor_(
        self,
        target_tensor: np.ndarray,
        available_buffer: np.ndarray,
        axes: Sequence[int],
    ) -> Union[np.ndarray, type(NotImplemented)]:
        if self.is_parameterized():
            return NotImplemented

        c = np.exp(1j * np.pi * self.half_turns)
        one_one = linalg.slice_for_qubits_equal_to(axes, 0b11)
        target_tensor[one_one] *= c
        return target_tensor
示例#13
0
def _probs(state: np.ndarray, indices: List[int],
           num_qubits: int) -> List[float]:
    """Returns the probabilities for a measurement on the given indices."""
    # Tensor of squared amplitudes, shaped a rank [2, 2, .., 2] tensor.
    tensor = np.reshape(state, [2] * num_qubits)

    # Calculate the probabilities for measuring the particular results.
    probs = [
        np.linalg.norm(tensor[linalg.slice_for_qubits_equal_to(indices, b)])**2
        for b in range(2**len(indices))
    ]

    # To deal with rounding issues, ensure that the probabilities sum to 1.
    probs /= sum(probs)  # type: ignore
    return probs
示例#14
0
    def _apply_unitary_to_tensor_(self,
                                  target_tensor: np.ndarray,
                                  available_buffer: np.ndarray,
                                  axes: Sequence[int],
                                  ) -> Union[np.ndarray, NotImplementedType]:
        if protocols.is_parameterized(self):
            return NotImplemented

        c = 1j**(2 * self._exponent)
        one_one = linalg.slice_for_qubits_equal_to(axes, 0b11)
        target_tensor[one_one] *= c
        p = 1j**(2 * self._exponent * self._global_shift)
        if p != 1:
            target_tensor *= p
        return target_tensor
示例#15
0
def assert_phase_by_is_consistent_with_unitary(val: Any):
    """Uses `val._unitary_` to check `val._phase_by_`'s behavior."""

    original = protocols.unitary(val, None)
    if original is None:
        # If there's no unitary, it's vacuously consistent.
        return
    qid_shape = protocols.qid_shape(val,
                                    default=(2, ) *
                                    (len(original).bit_length() - 1))
    original = original.reshape(qid_shape * 2)

    for t in [0.125, -0.25, 1, sympy.Symbol('a'), sympy.Symbol('a') + 1]:
        p = 1j**(t * 4)
        p = protocols.resolve_parameters(p, {'a': -0.125})
        for i in range(len(qid_shape)):
            phased = protocols.phase_by(val, t, i, default=None)
            if phased is None:
                # If not phaseable, then phase_by is vacuously consistent.
                continue

            phased = protocols.resolve_parameters(phased, {'a': -0.125})
            actual = protocols.unitary(phased).reshape(qid_shape * 2)

            expected = np.array(original)
            s = linalg.slice_for_qubits_equal_to([i], 1)
            expected[s] *= p
            s = linalg.slice_for_qubits_equal_to([len(qid_shape) + i], 1)
            expected[s] *= np.conj(p)

            lin_alg_utils.assert_allclose_up_to_global_phase(
                actual,
                expected,
                atol=1e-8,
                err_msg=f'Phased unitary was incorrect for index #{i}',
            )
示例#16
0
def _probs(state: np.ndarray, indices: List[int],
           qid_shape: Tuple[int, ...]) -> List[float]:
    """Returns the probabilities for a measurement on the given indices."""
    # Tensor of squared amplitudes, shaped a rank [2, 2, .., 2] tensor.
    tensor = np.reshape(state, qid_shape)

    # Calculate the probabilities for measuring the particular results.
    meas_shape = tuple(qid_shape[i] for i in indices)
    probs = [
        np.linalg.norm(tensor[linalg.slice_for_qubits_equal_to(
            indices, big_endian_qureg_value=b, qid_shape=qid_shape)])**2
        for b in range(np.prod(meas_shape, dtype=int))
    ]

    # To deal with rounding issues, ensure that the probabilities sum to 1.
    probs /= sum(probs)  # type: ignore
    return probs
示例#17
0
def _probs(density_matrix: np.ndarray, indices: List[int],
           num_qubits: int) -> List[float]:
    """Returns the probabilities for a measurement on the given indices."""
    # Only diagonal elements matter.
    all_probs = np.diagonal(
        np.reshape(density_matrix, (2**num_qubits, 2**num_qubits)))
    # Shape into a tensor
    tensor = np.reshape(all_probs, [2] * num_qubits)

    # Calculate the probabilities for measuring the particular results.
    probs = [
        np.sum(np.abs(tensor[linalg.slice_for_qubits_equal_to(indices, b)]))
        for b in range(2**len(indices))
    ]

    # To deal with rounding issues, ensure that the probabilities sum to 1.
    probs /= np.sum(probs)  # type: ignore
    return probs
示例#18
0
def _probs(density_matrix: np.ndarray, indices: List[int],
           qid_shape: Tuple[int, ...]) -> List[float]:
    """Returns the probabilities for a measurement on the given indices."""
    # Only diagonal elements matter.
    all_probs = np.diagonal(
        np.reshape(density_matrix, (np.prod(qid_shape, dtype=int),) * 2))
    # Shape into a tensor
    tensor = np.reshape(all_probs, qid_shape)

    # Calculate the probabilities for measuring the particular results.
    meas_shape = tuple(qid_shape[i] for i in indices)
    probs = [
        np.sum(
            np.abs(tensor[linalg.slice_for_qubits_equal_to(
                indices, big_endian_qureg_value=b, qid_shape=qid_shape)]))
        for b in range(np.prod(meas_shape, dtype=int))
    ]

    # To deal with rounding issues, ensure that the probabilities sum to 1.
    probs /= np.sum(probs) # type: ignore
    return probs
示例#19
0
    def _apply_unitary_(self, args: 'cirq.ApplyUnitaryArgs'):
        transposed_args = args.with_axes_transposed_to_start()

        target_axes = transposed_args.axes[:len(self.base_operation.qubits)]
        control_axes = transposed_args.axes[len(self.base_operation.qubits):]
        control_max = np.product([q.dimension for q in self.register]).item()

        for i in range(control_max):
            operation = self.base_operation**(self.exponent_sign * i /
                                              control_max)
            control_index = linalg.slice_for_qubits_equal_to(
                control_axes, big_endian_qureg_value=i)
            sub_args = cirq.ApplyUnitaryArgs(
                transposed_args.target_tensor[control_index],
                transposed_args.available_buffer[control_index], target_axes)
            sub_result = cirq.apply_unitary(operation, sub_args)

            if sub_result is not sub_args.target_tensor:
                sub_args.target_tensor[...] = sub_result

        return args.target_tensor
示例#20
0
def measure_density_matrix(density_matrix: np.ndarray,
                           indices: List[int],
                           qid_shape: Optional[Tuple[int, ...]] = None,
                           out: np.ndarray = None,
                           seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None
                          ) -> Tuple[List[int], np.ndarray]:
    """Performs a measurement of the density matrix in the computational basis.

    This does not modify `density_matrix` unless the optional `out` is
    `density_matrix`.

    Args:
        density_matrix: The density matrix to be measured. This matrix is
            assumed to be positive semidefinite and trace one. The matrix is
            assumed to be of shape (2 ** integer, 2 ** integer) or
            (2, 2, ..., 2).
        indices: Which qubits are measured. The matrix is assumed to be supplied
            in big endian order. That is the xth index of v, when expressed as
            a bitstring, has the largest values in the 0th index.
        qid_shape: The qid shape of the density matrix.  Specify this argument
            when using qudits.
        out: An optional place to store the result. If `out` is the same as
            the `density_matrix` parameter, then `density_matrix` will be
            modified inline. If `out` is not None, then the result is put into
            `out`.  If `out` is None a new value will be allocated. In all of
            these cases `out` will be the same as the returned ndarray of the
            method. The shape and dtype of `out` will match that of
            `density_matrix` if `out` is None, otherwise it will match the
            shape and dtype of `out`.
        seed: A seed for the pseudorandom number generator.

    Returns:
        A tuple of a list and an numpy array. The list is an array of booleans
        corresponding to the measurement values (ordered by the indices). The
        numpy array is the post measurement matrix. This matrix has the same
        shape and dtype as the input matrix.

    Raises:
        ValueError if the dimension of the matrix is not compatible with a
            matrix of n qubits.
        IndexError if the indices are out of range for the number of qubits
            corresponding to the density matrix.
    """
    if qid_shape is None:
        num_qubits = _validate_num_qubits(density_matrix)
        qid_shape = (2,) * num_qubits
    else:
        _validate_density_matrix_qid_shape(density_matrix, qid_shape)
        num_qubits = len(qid_shape)
    meas_shape = _indices_shape(qid_shape, indices)

    if len(indices) == 0:
        if out is None:
            out = np.copy(density_matrix)
        elif out is not density_matrix:
            np.copyto(dst=out, src=density_matrix)
        return ([], out)
        # Final else: if out is matrix then matrix will be modified in place.

    prng = value.parse_random_state(seed)

    # Cache initial shape.
    initial_shape = density_matrix.shape

    # Calculate the measurement probabilities and then make the measurement.
    probs = _probs(density_matrix, indices, qid_shape)
    result = prng.choice(len(probs), p=probs)
    measurement_bits = value.big_endian_int_to_digits(result, base=meas_shape)

    # Calculate the slice for the measurement result.
    result_slice = linalg.slice_for_qubits_equal_to(
        indices, big_endian_qureg_value=result, qid_shape=qid_shape)
    # Create a mask which is False for only the slice.
    mask = np.ones(qid_shape * 2, dtype=bool)
    # Remove ellipses from last element of
    mask[result_slice * 2] = False

    if out is None:
        out = np.copy(density_matrix)
    elif out is not density_matrix:
        np.copyto(dst=out, src=density_matrix)
    # Final else: if out is matrix then matrix will be modified in place.

    # Potentially reshape to tensor, and then set masked values to 0.
    out.shape = qid_shape * 2
    out[mask] = 0

    # Restore original shape (if necessary) and renormalize.
    out.shape = initial_shape
    out /= probs[result]

    return measurement_bits, out
示例#21
0
def measure_state_vector(
    state_vector: np.ndarray,
    indices: Sequence[int],
    *,  # Force keyword args
    qid_shape: Optional[Tuple[int, ...]] = None,
    out: np.ndarray = None,
    seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
) -> Tuple[List[int], np.ndarray]:
    """Performs a measurement of the state in the computational basis.

    This does not modify `state` unless the optional `out` is `state`.

    Args:
        state_vector: The state to be measured. This state vector is assumed to
            be normalized. The state vector must be of size 2 ** integer.  The
            state vector can be of shape (2 ** integer) or (2, 2, ..., 2).
        indices: Which qubits are measured. The `state_vector` is assumed to be
            supplied in big endian order. That is the xth index of v, when
            expressed as a bitstring, has the largest values in the 0th index.
        qid_shape: The qid shape of the `state_vector`.  Specify this argument
            when using qudits.
        out: An optional place to store the result. If `out` is the same as
            the `state_vector` parameter, then `state_vector` will be modified
            inline. If `out` is not None, then the result is put into `out`.
            If `out` is None a new value will be allocated. In all of these
            case out will be the same as the returned ndarray of the method.
            The shape and dtype of `out` will match that of `state_vector` if
            `out` is None, otherwise it will match the shape and dtype of `out`.
        seed: A seed for the pseudorandom number generator.

    Returns:
        A tuple of a list and an numpy array. The list is an array of booleans
        corresponding to the measurement values (ordered by the indices). The
        numpy array is the post measurement state vector. This state vector has
        the same shape and dtype as the input `state_vector`.

    Raises:
        ValueError if the size of state is not a power of 2.
        IndexError if the indices are out of range for the number of qubits
            corresponding to the state.
    """
    shape = qis.validate_qid_shape(state_vector, qid_shape)
    num_qubits = len(shape)
    qis.validate_indices(num_qubits, indices)

    if len(indices) == 0:
        if out is None:
            out = np.copy(state_vector)
        elif out is not state_vector:
            np.copyto(dst=out, src=state_vector)
        # Final else: if out is state then state will be modified in place.
        return ([], out)

    prng = value.parse_random_state(seed)

    # Cache initial shape.
    initial_shape = state_vector.shape

    # Calculate the measurement probabilities and then make the measurement.
    probs = _probs(state_vector, indices, shape)
    result = prng.choice(len(probs), p=probs)
    ###measurement_bits = [(1 & (result >> i)) for i in range(len(indices))]
    # Convert to individual qudit measurements.
    meas_shape = tuple(shape[i] for i in indices)
    measurement_bits = value.big_endian_int_to_digits(result, base=meas_shape)

    # Calculate the slice for the measurement result.
    result_slice = linalg.slice_for_qubits_equal_to(
        indices, big_endian_qureg_value=result, qid_shape=shape)

    # Create a mask which is False for only the slice.
    mask = np.ones(shape, dtype=bool)
    mask[result_slice] = False

    if out is None:
        out = np.copy(state_vector)
    elif out is not state_vector:
        np.copyto(dst=out, src=state_vector)
    # Final else: if out is state then state will be modified in place.

    # Potentially reshape to tensor, and then set masked values to 0.
    out.shape = shape
    out[mask] = 0

    # Restore original shape (if necessary) and renormalize.
    out.shape = initial_shape
    out /= np.sqrt(probs[result])

    return measurement_bits, out
示例#22
0
def measure_density_matrix(
        density_matrix: np.ndarray,
        indices: List[int],
        out: np.ndarray = None) -> Tuple[List[bool], np.ndarray]:
    """Performs a measurement of the density matrix in the computational basis.

    This does not modify `density_matrix` unless the optional `out` is
    `density_matrix`.

    Args:
        density_matrix: The density matrix to be measured. This matrix is
            assumed to be positive semidefinite and trace one. The matrix is
            assumed to be of shape (2 ** integer, 2 ** integer) or
            (2, 2, ..., 2).
        indices: Which qubits are measured. The matrix is assumed to be supplied
            in big endian order. That is the xth index of v, when expressed as
            a bitstring, has the largest values in the 0th index.
        out: An optional place to store the result. If `out` is the same as
            the `density_matrix` parameter, then `density_matrix` will be
            modified inline. If `out` is not None, then the result is put into
            `out`.  If `out` is None a new value will be allocated. In all of
            these cases `out` will be the same as the returned ndarray of the
            method. The shape and dtype of `out` will match that of
            `density_matrix` if `out` is None, otherwise it will match the
            shape and dtype of `out`.

    Returns:
        A tuple of a list and an numpy array. The list is an array of booleans
        corresponding to the measurement values (ordered by the indices). The
        numpy array is the post measurement matrix. This matrix has the same
        shape and dtype as the input matrix.

    Raises:
        ValueError if the dimension of the matrix is not compatible with a
            matrix of n qubits.
        IndexError if the indices are out of range for the number of qubits
            corresponding to the density matrix.
    """
    num_qubits = _validate_num_qubits(density_matrix)
    _validate_indices(num_qubits, indices)

    if len(indices) == 0:
        if out is None:
            out = np.copy(density_matrix)
        elif out is not density_matrix:
            np.copyto(dst=out, src=density_matrix)
        return ([], out)
        # Final else: if out is matrix then matrix will be modified in place.

    # Cache initial shape.
    initial_shape = density_matrix.shape

    # Calculate the measurement probabilities and then make the measurement.
    probs = _probs(density_matrix, indices, num_qubits)
    result = np.random.choice(len(probs), p=probs)
    measurement_bits = [(1 & (result >> i)) for i in range(len(indices))]

    # Calculate the slice for the measurement result.
    result_slice = linalg.slice_for_qubits_equal_to(indices,
                                                    result,
                                                    num_qubits=num_qubits)
    # Create a mask which is False for only the slice.
    mask = np.ones([2] * 2 * num_qubits, dtype=bool)
    # Remove ellipses from last element of
    mask[result_slice * 2] = False

    if out is None:
        out = np.copy(density_matrix)
    elif out is not density_matrix:
        np.copyto(dst=out, src=density_matrix)
    # Final else: if out is matrix then matrix will be modified in place.

    # Potentially reshape to tensor, and then set masked values to 0.
    out.shape = [2] * num_qubits * 2
    out[mask] = 0

    # Restore original shape (if necessary) and renormalize.
    out.shape = initial_shape
    out /= probs[result]

    return measurement_bits, out
示例#23
0
def measure_state_vector(
        state: np.ndarray,
        indices: List[int],
        out: np.ndarray = None) -> Tuple[List[bool], np.ndarray]:
    """Performs a measurement of the state in the computational basis.

    This does not modify `state` unless the optional `out` is `state`.

    Args:
        state: The state to be measured. This state is assumed to be normalized.
            The state must be of size 2 ** integer.  The state can be of shape
            (2 ** integer) or (2, 2, ..., 2).
        indices: Which qubits are measured. The state is assumed to be supplied
            in big endian order. That is the xth index of v, when expressed as
            a bitstring, has the largest values in the 0th index.
        out: An optional place to store the result. If `out` is the same as
            the `state` parameter, then state will be modified inline. If `out`
            is not None, then the result is put into `out`.  If `out` is None
            a new value will be allocated. In all of these case out will be the
            same as the returned ndarray of the method. The shape and dtype of
            `out` will match that of state if `out` is None, otherwise it will
            match the shape and dtype of `out`.

    Returns:
        A tuple of a list and an numpy array. The list is an array of booleans
        corresponding to the measurement values (ordered by the indices). The
        numpy array is the post measurement state. This state has the same
        shape and dtype as the input state.

    Raises:
        ValueError if the size of state is not a power of 2.
        IndexError if the indices are out of range for the number of qubits
            corresponding to the state.
    """
    num_qubits = _validate_num_qubits(state)
    _validate_indices(num_qubits, indices)

    if len(indices) == 0:
        if out is None:
            out = np.copy(state)
        elif out is not state:
            np.copyto(dst=out, src=state)
        # Final else: if out is state then state will be modified in place.
        return ([], out)

    # Cache initial shape.
    initial_shape = state.shape

    # Calculate the measurement probabilities and then make the measurement.
    probs = _probs(state, indices, num_qubits)
    result = np.random.choice(len(probs), p=probs)
    measurement_bits = [(1 & (result >> i)) for i in range(len(indices))]

    # Calculate the slice for the measurement result.
    result_slice = linalg.slice_for_qubits_equal_to(indices, result)

    # Create a mask which is False for only the slice.
    mask = np.ones([2] * num_qubits, dtype=bool)
    mask[result_slice] = False

    if out is None:
        out = np.copy(state)
    elif out is not state:
        np.copyto(dst=out, src=state)
    # Final else: if out is state then state will be modified in place.

    # Potentially reshape to tensor, and then set masked values to 0.
    out.shape = [2] * num_qubits
    out[mask] = 0

    # Restore original shape (if necessary) and renormalize.
    out.shape = initial_shape
    out /= np.sqrt(probs[result])

    return measurement_bits, out
示例#24
0
def apply_unitary_to_tensor(
    val: Any,
    target_tensor: np.ndarray,
    available_buffer: np.ndarray,
    axes: Sequence[int],
    default: TDefault = RaiseTypeErrorIfNotProvided
) -> Union[np.ndarray, TDefault]:
    """High performance left-multiplication of a unitary effect onto a tensor.

    If `val` defines an _apply_unitary_to_tensor_ method, that method will be
    used to apply `val`'s unitary effect to the target tensor. Otherwise, if
    `val` defines a _unitary_ method, its unitary matrix will be retrieved and
    applied using a generic method. Otherwise the application fails, and either
    an exception is raised or the specified default value is returned.

        The target may represent a wavefunction, a unitary matrix, or some other
        tensor. Implementations will work in all of these cases as long as they
        correctly focus on only operating on the given axes. See also:
        `cirq.slice_for_qubits_equal_to(axes, int)`, which does the correct
        thing in all these cases.

    Args:
        val: The value with a unitary effect to apply to the target tensor.
        target_tensor: The input tensor that needs to be left-multiplied by
            the unitary effect of `val`. Note that this value may be mutated
            inline into the output. The tensor will have the shape
            (2, 2, 2, ..., 2). target_tensor may correspond to a multi-qubit
            superposition (with each axis being a qubit), a multi-qubit unitary
            transformation (with some axes being qubit inputs and others being
            qubit outputs), or some other concept.
        available_buffer: Pre-allocated workspace with the same shape and
            dtype as the target tensor. Note that the output may be written
            into this buffer.
        axes: Which axes the unitary effect is being applied to (e.g. the
            qubits that the gate is operating on). For example, a CNOT being
            applied to qubits #4 and #2 of a circuit would result in
            axes=(4, 2).
        default: What should be returned if `val` doesn't have a unitary effect.
            If not specified, a TypeError is raised instead of returning
            a default value.

    Returns:
        If the receiving object is not able to apply its unitary effect,
        the specified default value is returned (or a TypeError is raised).

        If the receiving object was able to work inline, directly
        mutating target_tensor it will return target_tensor. The caller is
        responsible for checking if the result is target_tensor.

        If the receiving object wrote its output over available_buffer, the
        result will be available_buffer. The caller is responsible for
        checking if the result is available_buffer (and e.g. swapping
        the buffer for the target tensor before the next call).

        The receiving object may also write its output over a new buffer
        that it created, in which case that new array is returned.

    Raises:
        TypeError: `val` doesn't have a unitary effect and `default` wasn't
            specified.
    """

    # Check if the specialized method is present.
    getter = getattr(val, '_apply_unitary_to_tensor_', None)
    if getter is not None:
        result = getter(target_tensor, available_buffer, axes)
        if result is not NotImplemented:
            return result

    # Fallback to using the object's _unitary_ matrix.
    matrix = unitary(val, None)
    if matrix is not None:
        # Special case for single-qubit operations.
        if matrix.shape == (2, 2):
            zero = linalg.slice_for_qubits_equal_to(axes, 0)
            one = linalg.slice_for_qubits_equal_to(axes, 1)
            return linalg.apply_matrix_to_slices(target_tensor,
                                                 matrix, [zero, one],
                                                 out=available_buffer)

        # Fallback to np.einsum for the general case.
        return linalg.targeted_left_multiply(matrix.astype(
            target_tensor.dtype).reshape((2, ) * (2 * len(axes))),
                                             target_tensor,
                                             axes,
                                             out=available_buffer)

    # Don't know how to apply. Fallback to specified default behavior.
    if default is not RaiseTypeErrorIfNotProvided:
        return default
    raise TypeError("object of type '{}' "
                    "has no _apply_unitary_to_tensor_ "
                    "or _unitary_ methods "
                    "(or they returned NotImplemented).".format(type(val)))