Exemplo n.º 1
0
def test_is_unitary_tolerance():
    tol = Tolerance(atol=0.5)

    # Pays attention to specified tolerance.
    assert predicates.is_unitary(np.array([[1, 0], [-0.5, 1]]), tol)
    assert not predicates.is_unitary(np.array([[1, 0], [-0.6, 1]]), tol)

    # Error isn't accumulated across entries.
    assert predicates.is_unitary(
        np.array([[1.2, 0, 0], [0, 1.2, 0], [0, 0, 1.2]]), tol)
    assert not predicates.is_unitary(
        np.array([[1.2, 0, 0], [0, 1.3, 0], [0, 0, 1.2]]), tol)
def bidiagonalize_unitary_with_special_orthogonals(
    mat: np.ndarray,
    *,
    rtol: float = 1e-5,
    atol: float = 1e-8,
    check_preconditions: bool = True
) -> Tuple[np.ndarray, np.array, np.ndarray]:
    """Finds orthogonal matrices L, R such that L @ matrix @ R is diagonal.

    Args:
        mat: A unitary matrix.
        rtol: Relative numeric error threshold.
        atol: Absolute numeric error threshold.
        check_preconditions: If set, verifies that the input is a unitary matrix
            (to the given tolerances). Defaults to set.

    Returns:
        A triplet (L, d, R) such that L @ mat @ R = diag(d). Both L and R will
        be orthogonal matrices with determinant equal to 1.

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not real).
    """

    if check_preconditions:
        if not predicates.is_unitary(mat, rtol=rtol, atol=atol):
            raise ValueError('matrix must be unitary.')

    # Note: Because mat is unitary, setting A = real(mat) and B = imag(mat)
    # guarantees that both A @ B.T and A.T @ B are Hermitian.
    left, right = bidiagonalize_real_matrix_pair_with_symmetric_products(
        np.real(mat),
        np.imag(mat),
        rtol=rtol,
        atol=atol,
        check_preconditions=check_preconditions)

    # Convert to special orthogonal w/o breaking diagonalization.
    if np.linalg.det(left) < 0:
        left[0, :] *= -1
    if np.linalg.det(right) < 0:
        right[:, 0] *= -1

    diag = combinators.dot(left, mat, right)

    return left, np.diag(diag), right
Exemplo n.º 3
0
def axis_angle(single_qubit_unitary: np.ndarray) -> AxisAngleDecomposition:
    """Decomposes a single-qubit unitary into axis, angle, and global phase.

    Args:
        single_qubit_unitary: The unitary of the single-qubit operation to
            decompose.

    Returns:
        An AxisAngleDecomposition equivalent to the given unitary.
    """
    u = single_qubit_unitary
    assert u.shape == (2, 2)
    assert predicates.is_unitary(single_qubit_unitary, atol=1e-8)

    # Extract phased quaternion components.
    [a, b], [c, d] = u
    wp = (a + d) / 2
    xp = (b + c) / 2j
    yp = (b - c) / 2
    zp = (a - d) / 2j

    # Extract global phase factor from largest component.
    p = max(wp, xp, yp, zp, key=abs)
    p /= abs(p)

    # Cancel global phase factor, pushing components onto the real line.
    w = min(1, max(-1, np.real(wp / p)))
    x = np.real(xp / p)
    y = np.real(yp / p)
    z = np.real(zp / p)
    angle = -2 * math.acos(w)

    # Normalize axis.
    n = math.sqrt(x * x + y * y + z * z)
    if n < 0.0000001:
        # There's an axis singularity near θ=0.
        # Default to no rotation around the X axis.
        return AxisAngleDecomposition(global_phase=p, angle=0, axis=(1, 0, 0))
    x /= n
    y /= n
    z /= n

    return AxisAngleDecomposition(axis=(x, y, z), angle=angle,
                                  global_phase=p).canonicalize()
Exemplo n.º 4
0
def bidiagonalize_unitary_with_special_orthogonals(
        mat: np.ndarray,
        tolerance: Tolerance = Tolerance.DEFAULT
) -> Tuple[np.ndarray, np.array, np.ndarray]:
    """Finds orthogonal matrices L, R such that L @ matrix @ R is diagonal.

    Args:
        mat: A unitary matrix.
        tolerance: Numeric error thresholds.

    Returns:
        A triplet (L, d, R) such that L @ mat @ R = diag(d). Both L and R will
        be orthogonal matrices with determinant equal to 1.

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not real).
        ArithmeticError: Failed to meet specified tolerance.
    """

    if not predicates.is_unitary(mat, tolerance):
        raise ValueError('matrix must be unitary.')

    # Note: Because mat is unitary, setting A = real(mat) and B = imag(mat)
    # guarantees that both A @ B.T and A.T @ B are Hermitian.
    left, right = bidiagonalize_real_matrix_pair_with_symmetric_products(
        np.real(mat),
        np.imag(mat),
        tolerance)

    # Convert to special orthogonal w/o breaking diagonalization.
    if np.linalg.det(left) < 0:
        left[0, :] *= -1
    if np.linalg.det(right) < 0:
        right[:, 0] *= -1

    diag = combinators.dot(left, mat, right)

    # Check acceptability vs tolerances.
    if not predicates.is_diagonal(diag, tolerance):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return left, np.diag(diag), right
Exemplo n.º 5
0
def bidiagonalize_unitary_with_special_orthogonals(
    mat: np.ndarray,
    tolerance: Tolerance = Tolerance.DEFAULT
) -> Tuple[np.ndarray, np.array, np.ndarray]:
    """Finds orthogonal matrices L, R such that L @ matrix @ R is diagonal.

    Args:
        mat: A unitary matrix.
        tolerance: Numeric error thresholds.

    Returns:
        A triplet (L, d, R) such that L @ mat @ R = diag(d). Both L and R will
        be orthogonal matrices with determinant equal to 1.

    Raises:
        ValueError: Matrices don't meet preconditions (e.g. not real).
        ArithmeticError: Failed to meet specified tolerance.
    """

    if not predicates.is_unitary(mat, tolerance):
        raise ValueError('matrix must be unitary.')

    # Note: Because mat is unitary, setting A = real(mat) and B = imag(mat)
    # guarantees that both A @ B.T and A.T @ B are Hermitian.
    left, right = bidiagonalize_real_matrix_pair_with_symmetric_products(
        np.real(mat), np.imag(mat), tolerance)

    # Convert to special orthogonal w/o breaking diagonalization.
    if np.linalg.det(left) < 0:
        left[0, :] *= -1
    if np.linalg.det(right) < 0:
        right[:, 0] *= -1

    diag = combinators.dot(left, mat, right)

    # Check acceptability vs tolerances.
    if not predicates.is_diagonal(diag, tolerance):
        raise ArithmeticError('Failed to diagonalize to specified tolerance.')

    return left, np.diag(diag), right
Exemplo n.º 6
0
def kak_decomposition(
    unitary_object: Union[np.ndarray, 'cirq.SupportsUnitary'],
    *,
    rtol: float = 1e-5,
    atol: float = 1e-8,
    check_preconditions: bool = True,
) -> KakDecomposition:
    """Decomposes a 2-qubit unitary into 1-qubit ops and XX/YY/ZZ interactions.

    Args:
        unitary_object: The value to decompose. Can either be a 4x4 unitary
            matrix, or an object that has a 4x4 unitary matrix (via the
            `cirq.SupportsUnitary` protocol).
        rtol: Per-matrix-entry relative tolerance on equality.
        atol: Per-matrix-entry absolute tolerance on equality.
        check_preconditions: If set, verifies that the input corresponds to a
            4x4 unitary before decomposing.

    Returns:
        A `cirq.KakDecomposition` canonicalized such that the interaction
        coefficients x, y, z satisfy:

            0 ≤ abs(z2) ≤ y2 ≤ x2 ≤ π/4
            if x2 = π/4, z2 >= 0

    Raises:
        ValueError: Bad matrix.
        ArithmeticError: Failed to perform the decomposition.

    References:
        'An Introduction to Cartan's KAK Decomposition for QC Programmers'
        https://arxiv.org/abs/quant-ph/0507171
    """
    if isinstance(unitary_object, KakDecomposition):
        return unitary_object
    if isinstance(unitary_object, np.ndarray):
        mat = unitary_object
    else:
        mat = protocols.unitary(unitary_object)
    if check_preconditions and (
            mat.shape !=
        (4, 4) or not predicates.is_unitary(mat, rtol=rtol, atol=atol)):
        raise ValueError(
            'Input must correspond to a 4x4 unitary matrix. Received matrix:\n'
            + str(mat))

    # Diagonalize in magic basis.
    left, d, right = diagonalize.bidiagonalize_unitary_with_special_orthogonals(
        KAK_MAGIC_DAG @ mat @ KAK_MAGIC,
        atol=atol,
        rtol=rtol,
        check_preconditions=False)

    # Recover pieces.
    a1, a0 = so4_to_magic_su2s(left.T,
                               atol=atol,
                               rtol=rtol,
                               check_preconditions=False)
    b1, b0 = so4_to_magic_su2s(right.T,
                               atol=atol,
                               rtol=rtol,
                               check_preconditions=False)
    w, x, y, z = (KAK_GAMMA @ np.vstack(np.angle(d))).flatten()
    g = np.exp(1j * w)

    # Canonicalize.
    inner_cannon = kak_canonicalize_vector(x, y, z)

    b1 = np.dot(inner_cannon.single_qubit_operations_before[0], b1)
    b0 = np.dot(inner_cannon.single_qubit_operations_before[1], b0)
    a1 = np.dot(a1, inner_cannon.single_qubit_operations_after[0])
    a0 = np.dot(a0, inner_cannon.single_qubit_operations_after[1])
    return KakDecomposition(
        interaction_coefficients=inner_cannon.interaction_coefficients,
        global_phase=g * inner_cannon.global_phase,
        single_qubit_operations_before=(b1, b0),
        single_qubit_operations_after=(a1, a0),
    )
Exemplo n.º 7
0
def test_is_unitary():
    assert predicates.is_unitary(np.empty((0, 0)))
    assert not predicates.is_unitary(np.empty((1, 0)))
    assert not predicates.is_unitary(np.empty((0, 1)))

    assert predicates.is_unitary(np.array([[1]]))
    assert predicates.is_unitary(np.array([[-1]]))
    assert predicates.is_unitary(np.array([[1j]]))
    assert not predicates.is_unitary(np.array([[5]]))
    assert not predicates.is_unitary(np.array([[3j]]))

    assert not predicates.is_unitary(np.array([[1, 0]]))
    assert not predicates.is_unitary(np.array([[1], [0]]))

    assert not predicates.is_unitary(np.array([[1, 0], [0, -2]]))
    assert predicates.is_unitary(np.array([[1, 0], [0, -1]]))
    assert predicates.is_unitary(np.array([[1j, 0], [0, 1]]))
    assert not predicates.is_unitary(np.array([[1, 0], [1, 1]]))
    assert not predicates.is_unitary(np.array([[1, 1], [0, 1]]))
    assert not predicates.is_unitary(np.array([[1, 1], [1, 1]]))
    assert not predicates.is_unitary(np.array([[1, -1], [1, 1]]))
    assert predicates.is_unitary(np.array([[1, -1], [1, 1]]) * np.sqrt(0.5))
    assert predicates.is_unitary(np.array([[1, 1j], [1j, 1]]) * np.sqrt(0.5))
    assert not predicates.is_unitary(
        np.array([[1, -1j], [1j, 1]]) * np.sqrt(0.5))

    assert predicates.is_unitary(
        np.array([[1, 1j + 1e-11], [1j, 1 + 1j * 1e-9]]) * np.sqrt(0.5))