示例#1
0
def jordan_wigner(n):
    '''
    Generates the Jordan-Wigner representation of the fermionic creation, annihilation,
    and Majorana operators for an n-mode system.

    The convention for the Majorana operators is as follows:

        c_j=aj^{dag}+aj
        c_{n+j}=i(aj^{dag}-aj)

    '''

    s = ket(2, 0) @ dag(ket(2, 1))

    S = su_generators(2)

    a = {}  # Dictionary for the annihilation operators
    c = {}  # Dictionary for the Majorana operators

    for j in range(1, n + 1):
        a[j] = tensor([S[3], j - 1], s, [S[0], n - j])
        c[j] = dag(a[j]) + a[j]
        c[n + j] = 1j * (dag(a[j]) - a[j])

    return a, c
def generate_channel_isometry(K, dimA, dimB):
    '''
    Generates an isometric extension of the
    channel specified by the Kraus operators K. dimA is the dimension of the
    input space of the channel, and dimB is the dimension of the output space
    of the channel. If dimA=dimB, then the function also outputs a unitary
    extension of the channel given by a particular construction.
    '''

    dimE = len(K)

    V = np.sum([tensor(K[i], ket(dimE, i)) for i in range(dimE)], 0)

    if dimA == dimB:
        # In this case, the unitary we generate has dimensions dimA*dimE x dimA*dimE
        U = tensor(V, dag(ket(dimE, 0)))
        states = [V @ ket(dimA, i) for i in range(dimA)]
        for i in range(dimA * dimE - dimA):
            states.append(RandomStateVector(dimA * dimE))

        states_new = gram_schmidt(states, dimA * dimE)

        count = dimA
        for i in range(dimA):
            for j in range(1, dimE):
                U = U + tensor(states_new[count], dag(ket(dimA, i)),
                               dag(ket(dimE, j)))
                count += 1

        return V, np.array(U)
    else:
        return V
示例#3
0
def CZ_ij(i, j, n):
    '''
    CZ gate on qubits i and j, i being the control and j being the target.
    The total number of qubits is n. (Note that for the CZ gate it does matter
    which qubit is the control and which qubit is the target.)
    '''

    dims = 2 * np.ones(n)
    dims = dims.astype(int)

    indices = np.linspace(1, n, n)
    indices_diff = np.setdiff1d(indices, [i, j])

    perm_arrange = np.append(np.array([i, j]), indices_diff)
    perm_rearrange = np.zeros(n)

    for i in range(n):
        perm_rearrange[i] = np.argwhere(perm_arrange == i + 1)[0][0] + 1

    perm_rearrange = perm_rearrange.astype(int)

    Sz = np.array([[1, 0], [0, -1]])
    CZ = tensor(ket(2, 0) @ dag(ket(2, 0)), eye(2)) + tensor(
        ket(2, 1) @ dag(ket(2, 1)), Sz)

    out_temp = tensor(CZ, [eye(2), n - 2])

    out = syspermute(out_temp, perm_rearrange, dims)

    return out
示例#4
0
def graph_state(A_G,n,density_matrix=False,return_CZ=False,alt=True):

    '''
    Generates the graph state corresponding to the undirected graph G with n vertices.
    A_G denotes the adjacency matrix of G, which for an undirected graph is a binary
    symmetric matrix indicating which vertices are connected.

    See the following book chapter for a review:

        ``Cluster States'' in Compedium of Quantum Physics, pp. 96-105, by H. J. Briegel.

    '''

    plus=(1/np.sqrt(2))*(ket(2,0)+ket(2,1))

    plus_n=tensor([plus,n])

    G=eye(2**n)

    for i in range(n):
        for j in range(i,n):
            if A_G[i,j]==1:
                G=G@CZ_ij(i+1,j+1,n)

    if density_matrix:
        plus_n=plus_n@dag(plus_n)
        if return_CZ:
            return G@plus_n@dag(G),G
        else:
            return G@plus_n@dag(G)
    else:
        if return_CZ:
            return G@plus_n,G
        else:
            return G@plus_n
示例#5
0
def MaxEnt_state(dim, normalized=True, density_matrix=True):
    '''
    Generates the dim-dimensional maximally entangled state, which is defined as

    (1/sqrt(dim))*(|0>|0>+|1>|1>+...+|d-1>|d-1>).

    If normalized=False, then the function returns the unnormalized maximally entangled
    vector.

    If density_matrix=True, then the function returns the state as a density matrix.
    '''

    if normalized:
        Bell = (1. / np.sqrt(dim)) * np.sum(
            [ket(dim, [i, i]) for i in range(dim)], 0)
        if density_matrix:
            return Bell @ dag(Bell)
        else:
            return Bell
    else:
        Gamma = np.sum([ket(dim, [i, i]) for i in range(dim)], 0)
        if density_matrix:
            return Gamma @ dag(Gamma)
        else:
            return Gamma
示例#6
0
def discrete_Weyl_X(d):

    '''
    Generates the X shift operators.
    '''

    X=ket(d,1)@dag(ket(d,0))

    for i in range(1,d):
        X=X+ket(d,(i+1)%d)@dag(ket(d,i))

    return X
示例#7
0
    def K(j, x):

        # j is between 1 and n, denoting the pair of R systems. x is either 0 or 1.
        # For each j, the qubit indices are 2*j and 2*j+1 for the pair Rj1 and Rj2

        Mx = tensor(eye(2), eye(2**(2 * j - 2)), eye(2),
                    ket(2, x) @ dag(ket(2, x)), eye(2**(2 * (n - j))), eye(2))

        C = CNOT_ij(2 * j, 2 * j + 1, 2 * n + 2)

        X = 1j * Rx_i(2 * j + 2, np.pi, 2 * n + 2)

        return Mx @ C @ matrix_power(X, x)
示例#8
0
def discrete_Weyl_Z(d):
    '''
    Generates the Z phase operators.
    '''

    w = np.exp(2 * np.pi * 1j / d)

    Z = ket(d, 0) @ dag(ket(d, 0))

    for i in range(1, d):
        Z = Z + w**i * ket(d, i) @ dag(ket(d, i))

    return Z
def coherent_state_fermi(A,rep='JW',density_matrix=False):

    '''
    Generates the fermionic coherent state vector for n modes, where A is a complex
    anti-symmetric n x n matrix. The matrix A should be at least 2 x 2 -- for one mode,
    the coherent state is the just the vacuum.

    The definition being used here comes from

        A. Perelomov. Generalized Coherent States and Their Applications (Sec. 9.2)

    '''

    n=np.shape(A)[0]  # Number of modes

    a,_=jordan_wigner(n)

    At=np.zeros((2**n,2**n),dtype=complex)

    N=np.linalg.det(eye(n)+A@dag(A))**(1/4)

    for i in range(1,n+1):
        for j in range(1,n+1):
            At=At+(-1/2)*A[i-1,j-1]*dag(a[j])@dag(a[i])

    vac=tensor([ket(2,0),n])

    if not density_matrix:
        return (1/N)*expm(At)@vac
    else:
        coh=(1/N)*expm(At)@vac
        return coh@dag(coh)
示例#10
0
def generate_all_kets(dims):

    '''
    Generates the tensor-product orthonormal basis corresponding to vector spaces
    with dimensions in the list dims.

    ------------------------
    Example:

        generate_all_kets([2,3]) returns a list containing

            |0,0>
            |0,1>
            |0,2>
            |1,0>
            |1,1>
            |1,2>

    '''

    dims_set=[range(d) for d in dims]

    L=list(itertools.product(*dims_set))

    K=[]

    for l in L:
        K.append(ket(dims,l))

    return K
    
示例#11
0
def Kraus_representation(P, dimA, dimB):
    '''
    Takes a Choi representation P of a channel and returns its Kraus representation.
    
    The Choi representation is defined with the channel acting on the second half of
    the maximally entangled vector.
    '''

    D, U = eig(P)

    U_cols = U.shape[1]

    # Need to check if the matrix U generated by eig is unitary (up to numerical precision)
    check1 = np.allclose(eye(dimA * dimB), U @ dag(U))
    check2 = np.allclose(eye(dimA * dimB), dag(U) @ U)

    if check1 and check2:
        U = np.array(U)

    # If U is not unitary, use Gram-Schmidt to make it unitary (i.e., make the columns of U orthonormal)
    else:
        C = gram_schmidt([U[:, i] for i in range(U_cols)], dimA * dimB)
        U = np.sum([tensor(dag(ket(U_cols, i)), C[i]) for i in range(U_cols)],
                   0)
        #print(U)
    K = []

    for i in range(U_cols):
        Col = U[:, i]
        K_tmp = np.array(np.sqrt(D[i]) * Col.reshape([dimA, dimB]))
        K.append(K_tmp.transpose())

    return K
示例#12
0
def nQudit_quadratures(d,n):

    '''
    Returns the list of n-qudit "quadrature" operators, which are defined as
    (for two qudits)

        S[0]=X(0) ⊗ Id
        S[1]=Z(0) ⊗ Id
        S[2]=Id ⊗ X(0)
        S[3]=Id ⊗ Z(0)

    In general, for n qubits:

        S[0]=X(0) ⊗ Id ⊗ ... ⊗ Id
        S[1]=Z(0) ⊗ Id ⊗ ... ⊗ Id
        S[2]=Id ⊗ X(0) ⊗ ... ⊗ Id
        S[3]=Id ⊗ Z(0) ⊗ ... ⊗ Id
        .
        .
        .
        S[2n-2]=Id ⊗ Id ⊗ ... ⊗ X(0)
        S[2n-1]=Id ⊗ Id ⊗ ... ⊗ Z(0)
    '''

    S={}

    count=0

    for i in range(1,2*n+1,2):
        v=list(np.array(dag(ket(n,count)),dtype=np.int).flatten())
        S[i]=generate_nQudit_X(d,v)
        S[i+1]=generate_nQudit_Z(d,v)
        count+=1

    return S
def entanglement_distillation(rho1,
                              rho2,
                              outcome=1,
                              twirl_after=False,
                              normalize=False):
    '''
    Applies a particular entanglement distillation channel to the two two-qubit states
    rho1 and rho2. [PRL 76, 722 (1996)]

    The channel is probabilistic. If the variable outcome=1, then the function returns
    the two-qubit state conditioned on the success of the distillation protocol.
    '''

    CNOT = CNOT_ij(1, 2, 2)
    proj0 = ket(2, 0) @ dag(ket(2, 0))
    proj1 = ket(2, 1) @ dag(ket(2, 1))

    P0 = tensor(eye(2), proj0, eye(2), proj0)
    P1 = tensor(eye(2), proj1, eye(2), proj1)
    P2 = eye(16) - P0 - P1
    C = tensor(CNOT, CNOT)
    K0 = P0 * C
    K1 = P1 * C
    K2 = P2 * C

    rho_in = syspermute(tensor(rho1, rho2), [1, 3, 2, 4],
                        [2, 2, 2, 2])  # rho_in==rho_{A1A2B1B2}

    if outcome == 1:
        # rho_out is unnormalized. The trace of rho_out is equal to the success probability.
        rho_out = partial_trace(K0 @ rho_in @ dag(K0) + K1 @ rho_in @ dag(K1),
                                [2, 4], [2, 2, 2, 2])
        if twirl_after:
            rho_out = isotropic_twirl_state(rho_out, 2)
        if normalize:
            rho_out = rho_out / Tr(rho_out)

    elif outcome == 0:
        # rho_out is unnormalized. The trace of rho_out is equal to the failure probability.
        rho_out = partial_trace(K2 @ rho_in @ dag(K2), [2, 4], [2, 2, 2, 2])
        if normalize:
            rho_out = rho_out / Tr(rho_out)

    return rho_out
示例#14
0
    def objfunc(x):

        Re = np.array(x[0:dim**3])
        Im = np.array(x[dim**3:])

        psi = np.array([Re + 1j * Im]).T
        psi = psi / norm(psi)

        p = []
        S = []

        for j in range(dim**2):
            R = tensor(dag(ket(dim**2, j)), eye(dim)) @ (
                psi @ dag(psi)) @ tensor(ket(dim**2, j), eye(dim))
            p.append(Tr(R))
            rho = R / Tr(R)
            rho_out = apply_channel(K, rho)
            S.append(rho_out)

        return -np.real(Holevo_inf_ensemble(p, S))
示例#15
0
def SWAP(sys, dim):
    '''
    Generates a swap matrix between the pair of systems in sys. dim is a list
    of the dimensions of the subsystems.

    For example, SWAP([1,2],[2,2]) generates the two-qubit swap matrix.
    '''

    dim_total = np.product(dim)

    n = len(dim)
    sys_rest = list(np.setdiff1d(range(1, n + 1), sys))
    perm = sys + sys_rest
    p = {}

    for i in range(1, n + 1):
        p[i] = perm[i - 1]

    p2 = {v: k for k, v in p.items()}

    perm_rearrange = list(p2.values())

    dim1 = dim[sys[0] - 1]  # Dimension of the first subsystem to be swapped
    dim2 = dim[sys[1] - 1]  # Dimension of the second subsystem to be swapped

    dim_rest = int(float(dim_total) / float(dim1 * dim2))

    G1 = np.matrix(np.sum([ket(dim1, [i, i]) for i in range(dim1)], 0))
    G2 = np.matrix(np.sum([ket(dim2, [i, i]) for i in range(dim2)], 0))

    G = G1 @ dag(G2)

    S = partial_transpose(G, [2], [(dim1, dim2), (dim1, dim2)])

    P = tensor(S, eye(dim_rest))

    p_alt = list(np.array(list(p.values())) - 1)

    P = syspermute(P, perm_rearrange, list(np.array(dim)[p_alt]))

    return P
示例#16
0
def avg_fidelity_qubit(K):
    '''
    K is the set of Kraus operators for the (qubit to qubit) channel whose
    average fidelity is to be found.
    '''

    ket0 = ket(2, 0)
    ket1 = ket(2, 1)
    ket_plus = (1. / np.sqrt(2)) * (ket0 + ket1)
    ket_minus = (1. / np.sqrt(2)) * (ket0 - ket1)
    ket_plusi = (1. / np.sqrt(2)) * (ket0 + 1j * ket1)
    ket_minusi = (1. / np.sqrt(2)) * (ket0 - 1j * ket1)

    states = [ket0, ket1, ket_plus, ket_minus, ket_plusi, ket_minusi]

    F = 0

    for state in states:

        F += np.real(
            Tr((state @ dag(state)) * apply_channel(K, state @ dag(state))))

    return (1. / 6.) * F
def generate_state_2design(C,n,display=False):

    '''
    Takes the n-qubit Clifford gates provided in C and returns a
    corresponding state 2-design. This uses the fact that the Clifford
    gates (for any n) form a unitary 2-design, and that any unitary 
    t-design can be used to construct a state t-design.
    '''

    def in_list(L,elem):
    
        '''
        Checks if the given pure state elem is in the list L.
        '''
        
        x=0
        
        for l in L:
            if np.around(trace_distance_pure_states(l,elem),10)==0:
                x=1
                break
        
        return x

    S=[ket(2**n,0)]

    for c in C:
        s_test=c@ket(2**n,0)

        if not in_list(S,s_test):
            S.append(s_test)

        if display:
            print(len(S))

    return S
示例#18
0
def GHZ_state(dim, n, density_matrix=True):
    '''
    Generates the n-party GHZ state in dim-dimensions for each party, which is defined as

        |GHZ_n> = (1/sqrt(dim))*(|0,0,...,0> + |1,1,...,1> + ... + |d-1,d-1,...,d-1>)

    If density_matrix=True, then the function returns the state as a density matrix.
    '''

    GHZ = (1 / np.sqrt(dim)) * np.sum([ket(dim, [i] * n)
                                       for i in range(dim)], 0)

    if density_matrix:
        return GHZ @ dag(GHZ)
    else:
        return GHZ
def apply_ent_swap_GHZ_channel(rho):

    '''
    Applies the channel that takes two copies of a maximally entangled state and outputs
    a three-party GHZ state. The input state rho is of the form

        rho_{A R1 R2 B}.

    A CNOT is applied to R1 and R2, followed by a measurement in the standard basis on
    R2, followed by a correction operation on B based on the outcome of the measurement.

    Currently only works for qubits.
    '''

    C=CNOT_ij(2,3,4)

    X=[matrix_power(discrete_Weyl_X(2),x) for x in range(2)]
    
    rho_out=np.sum([tensor(eye(4),dag(ket(2,x)),eye(2))@C@tensor(eye(8),X[x])@rho@tensor(eye(8),X[x])@dag(C)@tensor(eye(4),ket(2,x),eye(2)) for x in range(2)],0)

    return rho_out
示例#20
0
def nQubit_quadratures(n):
    '''
    Returns the list of n-qubit "quadrature" operators, which are defined as
    (for two qubits)

        S[0]=Sx \otimes Id
        S[1]=Sz \otimes Id
        S[2]=Id \otimes Sx
        S[3]=Id \otimes Sz

    In general, for n qubits:

        S[0]=Sx \otimes Id \otimes ... \otimes Id
        S[1]=Sz \otimes Id \otimes ... \otimes Id
        S[2]=Id \otimes Sx \otimes ... \otimes Id
        S[3]=Id \otimes Sz \otimes ... \otimes Id
        .
        .
        .
        S[2n-2]=Id \otimes Id \otimes ... \otimes Sx
        S[2n-1]=Id\otimes Id \otimes ... \otimes Sz
    '''

    S = {}

    #Sx=np.matrix([[0,1],[1,0]])
    #Sz=np.matrix([[1,0],[0,-1]])

    count = 0

    for i in range(1, 2 * n + 1, 2):
        v = list(np.array(dag(ket(n, count)), dtype=np.int).flatten())
        S[i] = generate_nQubit_Pauli_X(v)
        S[i + 1] = generate_nQubit_Pauli_Z(v)
        count += 1

    return S
示例#21
0
def su_generators(d):

    '''
    Generates the basis (aka generators) of the Lie algebra su(d)
    corresponding to the Lie group SU(d). The basis has d^2-1 elements.

    All of the generators are traceless and Hermitian. After adding the
    identity matrix, they form an orthogonal basis for all dxd matrices.

    The orthogonality condition is

        Tr[S_i*S_j]=d*delta_{i,j}

    (This is a particular convention we use here; there are other conventions.)

    For d=2, we get the Pauli matrices.
    '''

    S=[]

    S.append(eye(d))

    for l in range(d):
        for k in range(l):
            S.append(np.sqrt(d/2)*(ket(d,k)@dag(ket(d,l))+ket(d,l)@dag(ket(d,k))))
            S.append(np.sqrt(d/2)*(-1j*ket(d,k)@dag(ket(d,l))+1j*ket(d,l)@dag(ket(d,k))))

    for k in range(1,d):
        X=0
        for j in range(k):
            X+=ket(d,j)@dag(ket(d,j))
        
        S.append(np.sqrt(d/(k*(k+1)))*(X-k*ket(d,k)@dag(ket(d,k))))

    
    return S