Exemplo n.º 1
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
Exemplo n.º 2
0
def nQubit_Pauli_coeff(X, n, return_dict=False):
    '''
    Generates the coefficients of the matrix X in the n-qubit Pauli basis.
    The coefficients c_{alpha} are such that

    X=(1/2^n)\sum_{alpha} c_alpha \sigma_alpha

    The coefficients are returned in lexicographical ordering.
    '''

    indices = list(itertools.product(*[range(0, 4)] * n))

    if return_dict:
        C = {}
    else:
        C = []

    for index in indices:
        sigma_i = generate_nQubit_Pauli(index)
        if return_dict:
            C[index] = Tr(dag(sigma_i) @ X)
        else:
            C.append(Tr(dag(sigma_i) @ X))

    return 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
Exemplo n.º 4
0
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)
Exemplo n.º 5
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
Exemplo n.º 6
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
Exemplo n.º 7
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
Exemplo n.º 8
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 Clifford_twirl_channel_one_qubit(K, rho, sys=1, dim=[2]):
    '''
    Twirls the given channel with Kraus operators in K by the one-qubit 
    Clifford group on the given subsystem (specified by sys).
    '''

    n = int(np.log2(np.sum([d for d in dim])))

    C1 = eye(2**n)
    C2 = Rx_i(sys, np.pi, n)
    C3 = Rx_i(sys, np.pi / 2., n)
    C4 = Rx_i(sys, -np.pi / 2., n)
    C5 = Rz_i(sys, np.pi, n)
    C6 = Rx_i(sys, np.pi, n) * Rz_i(sys, np.pi, n)
    C7 = Rx_i(sys, np.pi / 2., n) * Rz_i(sys, np.pi, n)
    C6 = Rx_i(sys, np.pi, n) * Rz_i(sys, np.pi, n)
    C8 = Rx_i(sys, -np.pi / 2., n) * Rz_i(sys, np.pi, n)
    C9 = Rz_i(sys, np.pi / 2., n)
    C10 = Ry_i(sys, np.pi, n) * Rz_i(sys, np.pi / 2., n)
    C11 = Ry_i(sys, -np.pi / 2., n) * Rz_i(sys, np.pi / 2., n)
    C12 = Ry_i(sys, np.pi / 2., n) * Rz_i(sys, np.pi / 2., n)
    C13 = Rz_i(sys, -np.pi / 2., n)
    C14 = Ry_i(sys, np.pi, n) * Rz_i(sys, -np.pi / 2., n)
    C15 = Ry_i(sys, -np.pi / 2., n) * Rz_i(sys, -np.pi / 2., n)
    C16 = Ry_i(sys, np.pi / 2., n) * Rz_i(sys, -np.pi / 2., n)
    C17 = Rz_i(sys, -np.pi / 2., n) * Rx_i(sys, np.pi / 2., n) * Rz_i(
        sys, np.pi / 2., n)
    C18 = Rz_i(sys, np.pi / 2., n) * Rx_i(sys, np.pi / 2., n) * Rz_i(
        sys, np.pi / 2., n)
    C19 = Rz_i(sys, np.pi, n) * Rx_i(sys, np.pi / 2., n) * Rz_i(
        sys, np.pi / 2., n)
    C20 = Rx_i(sys, np.pi / 2., n) * Rz_i(sys, np.pi / 2., n)
    C21 = Rz_i(sys, np.pi / 2., n) * Rx_i(sys, -np.pi / 2., n) * Rz_i(
        sys, np.pi / 2., n)
    C22 = Rz_i(sys, -np.pi / 2., n) * Rx_i(sys, -np.pi / 2., n) * Rz_i(
        sys, np.pi / 2., n)
    C23 = Rx_i(sys, -np.pi / 2., n) * Rz_i(sys, np.pi / 2., n)
    C24 = Rx_i(sys, np.pi, n) * Rx_i(sys, -np.pi / 2., n) * Rz_i(
        sys, np.pi / 2., n)

    C = [
        C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16,
        C17, C18, C19, C20, C21, C22, C23, C24
    ]

    rho_twirl = 0

    for i in range(len(C)):
        rho_twirl += (1. / 24.) * C[i] @ apply_channel(K,
                                                       dag(C[i]) @ rho @ C[i],
                                                       sys, dim) @ dag(C[i])

    return rho_twirl, C
Exemplo n.º 10
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
Exemplo n.º 11
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
Exemplo n.º 12
0
def trace_distance_pure_states(psi, phi):
    '''
    Computes the squared trace distance between two pure states psi and phi,
    i.e.,

    || |psi><psi|-|phi><phi| ||_1^2

    '''

    if psi.shape[1] == 1:
        psi = psi @ dag(psi)
    if phi.shape[1] == 1:
        phi = phi @ dag(phi)

    return 1 - Tr(psi * phi)
Exemplo n.º 13
0
def RandomStateVector(dim, rank=None):
    '''
    Generates a random pure state.

    For multipartite states, dim should be a list of dimensions for each
    subsystem. In this case, the rank variable is for the Schmidt rank. To specify
    the Schmidt rank, there has to be a bipartition of the systems, so that dim
    has only two elements.
    '''

    if rank == None:
        if type(dim) == list:
            dim = np.prod(dim)

        # Generate the real and imaginary parts of the components using numbers
        # sampled from the standard normal distribution (normal distribution with
        # mean zero and variance 1).
        psi = dag(
            np.array([np.random.randn(dim)]) +
            1j * np.array([np.random.randn(dim)]))

        psi = psi / norm(psi)

        return psi
    else:
        dimA = dim[0]
        dimB = dim[1]

        if rank == None:
            rank = max([dimA, dimB])
        else:
            k = rank

        psi_k = MaxEnt_state(k, density_matrix=False, normalized=False)
        a = dag(
            np.array([np.random.rand(dimA * k)]) +
            1j * np.array([np.random.rand(dimA * k)]))
        b = dag(
            np.array([np.random.rand(dimB * k)]) +
            1j * np.array([np.random.rand(dimB * k)]))

        psi_init = syspermute(tensor(a, b), [1, 3, 2, 4], [k, dimA, k, dimB])

        psi = tensor(dag(psi_k), eye(dimA * dimB)) @ psi_init

        psi = psi / norm(psi)

        return psi
Exemplo n.º 14
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 apply_teleportation_channel(rho,dA=2,dR1=2,dR2=2,dB=2):

    '''
    Applies the d-dimensional teleportation channel to the four-qudit state rho_{AR1R2B}.
    The channel measures R1 and R2 in the d-dimensional Bell basis and, based on the
    outcome, applies a 'correction operation' to B. So the output of the channel consists
    only of the systems A and B.

    We obtain quantum teleportation by letting

        rho_{AR1R2B} = psi_{R1} ⊗ Phi_{R2B}^+,

    so that dA=1. This simulates teleportation of the state psi in the system R1 to
    the system B. 

    We obtain entanglement swapping by letting

        rho_{AR1R2B} = Phi_{AR1}^+ ⊗ Phi_{R2B}^+.
    
    The result of the channel is then Phi_{AB}^+
    '''

    X=[matrix_power(discrete_Weyl_X(dB),x) for x in range(dB)]
    Z=[matrix_power(discrete_Weyl_Z(dB),z) for z in range(dB)]
    
    rho_out=np.sum([tensor(eye(dA),dag(Bell_state(dR1,z,x)),Z[z]@X[x])@rho@tensor(eye(dA),Bell_state(dR1,z,x),dag(X[x])@dag(Z[z])) for z in range(dB) for x in range(dB)],0)

    return rho_out
Exemplo n.º 16
0
def partial_trace(X,sys,dim):

    '''
    sys is a list of systems over which to take the partial trace (i.e., the
    systems to discard).

    Example: If rho_AB is a bipartite state with dimA the dimension of system A 
    and dimB the dimension of system B, then

    partial_trace(rho_AB,[2],[dimA,dimB]) gives the density matrix on

    system A, i.e., rho_A:=partial_trace[rho_AB].

    Similarly, partial_trace(rho_AB,[1],[dimA,dimB]) discards the first subsystem,
    returning the density matrix of system B.

    If rho_ABC is a tripartite state, then, e.g.,

    partial_trace(rho_ABC,[1,3],[dimA,dimB,dimC])

    discards the first and third subsystems, so that we obtain the density
    matrix for system B.

    '''

    if isinstance(X,cvxpy.Variable):
        X=cvxpy_to_numpy(X)
        X_out=partial_trace(X,sys,dim)
        return numpy_to_cvxpy(X_out)


    if not sys:  # If sys is empty, just return the original operator
        return X
    elif len(sys)==len(dim):  # If tracing over all systems
        return Tr(X)
    else:

        if X.shape[1]==1:
            X=X@dag(X)

        num_sys=len(dim)
        total_sys=range(1,num_sys+1)

        dims_sys=[dim[s-1] for s in sys] # Dimensions of the system to be traced over
        dims_keep=[dim[s-1] for s in list(set(total_sys)-set(sys))]
        dim_sys=np.product(dims_sys)
        dim_keep=np.product(dims_keep)

        perm=sys+list(set(total_sys)-set(sys))
        X=syspermute(X,perm,dim)

        X=np.array(X)
        dim=[dim_sys]+dims_keep
        X_reshape=np.reshape(X,dim+dim)
        X_reshape=np.sum(np.diagonal(X_reshape,axis1=0,axis2=len(dim)),axis=-1)
        X=np.reshape(X_reshape,(dim_keep,dim_keep))

        return X
def apply_teleportation_chain_channel(rho, n, dA=2, dR=2, dB=2):
    '''
    Applies the teleportation chain channel to the state rho, which is of the form

        rho_{A R11 R12 R21 R22 ... Rn1 Rn2 B}.
    
    The channel is defined by performing a d-dimensional Bell basis measurement
    independently on the system pairs Ri1 and Ri2, for 1 <= i <= n; based on the
    outcome, a 'correction operation' is applied to B. The system pairs Ri1 and Ri2
    can be thought of as 'repeaters'. Note that n>=1. For n=1, we get the same channel
    as in apply_teleportation_channel().

    We obtain teleportation by letting dA=1 and letting

        rho_{A R11 R12 R21 R22 ... Rn1 Rn2 B} = psi_{R11} ⊗ Phi_{R12 R21}^+ ⊗ ... ⊗ Phi_{Rn2 B}^+,
    
    so that we have teleportation of the state psi in the system R11 to the system B. 

    We obtain a chain of entanglement swaps by letting

        rho_{A R11 R12 R21 R22 ... Rn1 Rn2 B} = Phi_{A R11}^+ ⊗ Phi_{R12 R21}^+ ⊗ ... ⊗ Phi_{Rn2 B}^+.
    '''

    indices = list(itertools.product(*[range(dB)] * n))

    rho_out = np.array(np.zeros((dA * dB, dA * dB), dtype=complex))

    for z_indices in indices:
        for x_indices in indices:

            Bell_zx = Bell_state(dB, z_indices[0], x_indices[0])
            for j in range(1, n):
                Bell_zx = tensor(Bell_zx,
                                 Bell_state(dB, z_indices[j], x_indices[j]))

            z_sum = np.mod(sum(z_indices), dB)
            x_sum = np.mod(sum(x_indices), dB)

            W_zx = matrix_power(discrete_Weyl_Z(dB), z_sum) @ matrix_power(
                discrete_Weyl_X(dB), x_sum)

            rho_out = rho_out + tensor(eye(dA), dag(
                Bell_zx), W_zx) @ rho @ tensor(eye(dA), Bell_zx, dag(W_zx))

    return rho_out
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
Exemplo n.º 19
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))
Exemplo n.º 20
0
    def objfunc(x):

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

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

        rho = psi @ dag(psi)
        rho_out = apply_channel(K, rho)

        return entropy(rho_out)
Exemplo n.º 21
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)
Exemplo n.º 22
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
Exemplo n.º 23
0
def unitary_distance(U, V):
    '''
    Checks whether two unitaries U and V are the same (taking into account global phase) by using the distance measure:
    
    1-(1/d)*|Tr[UV^†]|,
    
    where d is the dimension of the space on which the unitaries act.
    
    U and V are the same if and only if this is equal to zero; otherwise, it is greater than zero.
    '''

    d = U.shape[0]

    return 1 - (1 / d) * np.abs(Tr(U @ dag(V)))
Exemplo n.º 24
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
Exemplo n.º 25
0
def apply_ent_swap_GHZ_chain_channel(rho, n):
    '''
    Applies the channel that takes n+1 copies of a maximally entangled state and outputs
    a (n+2)-party GHZ state. The input state rho is of the form

        rho_{A R11 R12 R21 R22 ... Rn1 Rn2 B}

    A CNOT is applies to each pair Rj1 Rj2. Then, the qubits Rj2 are measured in the
    standard basis. Conditioned on these outcomes, a correction operation is applied
    at B.

    Currently only works for qubits. For n=1, we get the same thing as apply_ent_swap_GHZ_channel().
    '''
    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)

    indices = list(itertools.product(*[range(2)] * n))

    rho_out = np.array(
        np.zeros((2**(2 * n + 2), 2**(2 * n + 2)), dtype=complex))

    for index in indices:
        index = list(index)

        L = K(1, index[0])
        for j in range(2, n + 1):
            L = K(j, index[j - 1]) @ L

        rho_out = rho_out + L @ rho @ dag(L)

    rho_out = partial_trace(rho_out, [2 * j + 1 for j in range(1, n + 1)],
                            [2] * (2 * n + 2))

    return rho_out
Exemplo n.º 26
0
def nQudit_Weyl_coeff(X, d, n):
    '''
    Generates the coefficients of the operator X acting on n qudit
    systems.
    '''

    C = {}

    S = list(itertools.product(*[range(0, d)] * n))

    for s in S:
        s = list(s)
        for t in S:
            t = list(t)
            G = generate_nQudit_X(d, s) @ generate_nQudit_Z(d, t)
            C[(str(s), str(t))] = np.around(Tr(dag(X) @ G), 10)

    return C
Exemplo n.º 27
0
def diamond_norm(J, dimA, dimB, display=False):
    '''
    Computes the diamond norm of a superoperator with Choi representation J.
    dimA is the dimension of the input space of the channel, and dimB is the
    dimension of the output space.

    The form of the SDP used comes from Theorem 3.1 of:
        
        'Simpler semidefinite programs for completely bounded norms',
            Chicago Journal of Theoretical Computer Science 2013,
            by John Watrous
    '''
    '''
    The Choi representation J in the above paper is defined using a different
    convention:
        J=(N\otimes I)(|Phi^+><Phi^+|).
    In other words, the channel N acts on the first half of the maximally-
    entangled state, while the convention used throughout this code stack
    is
        J=(I\otimes N)(|Phi^+><Phi^+|).
    We thus use syspermute to convert to the form used in the aforementioned
    paper.
    '''

    J = syspermute(J, [2, 1], [dimA, dimB])

    X = cvx.Variable((dimA * dimB, dimA * dimB), hermitian=False)
    rho0 = cvx.Variable((dimA, dimA), PSD=True)
    rho1 = cvx.Variable((dimA, dimA), PSD=True)

    M = cvx.bmat([[cvx.kron(eye(dimB), rho0), X],
                  [X.H, cvx.kron(eye(dimB), rho1)]])

    c = []
    c += [M >> 0, cvx.trace(rho0) == 1, cvx.trace(rho1) == 1]

    obj = cvx.Maximize((1 / 2) * cvx.real(cvx.trace(dag(J) @ X)) +
                       (1 / 2) * cvx.real(cvx.trace(J @ X.H)))

    prob = cvx.Problem(obj, constraints=c)

    prob.solve(verbose=display, eps=1e-7)

    return prob.value
Exemplo n.º 28
0
def Bell_state(d, z, x, density_matrix=False):
    '''
    Generates a d-dimensional Bell state with 0 <= z,x <= d-1. These are defined as

    |Phi_{z,x}> = (Z(z)X(x) ⊗ I)|Phi^+>

    '''

    Bell = MaxEnt_state(d, density_matrix=density_matrix)

    W_zx = matrix_power(discrete_Weyl_Z(d), z) @ matrix_power(
        discrete_Weyl_X(d), x)

    if density_matrix:
        out = tensor(W_zx, eye(d)) @ Bell @ tensor(dag(W_zx), eye(d))
        return out
    else:
        out = tensor(W_zx, eye(d)) @ Bell
        return out
Exemplo n.º 29
0
def permute_tensor_factors(perm, dims):
    '''
    Generates the permutation operator that permutes the tensor factors according
    to the given permutation.
    
    perm is a list
    containing the desired order, and dim is a list of the dimensions of all
    tensor factors.
    '''

    K = generate_all_kets(dims)

    dim = np.prod(dims)

    W = np.zeros((dim, dim), dtype=complex)

    for ket in K:
        W = W + syspermute(ket, perm, dims) @ dag(ket)

    return W
Exemplo n.º 30
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