Exemple #1
0
def test_expw_matrix(half_turns):
    if (version[0] == 0 and version[1] <= 3):
        nptest.assert_array_almost_equal(xmon_gates.Exp11Gate(half_turns=half_turns).matrix,
                                google.Exp11Gate(half_turns=half_turns).matrix())
    else:
        nptest.assert_array_almost_equal(xmon_gates.Exp11Gate(half_turns=half_turns).matrix,
                                unitary(ops.CZPowGate(exponent=half_turns)))
Exemple #2
0
def test_expw_matrix(half_turns, axis_half_turns):
    m1 = xmon_gates.ExpWGate(half_turns=half_turns,
                                axis_half_turns=axis_half_turns).matrix
    if (version[0] == 0 and version[1] <= 3):
        m2 = google.ExpWGate(half_turns=half_turns,
                            axis_half_turns=axis_half_turns).matrix()
    else:
         m2 = unitary(ops.PhasedXPowGate(exponent=half_turns,
                            phase_exponent=axis_half_turns))
    nptest.assert_array_almost_equal(m1, m2)
Exemple #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(
                'No Pauli expansion for object {} of type {}'.format(
                    val, type(val)))
        return default

    matrix = unitary(val, default=None)
    if matrix is None:
        if default is RaiseTypeErrorIfNotProvided:
            raise TypeError(
                'No Pauli expansion for object {} of type {}'.format(
                    val, 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)
Exemple #4
0
def _strat_distance_from_unitary(val: Any) -> Optional[float]:
    """Attempts to compute a value's trace_distance_bound from its unitary."""
    u = unitary(val, default=None)

    if u is None:
        return NotImplemented

    if u.shape == (2, 2):
        squared = 1 - (0.5 * abs(u[0][0] + u[1][1]))**2
        if squared <= 0:
            return 0.0
        return squared**0.5

    return trace_distance_from_angle_list(np.angle(np.linalg.eigvals(u)))
def pauli_expansion(val: Any,
                    *,
                    default: Optional[Dict[
                        str, complex]] = RaiseTypeErrorIfNotProvided,
                    tolerance: float = 1e-9) -> Optional[Dict[str, complex]]:
    """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.
        tolerance: 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 _filter_coefficients(expansion, tolerance)

    matrix = unitary(val, default=None)
    if matrix is None:
        if default is RaiseTypeErrorIfNotProvided:
            raise TypeError(
                'No Pauli expansion for object {} of type {}'.format(
                    val, 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 _filter_coefficients(expansion, tolerance)
Exemple #6
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)))
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)))