def prepare_into_buffer(k: int):
     linalg.targeted_left_multiply(
         left_matrix=kraus_tensors[k],
         right_target=self._state_vector,
         target_axes=axes,
         out=self._buffer,
     )
示例#2
0
def _compute_samples_display_value(display: ops.SamplesDisplay,
        state: np.ndarray,
        qubit_order: ops.QubitOrder,
        qubit_map: Dict[ops.Qid, int]):
    n = len(qubit_map)
    state = np.reshape(state, (2,) * n * 2)
    basis_change = ops.flatten_op_tree(display.measurement_basis_change())
    for op in basis_change:
        # TODO: Use apply_channel similar to apply_unitary.
        indices = [qubit_map[qubit] for qubit in op.qubits]
        gate = cast(ops.GateOperation, op).gate
        unitary = protocols.unitary(gate)
        krauss_tensor = np.reshape(unitary,
                                   (2,) * gate.num_qubits() * 2)
        state = linalg.targeted_left_multiply(krauss_tensor,
                                               state,
                                               indices)
        # TODO add a test that fails if the below is not performed
        state = linalg.targeted_left_multiply(
            np.conjugate(krauss_tensor),
            state,
            [x + n for x in indices])
    state = state.reshape((2**n, 2**n))
    indices = [qubit_map[qubit] for qubit in display.qubits]
    samples = density_matrix_utils.sample_density_matrix(
        state, indices, display.num_samples)
    return display.value_derived_from_samples(samples)
 def prepare_into_buffer(k: int):
     linalg.targeted_left_multiply(
         left_matrix=kraus_tensors[k],
         right_target=args.target_tensor,
         target_axes=args.axes,
         out=args.available_buffer,
     )
    def apply_mixture(self, action: Any, axes: Sequence[int],
                      prng) -> Optional[int]:
        """Apply mixture to state.

        Args:
            action: The value with a mixture to apply.
            axes: The axes on which to apply the mixture.
            prng: The pseudo random number generator to use.
        Returns:
            The mixture index if the operation succeeded, otherwise None.
        """
        mixture = protocols.mixture(action, default=None)
        if mixture is None:
            return None
        probabilities, unitaries = zip(*mixture)

        index = prng.choice(range(len(unitaries)), p=probabilities)
        shape = protocols.qid_shape(action) * 2
        unitary = unitaries[index].astype(
            self._state_vector.dtype).reshape(shape)
        linalg.targeted_left_multiply(unitary,
                                      self._state_vector,
                                      axes,
                                      out=self._buffer)
        self._swap_target_tensor_for(self._buffer)
        return index
def _apply_unitary_from_matrix_strat(
        val: np.ndarray, args: 'ApplyMixtureArgs',
        is_density_matrix: bool) -> Optional[np.ndarray]:
    """Used to enact mixture tuples that are given as (probability, np.ndarray)

    If `val` does not support `apply_unitary` returns None.
    """
    qid_shape = tuple(args.target_tensor.shape[i] for i in args.left_axes)
    matrix_tensor = np.reshape(val.astype(args.target_tensor.dtype),
                               qid_shape * 2)
    linalg.targeted_left_multiply(matrix_tensor,
                                  args.target_tensor,
                                  args.left_axes,
                                  out=args.auxiliary_buffer0)

    if not is_density_matrix:
        return args.auxiliary_buffer0
    # No need to transpose as we are acting on the tensor
    # representation of matrix, so transpose is done for us.
    linalg.targeted_left_multiply(
        np.conjugate(matrix_tensor),
        args.auxiliary_buffer0,
        cast(Tuple[int], args.right_axes),
        out=args.target_tensor,
    )
    return args.target_tensor
示例#6
0
def _strat_apply_unitary_from_unitary(
        unitary_value: Any, args: ApplyUnitaryArgs) -> Optional[np.ndarray]:
    # Check for magic method.
    method = getattr(unitary_value, '_unitary_', None)
    if method is None:
        return NotImplemented

    # Attempt to get the unitary matrix.
    matrix = method()
    if matrix is NotImplemented or matrix is None:
        return matrix

    val_qid_shape = qid_shape_protocol.qid_shape(unitary_value,
                                                 default=(2, ) *
                                                 len(args.axes))
    sub_args = args._for_operation_with_qid_shape(range(len(val_qid_shape)),
                                                  val_qid_shape)
    matrix = matrix.astype(sub_args.target_tensor.dtype)
    if len(val_qid_shape) == 1 and val_qid_shape[0] <= 2:
        # Special case for single-qubit, 2x2 or 1x1 operations.
        # np.einsum is faster for larger cases.
        subspaces = [(..., level) for level in range(val_qid_shape[0])]
        sub_result = linalg.apply_matrix_to_slices(
            sub_args.target_tensor,
            matrix,
            subspaces,
            out=sub_args.available_buffer)
    else:
        # General case via np.einsum.
        sub_result = linalg.targeted_left_multiply(
            matrix.reshape(val_qid_shape * 2),
            sub_args.target_tensor,
            sub_args.axes,
            out=sub_args.available_buffer)
    return _incorporate_result_into_target(args, sub_args, sub_result)
示例#7
0
def _strat_apply_unitary_from_unitary(
        unitary_value: Any, args: ApplyUnitaryArgs) -> Optional[np.ndarray]:
    # Check for magic method.
    method = getattr(unitary_value, '_unitary_', None)
    if method is None:
        return NotImplemented

    # Attempt to get the unitary matrix.
    matrix = method()
    if matrix is NotImplemented or matrix is None:
        return matrix

    # Special case for single-qubit operations.
    if matrix.shape == (2, 2):
        zero = args.subspace_index(0)
        one = args.subspace_index(1)
        return linalg.apply_matrix_to_slices(args.target_tensor,
                                             matrix, [zero, one],
                                             out=args.available_buffer)

    # General case via np.einsum.
    return linalg.targeted_left_multiply(matrix.astype(
        args.target_tensor.dtype).reshape((2, ) * (2 * len(args.axes))),
                                         args.target_tensor,
                                         args.axes,
                                         out=args.available_buffer)
示例#8
0
def _strat_act_on_state_vector_from_mixture(
        action: Any, args: 'cirq.ActOnStateVectorArgs') -> bool:
    mixture = protocols.mixture(action, default=None)
    if mixture is None:
        return NotImplemented
    probabilities, unitaries = zip(*mixture)

    index = args.prng.choice(range(len(unitaries)), p=probabilities)
    shape = protocols.qid_shape(action) * 2
    unitary = unitaries[index].astype(args.target_tensor.dtype).reshape(shape)
    linalg.targeted_left_multiply(unitary,
                                  args.target_tensor,
                                  args.axes,
                                  out=args.available_buffer)
    args.swap_target_tensor_for(args.available_buffer)
    return True
示例#9
0
def _apply_krauss_multi_qubit(krauss: Union[Tuple[Any], Sequence[Any]],
                              args: 'ApplyChannelArgs') -> np.ndarray:
    """Use numpy's einsum to apply a multi-qubit channel."""
    for krauss_op in krauss:
        np.copyto(dst=args.target_tensor, src=args.auxiliary_buffer0)
        krauss_tensor = np.reshape(krauss_op.astype(args.target_tensor.dtype),
                                   (2, ) * len(args.left_axes) * 2)
        linalg.targeted_left_multiply(krauss_tensor,
                                      args.target_tensor,
                                      args.left_axes,
                                      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.targeted_left_multiply(np.conjugate(krauss_tensor),
                                      args.auxiliary_buffer1,
                                      args.right_axes,
                                      out=args.target_tensor)
        args.out_buffer += args.target_tensor
    return args.out_buffer
示例#10
0
def _strat_act_on_state_vector_from_mixture(
        action: Any, args: 'cirq.ActOnStateVectorArgs',
        qubits: Sequence['cirq.Qid']) -> bool:
    mixture = protocols.mixture(action, default=None)
    if mixture is None:
        return NotImplemented
    probabilities, unitaries = zip(*mixture)

    index = args.prng.choice(range(len(unitaries)), p=probabilities)
    shape = protocols.qid_shape(action) * 2
    unitary = unitaries[index].astype(args.target_tensor.dtype).reshape(shape)
    linalg.targeted_left_multiply(unitary,
                                  args.target_tensor,
                                  args.get_axes(qubits),
                                  out=args.available_buffer)
    args.swap_target_tensor_for(args.available_buffer)
    if protocols.is_measurement(action):
        key = protocols.measurement_key_name(action)
        args._classical_data.record_channel_measurement(key, index)
    return True
示例#11
0
 def _simulate_mixture(self, op: ops.Operation, data: _StateAndBuffer,
         indices: List[int]) -> None:
     """Simulate an op that is a mixtures of unitaries."""
     probs, unitaries = zip(*protocols.mixture(op))
     # We work around numpy barfing on choosing from a list of
     # numpy arrays (which is not `one-dimensional`) by selecting
     # the index of the unitary.
     index = np.random.choice(range(len(unitaries)), p=probs)
     shape = (2,) * (2 * len(indices))
     unitary = unitaries[index].astype(self._dtype).reshape(shape)
     result = linalg.targeted_left_multiply(unitary, data.state, indices,
                                            out=data.buffer)
     data.buffer = data.state
     data.state = result
示例#12
0
def apply_unitary(
    unitary_value: Any,
    args: ApplyUnitaryArgs,
    default: TDefault = RaiseTypeErrorIfNotProvided
) -> Union[np.ndarray, TDefault]:
    """High performance left-multiplication of a unitary effect onto a tensor.

    If `unitary_value` defines an `_apply_unitary_` method, that method will be
    used to apply `unitary_value`'s unitary effect to the target tensor.
    Otherwise, if `unitary_value` 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.

    Args:
        unitary_value: The value with a unitary effect to apply to the target.
        args: A mutable `cirq.ApplyUnitaryArgs` object describing the target
            tensor, available workspace, and axes to operate on. The attributes
            of this object will be mutated as part of computing the result.
        default: What should be returned if `unitary_value` 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
        this occurs, then `target_tensor` should not have been mutated.

        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: `unitary_value` doesn't have a unitary effect and `default`
            wasn't specified.
    """

    # Check if the specialized method is present.
    func = getattr(unitary_value, '_apply_unitary_', None)
    if func is not None:
        result = func(args)
        if result is not NotImplemented and result is not None:
            return result

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

        # Fallback to np.einsum for the general case.
        return linalg.targeted_left_multiply(matrix.astype(
            args.target_tensor.dtype).reshape((2, ) * (2 * len(args.axes))),
                                             args.target_tensor,
                                             args.axes,
                                             out=args.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_ or _unitary_ methods "
        "(or they returned None or NotImplemented).".format(
            type(unitary_value)))
示例#13
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)))
示例#14
0
def _circuit_as_layers(circuit: circuits.Circuit,
                       grouping: _QubitGrouping) -> List[_TransformsThenCzs]:
    """Transforms a circuit into a series of GroupMatrix+CZ layers.

    Args:
        circuit: The circuit to transform.
        grouping: How the circuit's qubits are combined into groups.

    Returns:
        A list of layers. Each layer has a matrix to apply to each group of
        qubits, and a list of CZs to apply to pairs of qubits crossing
        between groups.
    """
    frontier = {q: 0 for q in circuit.all_qubits()}

    layers = []
    while True:
        # Pull within-group operations into per-group matrices.
        any_group_matrices = False
        group_matrices = []
        for g in grouping.groups:
            # Scan for reachable operations contained within the group qubits.
            start_frontier = {q: frontier[q] for q in g}
            end_frontier = circuit.reachable_frontier_from(start_frontier)
            mergeable_ops = circuit.findall_operations_between(
                start_frontier, end_frontier)

            # Advance frontier.
            for q, v in end_frontier.items():
                frontier[q] = v

            # Fold reachable operations into a single group matrix.
            group_matrix = np.eye(1 << len(g)).reshape((2, 2) * len(g))
            if mergeable_ops:
                any_group_matrices = True
            for _, op in mergeable_ops:
                group_matrix = linalg.targeted_left_multiply(
                    left_matrix=protocols.unitary(op).reshape(
                        (2, 2) * len(op.qubits)),
                    right_target=group_matrix,
                    target_axes=[grouping.loc(q)[1] for q in op.qubits])
            group_matrices.append(
                np.transpose(group_matrix.reshape(1 << len(g), 1 << len(g))))

        # Scan for reachable CZ operations between groups.
        end_frontier = circuit.reachable_frontier_from(
            frontier,
            is_blocker=lambda op: grouping.all_in_same_group(*op.qubits))
        cz_ops = circuit.findall_operations_between(frontier, end_frontier)

        # Advance frontier.
        frontier = end_frontier

        # List out qubit index pairs for each CZ.
        cz_indices = []
        for _, cz in cz_ops:
            a, b = cz.qubits
            assert cz == ops.CZ(a, b)
            cz_indices.append((grouping.ind(a), grouping.ind(b)))

        # Combine group and CZ operations into a simulation layer.
        if not any_group_matrices and not cz_indices:
            break
        layer = _TransformsThenCzs(group_matrices=group_matrices,
                                   cz_indices=cz_indices)
        layers.append(layer)

    # We should have processed the whole circuit.
    assert frontier == {q: len(circuit) for q in circuit.all_qubits()}

    return layers
示例#15
0
    def _base_iterator(self,
                       circuit: circuits.Circuit,
                       qubit_order: ops.QubitOrderOrList,
                       initial_state: Union[int, np.ndarray],
                       perform_measurements: bool = True) -> Iterator:
        qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(
            circuit.all_qubits())
        num_qubits = len(qubits)
        qubit_map = {q: i for i, q in enumerate(qubits)}
        matrix = density_matrix_utils.to_valid_density_matrix(
            initial_state, num_qubits, self._dtype)
        if len(circuit) == 0:
            yield DensityMatrixStepResult(matrix, {}, qubit_map, self._dtype)
        matrix = np.reshape(matrix, (2, ) * num_qubits * 2)

        def on_stuck(bad_op: ops.Operation):
            return TypeError(
                "Can't simulate operations that don't implement "
                "SupportsUnitary, SupportsApplyUnitary, SupportsMixture, "
                "SupportsChannel or is a measurement: {!r}".format(bad_op))

        def keep(potential_op: ops.Operation) -> bool:
            return (protocols.has_channel(potential_op)
                    or ops.MeasurementGate.is_measurement(potential_op) or
                    isinstance(potential_op,
                               (ops.SamplesDisplay, ops.WaveFunctionDisplay)))

        matrix = np.reshape(matrix, (2, ) * num_qubits * 2)
        for moment in circuit:
            measurements = collections.defaultdict(
                list)  # type: Dict[str, List[bool]]

            channel_ops_and_measurements = protocols.decompose(
                moment.operations, keep=keep, on_stuck_raise=on_stuck)

            for op in channel_ops_and_measurements:
                indices = [qubit_map[qubit] for qubit in op.qubits]
                if isinstance(op,
                              (ops.SamplesDisplay, ops.WaveFunctionDisplay)):
                    continue
                elif ops.MeasurementGate.is_measurement(op):
                    meas = cast(ops.MeasurementGate,
                                cast(ops.GateOperation, op).gate)
                    if perform_measurements:
                        invert_mask = meas.invert_mask or num_qubits * (
                            False, )
                        # Measure updates inline.
                        bits, _ = density_matrix_utils.measure_density_matrix(
                            matrix, indices, matrix)
                        corrected = [
                            bit ^ mask for bit, mask in zip(bits, invert_mask)
                        ]
                        key = protocols.measurement_key(meas)
                        measurements[key].extend(corrected)
                else:
                    # TODO: Use apply_channel similar to apply_unitary.
                    gate = cast(ops.GateOperation, op).gate
                    channel = protocols.channel(gate)
                    sum_buffer = np.zeros((2, ) * 2 * num_qubits,
                                          dtype=self._dtype)
                    for krauss in channel:
                        krauss_tensor = np.reshape(krauss.astype(
                            self._dtype), (2, ) * gate.num_qubits() * 2)
                        buffer = linalg.targeted_left_multiply(
                            krauss_tensor, matrix, indices)
                        # No need to transpose as we are acting on the tensor
                        # representation of matrix, so transpose is done for us.
                        buffer = linalg.targeted_left_multiply(
                            np.conjugate(krauss_tensor), buffer,
                            [num_qubits + x for x in indices])
                        sum_buffer += buffer
                    np.copyto(dst=matrix, src=sum_buffer)
            yield DensityMatrixStepResult(density_matrix=matrix,
                                          measurements=measurements,
                                          qubit_map=qubit_map,
                                          dtype=self._dtype)