예제 #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)
    result += apply_on_qubit(su_to_gates(magic_decomp['VB']), 0)
    result += decompose_magic_N(magic_decomp['alpha'])
    result += apply_on_qubit(su_to_gates(magic_decomp['UA']), 1)
    result += apply_on_qubit(su_to_gates(magic_decomp['UB']), 0)

    # 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))
        result.append(GateSingle(Gate2('R1', 2 * gl_phase), 0))

    result = skip_identities(result)

    assert _allclose(U, gates_to_matrix(result, 2))

    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 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)]
예제 #5
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
예제 #6
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,
    }
예제 #7
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