示例#1
0
    def for_gate(val: Any, start: int = 0, step: int = 1) -> List['LineQid']:
        """Returns a range of line qids with the same qid shape as the gate.

        Args:
            val: Any value that supports the `cirq.qid_shape` protocol.  Usually
                a gate.
            start: The x coordinate of the first `LineQid`.
            step: The amount to increment each x coordinate.
        """
        # Avoids circular import.
        from cirq.protocols.qid_shape_protocol import qid_shape

        return LineQid.for_qid_shape(qid_shape(val), start=start, step=step)
示例#2
0
def _strat_apply_unitary_from_apply_unitary(
        unitary_value: Any, args: ApplyUnitaryArgs) -> Optional[np.ndarray]:
    # Check for magic method.
    func = getattr(unitary_value, '_apply_unitary_', None)
    if func is None:
        return NotImplemented
    op_qid_shape = qid_shape_protocol.qid_shape(unitary_value,
                                                (2, ) * len(args.axes))
    sub_args = args._for_operation_with_qid_shape(range(len(op_qid_shape)),
                                                  op_qid_shape)
    sub_result = func(sub_args)
    if sub_result is NotImplemented or sub_result is None:
        return sub_result
    return _incorporate_result_into_target(args, sub_args, sub_result)
示例#3
0
def pauli_expansion(
    val: Any,
    *,
    default: Union[value.LinearDict[str], TDefault] = RaiseTypeErrorIfNotProvided,
    atol: float = 1e-9,
) -> Union[value.LinearDict[str], TDefault]:
    """Returns coefficients of the expansion of val in the Pauli basis.

    Args:
        val: The value whose Pauli expansion is to returned.
        default: Determines what happens when `val` does not have methods that
            allow Pauli expansion to be obtained (see below). If set, the value
            is returned in that case. Otherwise, TypeError is raised.
        atol: Ignore coefficients whose absolute value is smaller than this.

    Returns:
        If `val` has a _pauli_expansion_ method, then its result is returned.
        Otherwise, if `val` has a small unitary then that unitary is expanded
        in the Pauli basis and coefficients are returned. Otherwise, if default
        is set to None or other value then default is returned. Otherwise,
        TypeError is raised.

    Raises:
        TypeError if `val` has none of the methods necessary to obtain its Pauli
        expansion and no default value has been provided.
    """
    method = getattr(val, '_pauli_expansion_', None)
    expansion = NotImplemented if method is None else method()

    if expansion is not NotImplemented:
        return expansion.clean(atol=atol)

    # Don't attempt to derive the pauli expansion if this is a qudit gate
    if not all(d == 2 for d in qid_shape_protocol.qid_shape(val, default=())):
        if default is RaiseTypeErrorIfNotProvided:
            raise TypeError(f'No Pauli expansion for object {val} of type {type(val)}')
        return default

    matrix = unitary_protocol.unitary(val, default=None)
    if matrix is None:
        if default is RaiseTypeErrorIfNotProvided:
            raise TypeError(f'No Pauli expansion for object {val} of type {type(val)}')
        return default

    num_qubits = matrix.shape[0].bit_length() - 1
    basis = operator_spaces.kron_bases(operator_spaces.PAULI_BASIS, repeat=num_qubits)

    expansion = operator_spaces.expand_matrix_in_orthogonal_basis(matrix, basis)
    return expansion.clean(atol=atol)
示例#4
0
def _strat_has_unitary_from_apply_unitary(val: Any) -> Optional[bool]:
    """Attempts to infer a value's unitary-ness via its _apply_unitary_ method."""
    method = getattr(val, '_apply_unitary_', None)
    if method is None:
        return None

    val_qid_shape = qid_shape_protocol.qid_shape(val, None)
    if val_qid_shape is None:
        return None
    state = qis.one_hot(shape=val_qid_shape, dtype=np.complex64)
    buffer = np.empty_like(state)
    result = method(ApplyUnitaryArgs(state, buffer, range(len(val_qid_shape))))
    if result is NotImplemented:
        return None
    return result is not None
示例#5
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

    if args.slices is None:
        val_qid_shape = qid_shape_protocol.qid_shape(unitary_value,
                                                     default=(2, ) *
                                                     len(args.axes))
        slices = tuple(slice(0, size) for size in val_qid_shape)
    else:
        slices = args.slices
        val_qid_shape = tuple(
            ((s.step if s.stop is None else s.stop) - s.start) // (s.step or 1)
            for s in slices)
    sub_args = args._for_operation_with_qid_shape(range(len(slices)), slices)
    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)
示例#6
0
def _strat_unitary_from_apply_unitary(val: Any) -> Optional[np.ndarray]:
    """Attempts to compute a value's unitary via its _apply_unitary_ method."""
    # Check for the magic method.
    method = getattr(val, '_apply_unitary_', None)
    if method is None:
        return NotImplemented

    # Get the qid_shape.
    val_qid_shape = qid_shape_protocol.qid_shape(val, None)
    if val_qid_shape is None:
        return NotImplemented

    # Apply unitary effect to an identity matrix.
    state = qis.eye_tensor(val_qid_shape, dtype=np.complex128)
    buffer = np.empty_like(state)
    result = method(ApplyUnitaryArgs(state, buffer, range(len(val_qid_shape))))

    if result is NotImplemented or result is None:
        return result
    state_len = np.prod(val_qid_shape, dtype=np.int64)
    return result.reshape((state_len, state_len))
示例#7
0
def _strat_has_unitary_from_apply_unitary(val: Any) -> Optional[bool]:
    """Attempts to infer a value's unitary-ness via its _apply_unitary_ method.
    """
    from cirq.protocols.apply_unitary import ApplyUnitaryArgs
    from cirq import devices, linalg, ops

    method = getattr(val, '_apply_unitary_', None)
    if method is None:
        return None
    if isinstance(val, ops.Gate):
        val = val.on(*devices.LineQubit.range(val.num_qubits()))
    if not isinstance(val, ops.Operation):
        return None

    val_qid_shape = qid_shape_protocol.qid_shape(val)
    state = linalg.one_hot(shape=val_qid_shape, dtype=np.complex64)
    buffer = np.empty_like(state)
    result = method(ApplyUnitaryArgs(state, buffer, range(len(val_qid_shape))))
    if result is NotImplemented:
        return None
    return result is not None
示例#8
0
def apply_channel(val: Any,
                  args: ApplyChannelArgs,
                  default: TDefault = RaiseTypeErrorIfNotProvided
                 ) -> Union[np.ndarray, TDefault]:
    """High performance evolution under a channel evolution.

    If `val` defines an `_apply_channel_` method, that method will be
    used to apply `val`'s channel effect to the target tensor. Otherwise, if
    `val` defines an `_apply_unitary_` method, that method will be used to
    apply `val`s channel effect to the target tensor.  Otherwise, if `val`
    returns a non-default channel with `cirq.channel`, that channel will be
    applied using a generic method.  If none of these cases apply, an
    exception is raised or the specified default value is returned.


    Args:
        val: The value with a channel to apply to the target.
        args: A mutable `cirq.ApplyChannelArgs` object describing the target
            tensor, available workspace, and left and right axes to operate on.
            The attributes of this object will be mutated as part of computing
            the result.
        default: What should be returned if `val` doesn't have a channel. If
            not specified, a TypeError is raised instead of returning a default
            value.

    Returns:
        If the receiving object is not able to apply a channel,
        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 `out_buffer`, the
        result will be `out_buffer`. The caller is responsible for
        checking if the result is `out_buffer` (and e.g. swapping
        the buffer for the target tensor before the next call).

        Note that it is an error for the return object to be either of the
        auxiliary buffers, and the method will raise an AssertionError if
        this contract is violated.

        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 channel and `default` wasn't specified.
        ValueError: Different left and right shapes of `args.target_tensor`
            selected by `left_axes` and `right_axes` or `qid_shape(val)` doesn't
            equal the left and right shapes.
        AssertionError: `_apply_channel_` returned an auxiliary buffer.
    """
    # Verify that val has the same qid shape as the selected axes of the density
    # matrix tensor.
    val_qid_shape = qid_shape_protocol.qid_shape(val,
                                                 (2,) * len(args.left_axes))
    left_shape = tuple(args.target_tensor.shape[i] for i in args.left_axes)
    right_shape = tuple(args.target_tensor.shape[i] for i in args.right_axes)
    if left_shape != right_shape:
        raise ValueError('Invalid target_tensor shape or selected axes. '
                         'The selected left and right shape of target_tensor '
                         'are not equal. Got {!r} and {!r}.'.format(
                             left_shape, right_shape))
    if val_qid_shape != left_shape:
        raise ValueError('Invalid channel qid shape is not equal to the '
                         'selected left and right shape of target_tensor. '
                         'Got {!r} but expected {!r}.'.format(
                             val_qid_shape, left_shape))

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

            def err_str(buf_num_str):
                return ("Object of type '{}' returned a result object equal to "
                        "auxiliary_buffer{}. This type violates the contract "
                        "that appears in apply_channel's documentation.".format(
                            type(val), buf_num_str))

            assert result is not args.auxiliary_buffer0, err_str('0')
            assert result is not args.auxiliary_buffer1, err_str('1')
            return result

    # Possibly use `apply_unitary`.
    result = _apply_unitary(val, args)
    if result is not None:
        return result

    # Fallback to using the object's `_channel_` matrices.
    kraus = channel(val, None)
    if kraus is not None:
        return _apply_kraus(kraus, args)

    # Don't know how to apply channel. Fallback to specified default behavior.
    if default is not RaiseTypeErrorIfNotProvided:
        return default
    raise TypeError(
        "object of type '{}' has no _apply_channel_, _apply_unitary_, "
        "_unitary_, or _channel_ methods (or they returned None or "
        "NotImplemented).".format(type(val)))
示例#9
0
def apply_unitaries(
        unitary_values: Iterable[Any],
        qubits: Sequence['cirq.Qid'],
        args: Optional[ApplyUnitaryArgs] = None,
        default: Any = RaiseTypeErrorIfNotProvided) -> Optional[np.ndarray]:
    """Apply a series of unitaries onto a state tensor.

    Uses `cirq.apply_unitary` on each of the unitary values, to apply them to
    the state tensor from the `args` argument.

    CAUTION: if one of the given unitary values does not have a unitary effect,
    forcing the method to terminate, the method will not rollback changes
    from previous unitary values.

    Args:
        unitary_values: The values with unitary effects to apply to the target.
        qubits: The qubits that will be targeted by the unitary values. These
            qubits match up, index by index, with the `indices` property of the
            `args` argument.
        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. If
            not specified, this defaults to the zero state of the given qubits
            with an axis ordering matching the given qubit ordering.
        default: What should be returned if any of the unitary values actually
            don't have a unitary effect. If not specified, a TypeError is
            raised instead of returning a default value.

    Returns:
        If any of the unitary values do not have a unitary effect, the
        specified default value is returned (or a TypeError is raised).
        CAUTION: If this occurs, the contents of `args.target_tensor`
        and `args.available_buffer` may have been mutated.

        If all of the unitary values had a unitary effect that was
        successfully applied, this method returns the `np.ndarray`
        storing the final result. This `np.ndarray` may be
        `args.target_tensor`, `args.available_buffer`, or some
        other instance. The caller is responsible for dealing with
        this potential aliasing of the inputs and the result.

    Raises:
        TypeError: An item from `unitary_values` doesn't have a unitary effect
            and `default` wasn't specified.
    """
    if args is None:
        qid_shape = qid_shape_protocol.qid_shape(qubits)
        args = ApplyUnitaryArgs.default(qid_shape=qid_shape)
    if len(qubits) != len(args.axes):
        raise ValueError('len(qubits) != len(args.axes)')
    qubit_map = {
        q.with_dimension(1): args.axes[i]
        for i, q in enumerate(qubits)
    }
    state = args.target_tensor
    buffer = args.available_buffer

    for op in unitary_values:
        indices = [qubit_map[q.with_dimension(1)] for q in op.qubits]
        result = apply_unitary(unitary_value=op,
                               args=ApplyUnitaryArgs(state, buffer, indices),
                               default=None)

        # Handle failure.
        if result is None:
            if default is RaiseTypeErrorIfNotProvided:
                raise TypeError(
                    "cirq.apply_unitaries failed. "
                    "There was a non-unitary value in the `unitary_values` "
                    "list.\n"
                    "\n"
                    "non-unitary value type: {}\n"
                    "non-unitary value: {!r}".format(type(op), op))
            return default

        # Handle aliasing of results.
        if result is buffer:
            buffer = state
        state = result

    return state