Exemple #1
0
    def Graph_t(self, step):
        """
        return a state which CZ gates have been applied #step on all plus state
        """
        MPS_t = []

        chi = 2  # this particular graph state has bond dimension of 2
        U1 = np.reshape(
            np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0],
                      [0, 0, 0, -1]]), (2, 2, 2, 2))  # ctlr z
        plus = (1.0 / np.sqrt(2.0)) * np.ones(2)

        # first qubit
        temp = ncon((plus, plus, U1), ([1], [2], [-1, -2, 1, 2]))
        X, Y, Z = np.linalg.svd(temp, full_matrices=0)

        # truncation
        chi2 = np.min([np.sum(Y > 10.**(-10)), chi])
        piv = np.zeros(len(Y), np.bool)
        piv[(np.argsort(Y)[::-1])[:chi2]] = True
        Y = Y[piv]
        invsq = np.sqrt(sum(Y**2))
        X = X[:, piv]
        Z = Z[piv, :]

        MPS_t.append(np.matmul(X, np.diag(Y)))

        for i in range(step - 1):
            temp = ncon((Z, plus, U1), ([-1, 1], [2], [-2, -3, 1, 2]))
            s0 = temp.shape[0]
            s1 = temp.shape[1]
            s2 = temp.shape[2]

            temp = np.reshape(temp, (s0 * s1, s2))
            X, Y, Z = np.linalg.svd(temp, full_matrices=0)
            # truncation
            chi2 = np.min([np.sum(Y > 10.**(-10)), chi])
            piv = np.zeros(len(Y), np.bool)
            piv[(np.argsort(Y)[::-1])[:chi2]] = True
            Y = Y[piv]
            invsq = np.sqrt(sum(Y**2))

            X = X[:, piv]
            Z = Z[piv, :]

            MPS_t.append(np.reshape(np.matmul(X, np.diag(Y)), (s0, s1, chi2)))

        MPS_t.append(
            np.transpose(Z)
        )  # transpose is to keep the indexing order consistent with my poor choice
        #MPS_t.append(np.expand_dim(np.transpose(Z), axis=1))  # transpose is to keep the indexing order consistent with my poor choice

        for i in range(step + 1, self.N - 1):
            MPS_t.append(np.reshape(plus, [1, 2, 1]))

        #if step != self.N -1:
        #    MPS_t.append(np.reshape(plus, [2, 1]))
        MPS_t.append(np.reshape(plus, [2, 1]))
        return MPS_t
 def construct_Nframes(self):
     # Nqubit tensor product of frame Mn and dual frame Ntn
     self.Ntn = self.Nt.copy()
     self.Mn = self.M.copy()
     for i in range(self.N - 1):
         self.Ntn = ncon((self.Ntn, self.Nt), ([-1, -3, -5], [-2, -4, -6]))
         self.Mn = ncon((self.Mn, self.M), ([-1, -3, -5], [-2, -4, -6]))
         self.Ntn = np.reshape(self.Ntn,
                               (4**(i + 2), 2**(i + 2), 2**(i + 2)))
         self.Mn = np.reshape(self.Mn, (4**(i + 2), 2**(i + 2), 2**(i + 2)))
 def P_gate(self, gate, mask=True):
     gate_factor = int(gate.ndim / 2)
     if gate_factor == 1:
         g = ncon((self.M, gate, self.Nt, np.transpose(np.conj(gate))),
                  ([-1, 4, 1], [1, 2], [-2, 2, 5], [5, 4]))
     else:
         g = ncon((self.M, self.M, gate, self.Nt, self.Nt, np.conj(gate)),
                  ([-1, 9, 1], [-2, 10, 2], [1, 2, 3, 4], [-3, 3, 7],
                   [-4, 4, 8], [9, 10, 7, 8]))
     if mask:
         g_mask = np.abs(g.real) > 1e-15
         g = np.multiply(g, g_mask)
     return g.real.astype('float32')
    def get_initial_bias(self, initial_state, as_torch_tensor=False):
        # which initial product state?
        if initial_state == '0':
            s = np.array([1, 0])
        elif initial_state == '1':
            s = np.array([0, 1])
        elif initial_state == '+':
            s = (1.0 / np.sqrt(2.0)) * np.array([1, 1])
        elif initial_state == '-':
            s = (1.0 / np.sqrt(2.0)) * np.array([1, -1])
        elif initial_state == 'r':
            s = (1.0 / np.sqrt(2.0)) * np.array([1, 1j])
        elif initial_state == 'l':
            s = (1.0 / np.sqrt(2.0)) * np.array([1, -1j])

        self.P = np.real(ncon((self.M, s, np.conj(s)), ([-1, 1, 2], [1], [2])))

        # solving for bias
        self.bias = np.zeros(self.K)
        self.bias = np.log(self.P)

        if np.sum(np.abs(self.softmax(self.bias) - self.P)) > 0.00000000001:
            print("initial bias not found")
        elif as_torch_tensor:
            return torch.tensor(self.bias)
        else:
            return self.bias
 def N_body_gate(self, gate, mask=True):
     gn = ncon((self.Mn, gate, self.Ntn, np.transpose(np.conj(gate))),
               ([-1, 4, 1], [1, 2], [-2, 2, 5], [5, 4]))
     if mask:
         gn_mask = np.abs(gn.real) > 1e-15
         gn = np.multiply(gn, gn_mask)
     return gn.real.astype('float32')
 def one_body_gate(self, gate, mask=True):
     g1 = ncon((self.M, gate, self.Nt, np.transpose(np.conj(gate))),
               ([-1, 4, 1], [1, 2], [-2, 2, 5], [5, 4]))
     if mask:
         g1_mask = np.abs(g1.real) > 1e-15
         g1 = np.multiply(g1, g1_mask)
     return g1.real.astype('float32')
 def two_body_gate_all(self, gate, mask=True):
     g2 = ncon((self.M, self.M, gate, self.Nt, self.Nt, np.conj(gate)),
               ([-1, 9, 1], [-2, 10, 2], [1, 2, 3, 4], [-3, 3, 7],
                [-4, 4, 8], [9, 10, 7, 8]))
     #if mask:
     #  g2_mask = np.abs(g2.real) > 1e-15
     #  g2 = np.multiply(g2, g2_mask)
     return g2
Exemple #8
0
    def cFidelity_t(self, S, logP, step):
        """
        compute the classical fidelity with linear Graph state after apply CZ gates with #step
        """
        Fidelity = 0.0
        F2 = 0.0
        Ns = S.shape[0]
        L1 = 0.0
        L1_2 = 0.0
        MPS_t = self.Graph_t(step)
        #for i in range(len(MPS_t)):
        #    print(MPS_t[i].shape)
        plus = (1.0 / np.sqrt(2.0)) * np.ones(2)
        plus_pho = np.outer(plus, np.conjugate(plus))
        prob_plus = ncon((plus_pho, self.M), ([1, 2], [-1, 2, 1]))
        for i in range(Ns):

            P = ncon((MPS_t[0], MPS_t[0], self.M[S[i, 0], :, :]),
                     ([1, -1], [2, -2], [1, 2]))

            # contracting the entire TN for each sample S[i,:]
            for j in range(1, step):
                P = ncon((P, MPS_t[j], MPS_t[j], self.M[S[i, j], :, :]),
                         ([1, 2], [1, 3, -1], [2, 4, -2], [3, 4]))

            P = ncon((P, MPS_t[step], MPS_t[step], self.M[S[i, step], :, :]),
                     ([1, 2], [3, 1], [4, 2], [3, 4]))
            for j in range(step + 1, self.N):
                P *= prob_plus[S[i, j]]

            ratio = P / np.exp(logP[i])
            ee = np.sqrt(ratio)
            Fidelity = Fidelity + ee
            F2 = F2 + ee**2
            L1 = L1 + np.abs(1 - ratio)
            L1_2 = L1_2 + np.abs(1 - ratio)**2

        F2 = F2 / float(Ns)
        Fidelity = np.abs(Fidelity / float(Ns))
        Error = np.sqrt(np.abs(F2 - Fidelity**2) / float(Ns))
        L1_2 = L1_2 / float(Ns)
        L1 = np.abs(L1 / float(Ns))
        L1_err = np.sqrt(np.abs(L1_2 - L1**2) / float(Ns))

        return np.real(Fidelity), Error, L1, L1_err
Exemple #9
0
    def cFidelity(self, S, logP):
        Fidelity = 0.0
        F2 = 0.0
        Ns = S.shape[0]
        KL = 0.0
        K2 = 0.0
        L1 = 0.0
        L1_2 = 0.0
        for i in range(Ns):

            P = ncon((self.MPS[0], self.MPS[0], self.M[S[i, 0], :, :]),
                     ([1, -1], [2, -2], [1, 2]))

            # contracting the entire TN for each sample S[i,:]
            for j in range(1, self.N - 1):
                P = ncon((P, self.MPS[j], self.MPS[j], self.M[S[i, j], :, :]),
                         ([1, 2], [1, 3, -1], [2, 4, -2], [3, 4]))

            P = ncon((P, self.MPS[self.N - 1], self.MPS[self.N - 1],
                      self.M[S[i, self.N - 1], :, :]),
                     ([1, 2], [3, 1], [4, 2], [3, 4]))

            ratio = P / np.exp(logP[i])
            ee = np.sqrt(ratio)
            Fidelity = Fidelity + ee
            F2 = F2 + ee**2
            L1 = L1 + np.abs(1 - ratio)
            L1_2 = L1_2 + np.abs(1 - ratio)**2
            #KL = KL + 2 * np.log(ee + 1e-9)
            #K2 = K2 + 4 * (np.log(ee + 1e-9)) ** 2

        F2 = F2 / float(Ns)
        Fidelity = np.abs(Fidelity / float(Ns))
        Error = np.sqrt(np.abs(F2 - Fidelity**2) / float(Ns))
        L1_2 = L1_2 / float(Ns)
        L1 = np.abs(L1 / float(Ns))
        L1_err = np.sqrt(np.abs(L1_2 - L1**2) / float(Ns))
        K2 = K2 / float(Ns)
        KL = np.abs(KL / float(Ns))
        ErrorKL = np.sqrt(np.abs(K2 - KL**2) / float(Ns))

        return np.real(Fidelity), Error, L1, L1_err
Exemple #10
0
    def Fidelity(self, S):
        Fidelity = 0.0
        F2 = 0.0
        Ns = S.shape[0]
        for i in range(Ns):

            # contracting the entire TN for each sample S[i,:]
            # eT = ncon(( self.TB[0][:,:,S[i,0]], self.TB[1][:,:,:,:,S[i,1]]) ,( [1,2],[-1,-2,1,2 ]))
            eT = ncon((self.it[:, S[i, 0]], self.M, self.MPS[0], self.MPS[0]),
                      ([3], [3, 2, 1], [1, -1], [2, -2]))

            for j in range(1, self.N - 1):
                # eT = ncon((eT,self.TB[j][:,:,:,:,S[i,j]]),([ 1,2],[ -1,-2, 1,2 ]))
                eT = ncon(
                    (eT, self.it[:, S[i,
                                      j]], self.M, self.MPS[j], self.MPS[j]),
                    ([2, 4], [1], [1, 5, 3], [2, 3, -1], [4, 5, -2]))

                # eT = ncon((eT, self.TB[self.N-1][:,:,S[i,self.N-1]]),([1,2],[1,2 ]))
            j = self.N - 1
            eT = ncon(
                (eT, self.it[:, S[i, j]], self.M, self.MPS[j], self.MPS[j]),
                ([2, 5], [1], [1, 4, 3], [3, 2], [4, 5]))
            # print i, eT
            Fidelity = Fidelity + eT
            F2 = F2 + eT**2
            Fest = Fidelity / float(i + 1)
            F2est = F2 / float(i + 1)
            Error = np.sqrt(np.abs(F2est - Fest**2) / float(i + 1))
            # print i,np.real(Fest),Error
            # disp([i,i/Ns, real(Fest), real(Error)])
            # fflush(stdout)

        F2 = F2 / float(Ns)

        Fidelity = np.abs(Fidelity / float(Ns))

        Error = np.sqrt(np.abs(F2 - Fidelity**2) / float(Ns))

        return np.real(Fidelity), Error
    def getinitialbias(self, initial_state):

        self.P = np.real(
            ncon((self.M, self.s, np.conj(self.s)), ([-1, 1, 2], [1], [2])))

        # solving for bias
        self.bias = np.zeros(self.K)
        self.bias = np.log(self.P)

        if np.sum(np.abs(self.softmax(self.bias) - self.P)) > 0.00000000001:
            print("initial bias not found")
        else:
            return self.bias
Exemple #12
0
    def __init__(self, POVM='Trine', Number_qubits=4, MPS='GHZ'):

        self.N = Number_qubits
        # Hamiltonian for calculation of energy (TFIM in 1d)
        # self.Jz = Jz
        # self.hx = hx

        # POVMs and other operators
        # Pauli matrices

        self.I = np.array([[1, 0], [0, 1]])
        self.X = np.array([[0, 1], [1, 0]])
        self.s1 = self.X
        self.Z = np.array([[1, 0], [0, -1]])
        self.s3 = self.Z
        self.Y = np.array([[0, -1j], [1j, 0]])
        self.s2 = self.Y
        self.H = 1.0 / np.sqrt(2.0) * np.array([[1, 1], [1, -1]])
        self.Sp = np.array([[1.0, 0.0], [0.0, -1j]])
        self.oxo = np.array([[1.0, 0.0], [0.0, 0.0]])
        self.IxI = np.array([[0.0, 0.0], [0.0, 1.0]])
        self.Phase = np.array([[1.0, 0.0], [0.0, 1j]])  # =S = (Sp)^{\dag}
        self.T = np.array([[1.0, 0], [0, np.exp(-1j * np.pi / 4.0)]])
        self.U1 = np.array([[np.exp(-1j * np.pi / 3.0), 0],
                            [0, np.exp(1j * np.pi / 3.0)]])

        # two-qubit gates
        self.cy = ncon((self.oxo, self.I), ([-1, -3], [-2, -4])) + ncon(
            (self.IxI, self.Y), ([-1, -3], [-2, -4]))
        self.cz = ncon((self.oxo, self.I), ([-1, -3], [-2, -4])) + ncon(
            (self.IxI, self.Z), ([-1, -3], [-2, -4]))

        # Tetra POVM
        if POVM == '4Pauli':
            self.K = 4

            self.M = np.zeros((self.K, 2, 2), dtype=complex)

            self.M[0, :, :] = 1.0 / 3.0 * np.array([[1, 0], [0, 0]])
            self.M[1, :, :] = 1.0 / 6.0 * np.array([[1, 1], [1, 1]])
            self.M[2, :, :] = 1.0 / 6.0 * np.array([[1, -1j], [1j, 1]])
            self.M[3, :, :] = 1.0 / 3.0 * (np.array([[0, 0], [0, 1]]) +
                                           0.5 * np.array([[1, -1], [-1, 1]]) +
                                           0.5 * np.array([[1, 1j], [-1j, 1]]))

        if POVM == 'Tetra':
            self.K = 4

            self.M = np.zeros((self.K, 2, 2), dtype=complex)

            self.v1 = np.array([0, 0, 1.0])
            self.M[0, :, :] = 1.0 / 4.0 * (self.I + self.v1[0] * self.s1 +
                                           self.v1[1] * self.s2 +
                                           self.v1[2] * self.s3)

            self.v2 = np.array([2.0 * np.sqrt(2.0) / 3.0, 0.0, -1.0 / 3.0])
            self.M[1, :, :] = 1.0 / 4.0 * (self.I + self.v2[0] * self.s1 +
                                           self.v2[1] * self.s2 +
                                           self.v2[2] * self.s3)

            self.v3 = np.array(
                [-np.sqrt(2.0) / 3.0,
                 np.sqrt(2.0 / 3.0), -1.0 / 3.0])
            self.M[2, :, :] = 1.0 / 4.0 * (self.I + self.v3[0] * self.s1 +
                                           self.v3[1] * self.s2 +
                                           self.v3[2] * self.s3)

            self.v4 = np.array(
                [-np.sqrt(2.0) / 3.0, -np.sqrt(2.0 / 3.0), -1.0 / 3.0])
            self.M[3, :, :] = 1.0 / 4.0 * (self.I + self.v4[0] * self.s1 +
                                           self.v4[1] * self.s2 +
                                           self.v4[2] * self.s3)

        if POVM == 'Tetra_pos':
            self.K = 4
            self.M = np.zeros((self.K, 2, 2), dtype=complex)

            self.v1 = np.array([1.0, 1.0, 1.0]) / np.sqrt(3)
            self.M[0, :, :] = 1.0 / 4.0 * (self.I + self.v1[0] * self.s1 +
                                           self.v1[1] * self.s2 +
                                           self.v1[2] * self.s3)

            self.v2 = np.array([1.0, -1.0, -1.0]) / np.sqrt(3)
            self.M[1, :, :] = 1.0 / 4.0 * (self.I + self.v2[0] * self.s1 +
                                           self.v2[1] * self.s2 +
                                           self.v2[2] * self.s3)

            self.v3 = np.array([-1.0, 1.0, -1.0]) / np.sqrt(3)
            self.M[2, :, :] = 1.0 / 4.0 * (self.I + self.v3[0] * self.s1 +
                                           self.v3[1] * self.s2 +
                                           self.v3[2] * self.s3)

            self.v4 = np.array([-1.0, -1.0, 1.0]) / np.sqrt(3)
            self.M[3, :, :] = 1.0 / 4.0 * (self.I + self.v4[0] * self.s1 +
                                           self.v4[1] * self.s2 +
                                           self.v4[2] * self.s3)

        elif POVM == 'Trine':
            self.K = 3
            self.M = np.zeros((self.K, 2, 2), dtype=complex)
            phi0 = 0.0
            for k in range(self.K):
                phi = phi0 + k * 2 * np.pi / 3.0
                self.M[k, :, :] = 0.5 * (self.I + np.cos(phi) * self.Z +
                                         np.sin(phi) * self.X) * 2 / 3.0

        # % T matrix and its inverse
        self.t = ncon((self.M, self.M), ([-1, 1, 2], [-2, 2, 1]))
        self.it = np.linalg.inv(self.t)
        # Tensor for expectation value
        # self.Trsx  = np.zeros((self.N,self.K),dtype=complex)
        # self.Trsy  = np.zeros((self.N,self.K),dtype=complex)
        # self.Trsz  = np.zeros((self.N,self.K),dtype=complex)
        # self.Trrho = np.zeros((self.N,self.K),dtype=complex)
        # self.Trrho2 = np.zeros((self.N,self.K,self.K),dtype=complex)
        # self.T2 = np.zeros((self.N,self.K,self.K),dtype=complex)
        self.Trsx = np.zeros(self.K, dtype=complex)
        self.Trsy = np.zeros(self.K, dtype=complex)
        self.Trsz = np.zeros(self.K, dtype=complex)
        self.Trrho = np.zeros((self.N, self.K), dtype=complex)
        self.Trrho2 = np.zeros((self.N, self.K, self.K), dtype=complex)
        self.T2 = np.zeros((self.N, self.K, self.K), dtype=complex)

        self.Trsx = ncon((self.M, self.it, self.X),
                         ([3, 2, 1], [3, -1], [2, 1]))
        self.Trsy = ncon((self.M, self.it, self.Y),
                         ([3, 2, 1], [3, -1], [2, 1]))
        self.Trsz = ncon((self.M, self.it, self.Z),
                         ([3, 2, 1], [3, -1], [2, 1]))
        self.stab_ops = [self.Trsx, self.Trsz]

        if MPS == "GHZ":
            # Copy tensors used to construct GHZ as an MPS. The procedure below should work for any other MPS
            cc = np.zeros((2, 2))  # corner
            cc[0, 0] = 2**(-1.0 / (2 * self.N))
            cc[1, 1] = 2**(-1.0 / (2 * self.N))
            cb = np.zeros((2, 2, 2))  # bulk
            cb[0, 0, 0] = 2**(-1.0 / (2 * self.N))
            cb[1, 1, 1] = 2**(-1.0 / (2 * self.N))

            self.MPS = []
            self.MPS.append(cc)
            for i in range(self.N - 2):
                self.MPS.append(cb)
            self.MPS.append(cc)

        elif MPS == "Graph":

            MPS = []

            chi = 2  # this particular graph state has bond dimension of 2

            U1 = np.reshape(
                np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0],
                          [0, 0, 0, -1]]), (2, 2, 2, 2))  # ctlr z
            plus = (1.0 / np.sqrt(2.0)) * np.ones(2)

            # first qubit
            temp = ncon((plus, plus, U1), ([1], [2], [-1, -2, 1, 2]))
            X, Y, Z = np.linalg.svd(temp, full_matrices=0)

            # truncation
            chi2 = np.min([np.sum(Y > 10.**(-10)), chi])
            piv = np.zeros(len(Y), np.bool)
            piv[(np.argsort(Y)[::-1])[:chi2]] = True
            Y = Y[piv]
            invsq = np.sqrt(sum(Y**2))
            X = X[:, piv]
            Z = Z[piv, :]

            MPS.append(np.matmul(X, np.diag(Y)))

            for i in range(1, self.N - 1):
                temp = ncon((Z, plus, U1), ([-1, 1], [2], [-2, -3, 1, 2]))
                s0 = temp.shape[0]
                s1 = temp.shape[1]
                s2 = temp.shape[2]

                temp = np.reshape(temp, (s0 * s1, s2))
                X, Y, Z = np.linalg.svd(temp, full_matrices=0)
                # truncation
                chi2 = np.min([np.sum(Y > 10.**(-10)), chi])
                piv = np.zeros(len(Y), np.bool)
                piv[(np.argsort(Y)[::-1])[:chi2]] = True
                Y = Y[piv]
                invsq = np.sqrt(sum(Y**2))

                X = X[:, piv]
                Z = Z[piv, :]

                MPS.append(np.reshape(np.matmul(X, np.diag(Y)),
                                      (s0, s1, chi2)))

            # last qubit
            MPS.append(
                np.transpose(Z)
            )  # transpose is to keep the indexing order consistent with my poor choice

            # testing
            # GS = ncon((MPS[0],MPS[1],MPS[2],MPS[3]),([-1,1],[1,-2,2],[2,-3,3],[3,-4]))
            # GS = np.reshape(GS,(2**4))

            self.MPS = MPS

        elif MPS == "plus":
            print(MPS, "squi")
            plus = (1.0 / np.sqrt(2.0)) * np.ones(2)
            self.MPS = []

            self.MPS.append(np.reshape(plus, [2, 1]))

            for i in range(1, self.N - 1):
                self.MPS.append(np.reshape(plus, [1, 2, 1]))

            self.MPS.append(np.reshape(plus, [2, 1]))
Exemple #13
0
    def stabilizers(self, i, si, j, sj, k, sk):
        MPS = deepcopy(self.MPS)
        if i == 0:
            MPS[i] = ncon((MPS[i], si), ([1, -2], [-1, 1]))
        elif i == self.N - 1:
            MPS[i] = ncon((MPS[i], si), ([-2, 1], [-1, 1]))
        else:
            MPS[i] = ncon((MPS[i], si), ([-1, 1, -3], [-2, 1]))

        if j == 0:
            MPS[j] = ncon((MPS[j], sj), ([1, -2], [-1, 1]))
        elif j == self.N - 1:
            MPS[j] = ncon((MPS[j], sj), ([-2, 1], [-1, 1]))
        else:
            MPS[j] = ncon((MPS[j], sj), ([-1, 1, -3], [-2, 1]))

        if k == 0:
            MPS[k] = ncon((MPS[k], sk), ([1, -2], [-1, 1]))
        elif k == self.N - 1:
            MPS[k] = ncon((MPS[k], sk), ([-2, 1], [-1, 1]))
        else:
            MPS[k] = ncon((MPS[k], sk), ([-1, 1, -3], [-2, 1]))

        C = ncon((MPS[0], np.conj(self.MPS[0])), ([1, -1], [1, -2]))
        for ii in range(1, self.N - 1):
            C = ncon((C, MPS[ii], np.conj(self.MPS[ii])),
                     ([1, 2], [1, 3, -1], [2, 3, -2]))

        ii = self.N - 1
        C = ncon((C, MPS[ii], np.conj(self.MPS[ii])), ([1, 2], [3, 1], [3, 2]))
        return C
    def __init__(self,
                 POVM='4Pauli',
                 Number_qubits=4,
                 initial_state='+',
                 Jz=1.0,
                 hx=1.0,
                 eps=1e-4):

        self.type = POVM
        self.N = Number_qubits
        # Hamiltonian for calculation of energy (TFIM in 1d)
        self.Jz = Jz
        self.hx = hx
        self.eps = eps

        # POVMs and other operators
        # Pauli matrices,gates,simple states
        self.I = np.array([[1, 0], [0, 1]])
        self.X = np.array([[0, 1], [1, 0]])
        self.s1 = self.X
        self.Z = np.array([[1, 0], [0, -1]])
        self.s3 = self.Z
        self.Y = np.array([[0, -1j], [1j, 0]])
        self.s2 = self.Y
        self.H = 1.0 / np.sqrt(2.0) * np.array([[1, 1], [1, -1]])
        self.Sp = np.array([[1.0, 0.0], [0.0, -1j]])
        self.oxo = np.array([[1.0, 0.0], [0.0, 0.0]])
        self.IxI = np.array([[0.0, 0.0], [0.0, 1.0]])
        self.Phase = np.array([[1.0, 0.0], [0.0, 1j]])  # =S = (Sp)^{\dag}
        aa = 8.0
        self.R8 = np.array([[np.cos(np.pi / aa), -np.sin(np.pi / aa)],
                            [np.sin(np.pi / aa),
                             np.cos(np.pi / aa)]])
        self.T = np.array([[1.0, 0], [0, np.exp(-1j * np.pi / 4.0)]])
        self.U1 = np.array([[np.exp(-1j * np.pi / 3.0), 0],
                            [0, np.exp(1j * np.pi / 3.0)]])

        #two-qubit gates
        self.cx = ncon((self.oxo, self.I), ([-1, -3], [-2, -4])) + ncon(
            (self.IxI, self.X), ([-1, -3], [-2, -4]))
        self.cy = ncon((self.oxo, self.I), ([-1, -3], [-2, -4])) + ncon(
            (self.IxI, self.Y), ([-1, -3], [-2, -4]))
        self.cz = ncon((self.oxo, self.I), ([-1, -3], [-2, -4])) + ncon(
            (self.IxI, self.Z), ([-1, -3], [-2, -4]))
        self.cnot = ncon((self.oxo, self.I), ([-1, -3], [-2, -4])) + ncon(
            (self.IxI, self.X), ([-1, -3], [-2, -4]))
        self.cu1 = ncon((self.oxo, self.I), ([-1, -3], [-2, -4])) + ncon(
            (self.IxI, self.U1), ([-1, -3], [-2, -4]))
        self.HI = ncon((self.H, self.I), ([-1, -3], [-2, -4]))
        self.PhaseI = ncon((self.Phase, self.I), ([-1, -3], [-2, -4]))
        self.R8I = ncon((self.R8, self.I), ([-1, -3], [-2, -4]))
        self.TI = ncon((self.T, self.I), ([-1, -3], [-2, -4]))

        self.single_qubit = [self.H, self.Phase, self.T, self.U1]
        self.two_qubit = [
            self.cnot, self.cz, self.cy, self.cu1, self.HI, self.PhaseI,
            self.R8I, self.TI, self.cx
        ]

        if POVM == '4Pauli':
            self.K = 4

            self.M = np.zeros((self.K, 2, 2), dtype=complex)

            self.M[0, :, :] = 1.0 / 3.0 * np.array([[1, 0], [0, 0]])
            self.M[1, :, :] = 1.0 / 6.0 * np.array([[1, 1], [1, 1]])
            self.M[2, :, :] = 1.0 / 6.0 * np.array([[1, -1j], [1j, 1]])
            self.M[3,:,:] = 1.0/3.0*(np.array([[0, 0],[0, 1]]) + \
                                     0.5*np.array([[1, -1],[-1, 1]]) \
                                   + 0.5*np.array([[1, 1j],[-1j, 1]]) )

        if POVM == 'Tetra':  ## symmetric
            self.K = 4

            self.M = np.zeros((self.K, 2, 2), dtype=complex)

            self.v1 = np.array([0, 0, 1.0])
            self.M[0, :, :] = 1.0 / 4.0 * (self.I + self.v1[0] * self.s1 +
                                           self.v1[1] * self.s2 +
                                           self.v1[2] * self.s3)

            self.v2 = np.array([2.0 * np.sqrt(2.0) / 3.0, 0.0, -1.0 / 3.0])
            self.M[1, :, :] = 1.0 / 4.0 * (self.I + self.v2[0] * self.s1 +
                                           self.v2[1] * self.s2 +
                                           self.v2[2] * self.s3)

            self.v3 = np.array(
                [-np.sqrt(2.0) / 3.0,
                 np.sqrt(2.0 / 3.0), -1.0 / 3.0])
            self.M[2, :, :] = 1.0 / 4.0 * (self.I + self.v3[0] * self.s1 +
                                           self.v3[1] * self.s2 +
                                           self.v3[2] * self.s3)

            self.v4 = np.array(
                [-np.sqrt(2.0) / 3.0, -np.sqrt(2.0 / 3.0), -1.0 / 3.0])
            self.M[3, :, :] = 1.0 / 4.0 * (self.I + self.v4[0] * self.s1 +
                                           self.v4[1] * self.s2 +
                                           self.v4[2] * self.s3)

        if POVM == 'Tetra_pos':
            self.K = 4
            self.M = np.zeros((self.K, 2, 2), dtype=complex)

            self.v1 = np.array([1.0, 1.0, 1.0]) / np.sqrt(3)
            self.M[0, :, :] = 1.0 / 4.0 * (self.I + self.v1[0] * self.s1 +
                                           self.v1[1] * self.s2 +
                                           self.v1[2] * self.s3)

            self.v2 = np.array([1.0, -1.0, -1.0]) / np.sqrt(3)
            self.M[1, :, :] = 1.0 / 4.0 * (self.I + self.v2[0] * self.s1 +
                                           self.v2[1] * self.s2 +
                                           self.v2[2] * self.s3)

            self.v3 = np.array([-1.0, 1.0, -1.0]) / np.sqrt(3)
            self.M[2, :, :] = 1.0 / 4.0 * (self.I + self.v3[0] * self.s1 +
                                           self.v3[1] * self.s2 +
                                           self.v3[2] * self.s3)

            self.v4 = np.array([-1.0, -1.0, 1.0]) / np.sqrt(3)
            self.M[3, :, :] = 1.0 / 4.0 * (self.I + self.v4[0] * self.s1 +
                                           self.v4[1] * self.s2 +
                                           self.v4[2] * self.s3)

        elif POVM == 'Trine':
            self.K = 3
            self.M = np.zeros((self.K, 2, 2), dtype=complex)
            phi0 = 0.0
            for k in range(self.K):
                phi = phi0 + (k) * 2 * np.pi / 3.0
                self.M[k, :, :] = 0.5 * (self.I + np.cos(phi) * self.Z +
                                         np.sin(phi) * self.X) * 2 / 3.0

        #% T matrix and its inverse
        self.t = ncon((self.M, self.M), ([-1, 1, 2], [-2, 2, 1])).real
        self.it = np.linalg.inv(self.t)
        # dual frame of M
        self.Nt = ncon((self.it, self.M), ([-1, 1], [1, -2, -3]))
        self.Nt_norm = []
        for i in range(self.Nt.shape[0]):
            self.Nt_norm.append(np.linalg.norm(self.Nt[i]))
        self.Nt_norm = np.array(self.Nt_norm)

        # Tensor for expectation value
        self.Trsx = np.zeros((self.N, self.K), dtype=complex)
        self.Trsy = np.zeros((self.N, self.K), dtype=complex)
        self.Trsz = np.zeros((self.N, self.K), dtype=complex)
        self.Trrho = np.zeros((self.N, self.K), dtype=complex)
        self.Trrho2 = np.zeros((self.N, self.K, self.K), dtype=complex)
        self.T2 = np.zeros((self.N, self.K, self.K), dtype=complex)

        # probability gate set single qubit
        self.p_single_qubit = []
        for i in range(len(self.single_qubit)):
            #mat = ncon((self.M,self.single_qubit[i],self.M,self.it,np.transpose(np.conj(self.single_qubit[i]))),([-1,4,1],[1,2],[3,2,5],[3,-2],[5,4]))
            mat = self.one_body_gate(self.single_qubit[i])
            self.p_single_qubit.append(mat)

        # probability gate set two qubit
        self.p_two_qubit = []
        for i in range(len(self.two_qubit)):
            #mat = ncon((self.M,self.M,self.two_qubit[i],self.M,self.M,self.it,self.it,np.conj(self.two_qubit[i])),([-1,9,1],[-2,10,2],[1,2,3,4],[5,3,7],[6,4,8],[5,-3],[6,-4],[9,10,7,8]))
            mat = self.two_body_gate(self.two_qubit[i])
            self.p_two_qubit.append(mat)
            #print(np.real(np.sum(np.reshape(self.p_two_qubit[i],(16,16)),1)),np.real(np.sum(np.reshape(self.p_two_qubit[i],(16,16)),0)))

        # set initial wavefunction
        if initial_state == '0':
            self.s = np.array([1, 0])
        elif initial_state == '1':
            self.s = np.array([0, 1])
        elif initial_state == '+':
            self.s = (1.0 / np.sqrt(2.0)) * np.array([1, 1])
        elif initial_state == '-':
            self.s = (1.0 / np.sqrt(2.0)) * np.array([1, -1])
        elif initial_state == 'r':
            self.s = (1.0 / np.sqrt(2.0)) * np.array([1, 1j])
        elif initial_state == 'l':
            self.s = (1.0 / np.sqrt(2.0)) * np.array([1, -1j])

        # time evolution gate
        """
        hl = ZZ + XI
        hlx = ZZ + XI + IX
        """
        self.ZZ = np.kron(self.Z, self.Z)
        self.XI = np.kron(self.X, self.I)
        self.XI2 = np.kron(self.X, self.I) + np.kron(self.I, self.X)
        self.hl = self.Jz * np.kron(self.Z, self.Z) + self.hx * np.kron(
            self.X, self.I)
        self.hl = -np.reshape(self.hl, (2, 2, 2, 2))
        self.hlx = self.Jz * np.kron(self.Z, self.Z) + self.hx * (
            np.kron(self.X, self.I) + np.kron(self.I, self.X))
        self.hlx = -np.reshape(self.hlx, (2, 2, 2, 2))
        self.zz2 = np.reshape(self.ZZ, (2, 2, 2, 2))

        #self.sx = np.reshape(np.kron(self.X,self.I)+ np.kron(self.I,self.X),(2,2,2,2))
        self.sx = np.reshape(np.kron(self.X, self.I), (2, 2, 2, 2))

        self.exp_hl = np.reshape(-self.eps * self.hl, (4, 4))
        self.exp_hl = expm(self.exp_hl)
        self.exp_hl_norm = np.linalg.norm(self.exp_hl)
        self.exp_hl2 = self.exp_hl / self.exp_hl_norm

        self.mat = np.reshape(self.exp_hl, (2, 2, 2, 2))
        self.mat2 = np.reshape(self.exp_hl2, (2, 2, 2, 2))

        self.Up = self.two_body_gate(self.mat)
        self.Up2 = self.two_body_gate(self.mat2)

        #self.hlp = ncon((self.it,self.M,self.hl,self.it,self.M),([1,-1],[1,3,2],[2,5,3,6],[4,-2],[4,6,5])).real
        #self.sxp = ncon((self.it,self.M,self.sx,self.it,self.M),([1,-1],[1,3,2],[2,5,3,6],[4,-2],[4,6,5])).real

        # Hamiltonian observable list
        self.hl_ob = ncon(
            (self.hl, self.Nt, self.Nt),
            ([1, 2, 3, 4], [-1, 3, 1], [-2, 4, 2])).real.astype(np.float32)
        self.hlx_ob = ncon(
            (self.hlx, self.Nt, self.Nt),
            ([1, 2, 3, 4], [-1, 3, 1], [-2, 4, 2])).real.astype(np.float32)
        self.x_ob = ncon((-self.hx * self.X, self.Nt),
                         ([1, 2], [-1, 2, 1])).real.astype(np.float32)
        self.zz_ob = ncon(
            (self.zz2, self.Nt, self.Nt),
            ([1, 2, 3, 4], [-1, 3, 1], [-2, 4, 2])).real.astype(np.float32)

        # commuting and anti_computing operator
        hl_Nt = ncon((self.hl, self.Nt, self.Nt),
                     ([-1, -2, 1, 2], [-3, 1, -5], [-4, 2, -6]))
        Nt_hl = ncon((self.Nt, self.Nt, self.hl),
                     ([-3, -1, 1], [-4, -2, 2], [1, 2, -5, -6]))
        hlx_Nt = ncon((self.hlx, self.Nt, self.Nt),
                      ([-1, -2, 1, 2], [-3, 1, -5], [-4, 2, -6]))
        Nt_hlx = ncon((self.Nt, self.Nt, self.hlx),
                      ([-3, -1, 1], [-4, -2, 2], [1, 2, -5, -6]))
        x_Nt = ncon((-self.hx * self.X, self.Nt), ([-1, 1], [-2, 1, -3]))
        Nt_x = ncon((self.Nt, -self.hx * self.X), ([-2, -1, 1], [1, -3]))
        self.hl_anti = ncon(
            (hl_Nt + Nt_hl, self.M, self.M),
            ([1, 2, -3, -4, 3, 4], [-1, 3, 1], [-2, 4, 2])).real.astype(
                np.float32)
        self.hl_com = ncon(
            (-hl_Nt + Nt_hl, self.M, self.M),
            ([1, 2, -3, -4, 3, 4], [-1, 3, 1], [-2, 4, 2])).imag.astype(
                np.float32)
        self.hlx_anti = ncon(
            (hlx_Nt + Nt_hlx, self.M, self.M),
            ([1, 2, -3, -4, 3, 4], [-1, 3, 1], [-2, 4, 2])).real.astype(
                np.float32)
        self.hlx_com = ncon(
            (-hlx_Nt + Nt_hlx, self.M, self.M),
            ([1, 2, -3, -4, 3, 4], [-1, 3, 1], [-2, 4, 2])).imag.astype(
                np.float32)
        self.x_anti = ncon((x_Nt + Nt_x, self.M),
                           ([1, -2, 2], [-1, 2, 1])).real.astype(np.float32)
        self.x_com = ncon((-x_Nt + Nt_x, self.M),
                          ([1, -2, 2], [-1, 2, 1])).imag.astype(np.float32)

        # MPO H
        self.Ham = []

        mat = np.zeros((3, 3, 2, 2))
        mat[0, 0] = self.I
        mat[1, 0] = -self.Z
        mat[2, 0] = -self.X * self.hx
        mat[2, 1] = self.Z
        mat[2, 2] = self.I

        self.Ham.append(mat[2])
        for i in range(1, self.N - 1):
            self.Ham.append(mat)
        self.Ham.append(mat[:, 0, :, :])

        # MPS for Hamiltonian in probability space
        self.Hp = []
        mat = ncon((self.Ham[0], self.M, self.it),
                   ([-2, 3, 1], [2, 1, 3], [2, -1]))
        self.Hp.append(mat)
        for i in range(1, self.N - 1):
            mat = ncon((self.Ham[i], self.M, self.it),
                       ([-1, -3, 3, 1], [2, 1, 3], [2, -2]))
            self.Hp.append(mat)
        mat = ncon((self.Ham[self.N - 1], self.M, self.it),
                   ([-1, 3, 1], [2, 1, 3], [2, -2]))
        self.Hp.append(mat)