예제 #1
0
def decompose_4x4_optimal(U):
    """Builds optimal decomposition of general 4x4 unitary matrix.

    This decomposition consists of at most 3 CNOT gates, 15 Rx/Ry gates and one
    R1 gate.

    Returns list of `Gate`s.
    """
    assert is_unitary(U)

    magic_decomp = decompose_to_magic_diagonal(U)

    result = []
    result += apply_on_qubit(su_to_gates(magic_decomp['VA']), 1, 2)
    result += apply_on_qubit(su_to_gates(magic_decomp['VB']), 0, 2)
    result += decompose_magic_N(magic_decomp['alpha'])
    result += apply_on_qubit(su_to_gates(magic_decomp['UA']), 1, 2)
    result += apply_on_qubit(su_to_gates(magic_decomp['UB']), 0, 2)

    # Adding global phase using Rz and R1.
    gl_phase = magic_decomp['global_phase'] + 0.25 * np.pi
    if np.abs(gl_phase) > 1e-9:
        result.append(GateSingle(Gate2('Rz', 2 * gl_phase), 0, 2))
        result.append(GateSingle(Gate2('R1', 2 * gl_phase), 0, 2))

    result = optimize_gates(result)

    assert _allclose(U, gates_to_matrix(result))

    return result
예제 #2
0
def is_maximally_entangled_basis(A):
    """
    Checks if columns of matrx form a maximally entanfgled basis.

    Maximally entanfgled basis is basis consisting of maximally entangled
    orthonormal states.
    """
    assert A.shape == (4, 4)
    assert is_unitary(A)
    return all([is_maximally_entangled_state(A[:, i]) for i in range(4)])
예제 #3
0
    def __init__(self, matrix2x2, matrix_size, index1, index2):
        assert index1 != index2
        assert index1 < matrix_size and index2 < matrix_size
        assert matrix2x2.shape == (2, 2)
        assert is_unitary(matrix2x2)

        self.matrix_size = matrix_size
        self.index1 = index1
        self.index2 = index2
        self.matrix_2x2 = matrix2x2
        self.order_indices()
예제 #4
0
def two_level_decompose(A):
    """Returns list of two-level unitary matrices, which multiply to A.

    Matrices are listed in application order, i.e. if aswer is [u_1, u_2, u_3],
    it means A = u_3 u_2 u_1.
    """
    def make_eliminating_matrix(a, b):
        """Returns unitary matrix U, s.t. [a, b] U = [c, 0].

        Makes second element equal to zero.
        """
        assert (np.abs(a) > 1e-9 and np.abs(b) > 1e-9)
        theta = np.arctan(np.abs(b / a))
        lmbda = -np.angle(a)
        mu = np.pi + np.angle(b) - np.angle(a) - lmbda
        result = np.array([[
            np.cos(theta) * np.exp(1j * lmbda),
            np.sin(theta) * np.exp(1j * mu)
        ],
                           [
                               -np.sin(theta) * np.exp(-1j * mu),
                               np.cos(theta) * np.exp(-1j * lmbda)
                           ]])
        assert is_special_unitary(result)
        assert np.allclose(np.angle(result[0, 0] * a + result[1, 0] * b), 0)
        assert (np.abs(result[0, 1] * a + result[1, 1] * b) < 1e-9)
        return result

    assert is_unitary(A)
    n = A.shape[0]
    result = []
    # Make a copy, because we are going to mutate it.
    current_A = np.array(A)

    for i in range(n - 2):
        for j in range(n - 1, i, -1):
            if abs(current_A[i, j]) < 1e-9:
                # Element is already zero, skipping.
                pass
            else:
                if abs(current_A[i, j - 1]) < 1e-9:
                    # Just swap columns.
                    u_2x2 = PAULI_X
                else:
                    u_2x2 = make_eliminating_matrix(current_A[i, j - 1],
                                                    current_A[i, j])
                u_2x2 = TwoLevelUnitary(u_2x2, n, j - 1, j)
                u_2x2.multiply_right(current_A)
                result.append(u_2x2.inv())

    result.append(TwoLevelUnitary(current_A[n - 2:n, n - 2:n], n, n - 2,
                                  n - 1))
    return result
예제 #5
0
def unitary2x2_to_gates(A):
    """Decomposes 2x2 unitary to gates Ry, Rz, R1.

    R1(x) = diag(1, exp(i*x)).
    """
    assert is_unitary(A)
    phi = np.angle(np.linalg.det(A))
    if np.abs(phi) < 1e-9:
        return su_to_gates(A)
    elif np.allclose(A, PAULI_X):
        return [Gate2('X')]
    else:
        A = np.diag([1.0, np.exp(-1j * phi)]) @ A
        return su_to_gates(A) + [Gate2('R1', phi)]
예제 #6
0
def two_level_decompose_gray(A):
    """Retunrs list of two-level matrices, which multiplu to A.

    Guarantees that each matrix acts on single bit.
    """
    N = A.shape[0]
    assert is_power_of_two(N)
    assert A.shape == (N, N), "Matrix must be square."
    assert is_unitary(A)

    # Build permutation matrix.
    perm = [x ^ (x // 2) for x in range(N)]  # Gray code.
    P = np.zeros((N, N), dtype=np.complex128)
    for i in range(N):
        P[i][perm[i]] = 1

    result = two_level_decompose(P @ A @ P.T)
    for matrix in result:
        matrix.apply_permutation(perm)
    return result
예제 #7
0
def correct_phase(A, cur_phase):
    """Correct phases of 2x2 unitary to make it speical unitary."""
    assert is_unitary(A)
    n = A.shape[0]
    f = np.angle(np.linalg.det(A)) / n
    return A * np.exp(-1j * f), cur_phase + f
예제 #8
0
def decompose_to_magic_diagonal(U):
    """Decomposes unitary U as follows:
    U = e^(if) * (UAxUB) * N(alpha) * (VAxVB)
    f - global phase.
    UA, UB, VA, VB - 1-qubit _special_ unitaries (det=1).
    N(alpha) - "Magic N" matrix.

    Implements algorithm described in Appendix A in [2].
    """
    assert is_unitary(U)

    # Step 1 in [2].
    U_mb = Phi_dag @ U @ Phi
    Psi, eig_values = orthonormal_eigensystem(U_mb.T @ U_mb)
    eps = 0.5 * np.angle(eig_values)

    # Step 3 in [2].
    Psi_tilde = U_mb @ Psi @ np.diag(np.exp(-1j * eps))
    assert is_real(Psi_tilde)

    # Go back from magical to computational basis.
    Psi = Phi @ Psi
    Psi_tilde = Phi @ Psi_tilde
    assert is_maximally_entangled_basis(Psi)
    assert is_maximally_entangled_basis(Psi_tilde)

    # Step 2 in [2].
    VA, VB, xi = decompose_4x4_partial(Psi)

    # Step 4 in [2].
    UA_dag, UB_dag, lxe = decompose_4x4_partial(Psi_tilde)
    lmbda = lxe - xi - eps
    UA = UA_dag.conj().T
    UB = UB_dag.conj().T

    UAUB = np.kron(UA, UB)
    VAVB = np.kron(VA, VB)

    Ud = Phi @ np.diag(np.exp(-1j * lmbda)) @ Phi.conj().T

    assert np.allclose(VAVB @ Psi @ np.diag(np.exp(1j * xi)), Phi)
    assert np.allclose(U @ Psi @ np.diag(np.exp(-1j * eps)), Psi_tilde)
    assert np.allclose(
        UAUB.conj().T @ Psi_tilde @ np.diag(np.exp(1j * (eps + xi + lmbda))),
        Phi)
    assert _allclose(U, np.kron(UA, UB) @ Ud @ np.kron(VA, VB))

    # Restore coefficients of non-local unitary.
    gl_phase = -0.25 * np.sum(lmbda)
    alpha_x = -0.5 * (lmbda[0] + lmbda[2] + 2 * gl_phase)
    alpha_y = -0.5 * (lmbda[1] + lmbda[2] + 2 * gl_phase)
    alpha_z = -0.5 * (lmbda[0] + lmbda[1] + 2 * gl_phase)
    alpha = np.array([alpha_x, alpha_y, alpha_z])
    assert np.allclose(lmbda[0], -gl_phase - alpha_x + alpha_y - alpha_z)
    assert np.allclose(lmbda[1], -gl_phase + alpha_x - alpha_y - alpha_z)
    assert np.allclose(lmbda[2], -gl_phase - alpha_x - alpha_y + alpha_z)
    assert np.allclose(lmbda[3], -gl_phase + alpha_x + alpha_y + alpha_z)

    assert _allclose(Ud, np.exp(1j * gl_phase) * magic_N(alpha))
    assert _allclose(U, np.exp(1j * gl_phase) * UAUB @ magic_N(alpha) @ VAVB)

    UA, gl_phase = correct_phase(UA, gl_phase)
    UB, gl_phase = correct_phase(UB, gl_phase)
    VA, gl_phase = correct_phase(VA, gl_phase)
    VB, gl_phase = correct_phase(VB, gl_phase)

    for mx in [UA, UB, VA, VB]:
        assert np.allclose(np.linalg.det(mx), 1)

    UAUB = np.kron(UA, UB)
    VAVB = np.kron(VA, VB)
    assert _allclose(U, np.exp(1j * gl_phase) * UAUB @ magic_N(alpha) @ VAVB)

    return {
        'UA': UA,
        'UB': UB,
        'VA': VA,
        'VB': VB,
        'alpha': alpha,
        'global_phase': gl_phase,
    }
예제 #9
0
def decompose_4x4_partial(Psi):
    """Partially decomposes 4x4 unitary matrix.

    Takes matrix Psi, columns of which form fully entangled basis.
    Returns matrices UA, UB and vector zeta, such that:
    (UAxUB) * Psi * exp(i * diag(zeta)) = Phi.

    Implements algorithm in Lemma 1 in Appendix A from [2].
    """
    assert is_maximally_entangled_basis(Psi)

    Psi_bar = np.zeros_like(Psi)
    for i in range(4):
        Psi_bar[:, i] = to_real_in_magic_basis(Psi[:, i])

    e_f = (Psi_bar[:, 0] + 1j * Psi_bar[:, 1]) / np.sqrt(2)
    e_ort_f_ort = (Psi_bar[:, 0] - 1j * Psi_bar[:, 1]) / np.sqrt(2)
    e, f = decompose_product_state(e_f)
    e_ort, f_ort = decompose_product_state(e_ort_f_ort)
    e_f_ort = np.kron(e, f_ort)
    e_ort_f = np.kron(e_ort, f)
    assert np.allclose(np.dot(e.conj(), e_ort), 0)
    assert np.allclose(np.dot(f.conj(), f_ort), 0)

    def restore_phase(a, b, c):
        """Finds such delta, that a*exp(i*delta) + b*exp(-i*delta)=c."""
        assert np.abs(a) >= 1e-9
        x1 = (c + np.sqrt(c * c - 4 * a * b)) / (2 * a)
        x2 = (c - np.sqrt(c * c - 4 * a * b)) / (2 * a)
        xs = [v for v in [x1, x2] if np.allclose(np.abs(v), 1)]
        deltas = [np.angle(x) for x in xs]
        assert len(deltas) > 0
        for d in deltas:
            assert np.allclose(a * np.exp(1j * d) + b * np.exp(-1j * d), c)
        return deltas

    a_d = np.kron(e, f_ort)
    b_d = np.kron(e_ort, f)
    c_d = np.sqrt(2) * 1j * Psi_bar[:, 2]
    i_d = 0
    while np.abs(a_d[i_d]) < 1e-9:
        i_d += 1
    deltas = restore_phase(a_d[i_d], b_d[i_d], c_d[i_d])

    # If there are 2 solutions for delta, we need to choose correct one.
    delta = deltas[0]
    if len(deltas) == 2:
        delta = None
        for d in deltas:
            p2 = -1j * (e_f_ort * np.exp(1j * d) +
                        e_ort_f * np.exp(-1j * d)) / np.sqrt(2)
            if _allclose(p2, Psi_bar[:, 2]):
                delta = d
        assert delta is not None

    # Correcting ambiguity in sign.
    # Formula A5b has "+/-", and we need to choose correct sign.
    p3_1 = Psi_bar[:, 3]
    p3_2 = (e_f_ort * np.exp(1j * delta) -
            e_ort_f * np.exp(-1j * delta)) / np.sqrt(2)
    negate_k = 1
    for i in range(4):
        if abs(p3_2[i]) > 1e-9 and np.real(p3_1[i] / p3_2[i]) < -0.5:
            negate_k = -1
    Psi_bar[:, 3] *= negate_k

    # Check formulas A3-A5.
    assert is_unitary(np.array([e_f, e_f_ort, e_ort_f, e_ort_f_ort]))
    assert np.allclose(Psi_bar[:, 0], (e_f + e_ort_f_ort) / np.sqrt(2))
    assert np.allclose(Psi_bar[:, 1], -1j * (e_f - e_ort_f_ort) / np.sqrt(2))
    p2 = -1j * (e_f_ort * np.exp(1j * delta) +
                e_ort_f * np.exp(-1j * delta)) / np.sqrt(2)
    assert np.allclose(Psi_bar[:, 2], p2)
    p3 = (e_f_ort * np.exp(1j * delta) -
          e_ort_f * np.exp(-1j * delta)) / np.sqrt(2)
    assert _allclose(Psi_bar[:, 3], p3)

    UA = np.array([e.conj(), e_ort.conj() * np.exp(1j * delta)])
    UB = np.array([f.conj(), f_ort.conj() * np.exp(-1j * delta)])
    assert is_unitary(UA)
    assert is_unitary(UB)

    UAUB = np.kron(UA, UB)
    UAUB_alt = np.array([
        e_f.conj(),
        e_f_ort.conj() * np.exp(-1j * delta),
        e_ort_f.conj() * np.exp(1j * delta),
        e_ort_f_ort.conj()
    ])
    assert np.allclose(UAUB, UAUB_alt)
    assert is_unitary(UAUB)

    assert np.allclose(UAUB @ Psi_bar, Phi)

    D = (UAUB @ Psi).conj().T @ Phi
    zeta = np.angle(np.array([D[i, i] for i in range(4)]))
    assert _allclose(D, np.diag(np.exp(1j * zeta)))

    assert np.allclose(np.kron(UA, UB) @ Psi @ np.diag(np.exp(1j * zeta)), Phi)
    return UA, UB, zeta