def move_gauge_right_qr(ten1,ten2): """ Move the gauge via qr decomposition from ten1 to ten2 Args: ten1 : np or ctf array The site currently holding the gauge ten2 : np or ctf array The neighboring right site Returns: ten1 : np or ctf array The now isometrized tensor ten2 : np or ctf array The tensor now holding the gauge """ if DEBUG: res0 = einsum('abc,cde->abde',ten1,ten2).make_sparse() # Perform the svd on the tensor Q,R = ten1.qr(2) ten1 = Q # Multiply remainder into neighboring site ten2 = einsum('ab,bpc->apc',R,ten2) if DEBUG: res1 = einsum('abc,cde->abde',ten1,ten2).make_sparse() mpiprint(0,'QR (right) Difference = {}'.format((res0-res1).abs().sum())) # Return results return ten1,ten2
def contract(self,mps2): """ Contract an mps with another mps Args: self : MPS Object The mps which will be contracted mps2 : MPS Object The second mps which will be contracted Returns: res : float The resulting scalar from the contraction """ # Throw error if either mps is not in memory if not self.all_in_mem(): raise ValueError('MPS not in memory for norm calculation') if not mps2.all_in_mem(): raise ValueError('MPS not in memory for norm calculation') # Move to the left, contracting env with bra and ket tensors for site in range(self.N): if site == 0: # Form initial norm environment norm_env = einsum('apb,ApB->aAbB',self[site],mps2[site]) # Remove initial empty indices norm_env = norm_env.remove_empty_ind(0) norm_env = norm_env.remove_empty_ind(0) else: # Add next mps tensors to norm environment tmp1 = einsum('aA,apb->Apb',norm_env,self[site]) norm_env = einsum('Apb,ApB->bB',tmp1,mps2[site]) # Extract and return result if norm_env.sym is None: einstr = 'abcdefghijklmnopqrstuvwxyz'[:len(norm_env.ten.shape)]+'->' norm = norm_env.backend.einsum(einstr,norm_env.ten) else: einstr = 'abcdefghijklmnopqrstuvwxyz'[:len(norm_env.ten.array.shape)]+'->' norm = norm_env.backend.einsum(einstr,norm_env.ten.array) # Return result return norm
def quick_op(op1,op2): """ Convert two local operators (2 legs each) into an operator between sites (4 legs) """ #return einsum('io,IO->iIoO',op1,op2) return einsum('io,IO->oOiI',op1,op2)
def op(ops): """ Operator for sites in center of lattice Args: ops : OPS object An object holding required operators """ I = ops.I op = einsum('io,IO->iIoO',I,I) return op
def exp_mpo_nosym(h,a=1.): """ Take the exponential of a three site gate stored as an MPO for a non-symmetric tensor """ # Get correct library lib = h[0].backend # Contract MPO into a single tensor ten = einsum('oOa,apPqQ->opqOPQ',h[0],einsum('apPb,bqQ->apPqQ',h[1],h[2])) ten = ten.ten shape = ten.shape # reshape into a matrix ten = ten.reshape((np.prod(shape[:3]),np.prod(shape[3:]))) # Take the exponential of the matrix ten *= a exph = lib.expm(ten) # Do some reshaping and transposing res = exph.reshape(shape) res = res.transpose([0,3,1,4,2,5]) shape = res.shape # Do SVD on results to get back into an MPO res = res.reshape((np.prod(shape[:2]),-1)) U,S,V = lib.svd(res,full_matrices=False) ten1 = lib.einsum('ij,j->ij',U,lib.sqrt(S)) ten1 = ten1.reshape(shape[:2]+(-1,)) res = lib.einsum('j,jk->jk',lib.sqrt(S),V) res = res.reshape((np.prod([ten1.shape[2],shape[2],shape[3]]),-1)) U,S,V = lib.svd(res,full_matrices=False) ten2 = lib.einsum('ij,j->ij',U,lib.sqrt(S)) ten2 = ten2.reshape((ten1.shape[2],shape[2],shape[3],-1)) ten3 = lib.einsum('j,jk->jk',lib.sqrt(S),V) ten3 = ten3.reshape((-1,shape[4],shape[5])) # Put into gen_tens ten1 = GEN_TEN(ten=ten1) ten2 = GEN_TEN(ten=ten2) ten3 = GEN_TEN(ten=ten3) return [ten1,ten2,ten3]
def move_gauge_left(mps,site,truncate_mbd=1e100,return_ent=True,return_wgt=True,split_s=False): """ Move the gauge via svd from ten2 to ten1 Args: ten1 : np or ctf array The neighboring left site ten2 : np or ctf array The site currently holding the gauge Kwargs: return_ent : bool Whether or not to return the entanglement entropy and entanglement spectrum Default: True return_wgt : bool Whether or not to return the sum of the discarded weights. Default: True truncate_mbd : int The Maximum retained Bond Dimension Returns: ten1 : np or ctf array The tensor now holding the gauge ten2 : np or ctf array The now isometrized tensor EE : float The von neumann entanglement entropy Only returned if return_ent == True EEs : 1D Array of floats The von neumann entanglement spectrum Only returned if return_ent == True wgt : float The sum of the discarded weigths Only returned if return_wgt == True """ # Retrieve the relevant tensors ten1 = mps[site-1] ten2 = mps[site] if DEBUG: init_cont = einsum('abc,cde->abde',mps[site-1],mps[site]) # Move the gauge out = move_gauge_left_tens(ten1,ten2, truncate_mbd=truncate_mbd, return_ent=return_ent, return_wgt=return_wgt, split_s=split_s) ten1,ten2 = out[0],out[1] # Put back into the mps mps[site-1] = ten1 mps[site] = ten2 if DEBUG: fin_cont = einsum('abc,cde->abde',mps[site-1],mps[site]) if fin_cont.sym is None: diff = init_cont.backend.sum(init_cont.backend.abs(init_cont.ten-fin_cont.ten)) mpiprint(0,'\tDifference before/after QR/SVD = {}'.format(diff)) else: diff = init_cont.backend.sum(init_cont.backend.abs(init_cont.ten.make_sparse()-fin_cont.ten.make_sparse())) mpiprint(0,'\tDifference before/after QR/SVD = {}'.format(diff)) # Return results if return_wgt and return_ent: return mps,out[2],out[3],out[4] elif return_wgt: return mps,out[2] elif return_ent: return mps,out[2],out[3] else: return mps
def move_gauge_right(mps,site,truncate_mbd=1e100,return_ent=True,return_wgt=True,split_s=False): """ Move the gauge via svd from ten1 to ten2 Args: ten1 : np or ctf array The site currently holding the gauge ten2 : np or ctf array The neighboring right site Kwargs: return_ent : bool Whether or not to return the entanglement entropy and entanglement spectrum Default: True return_wgt : bool Whether or not to return the sum of the discarded weights. Default: True truncate_mbd : int The Maximum retained Bond Dimension Returns: ten1 : np or ctf array The now isometrized tensor ten2 : np or ctf array The tensor now holding the gauge EE : float The von neumann entanglement entropy Only returned if return_ent == True EEs : 1D Array of floats The von neumann entanglement spectrum Only returned if return_ent == True wgt : float The sum of the discarded weigths Only returned if return_wgt == True """ # Retrieve the relevant tensors ten1 = mps[site] ten2 = mps[site+1] # Check to make sure everything is done correctly if True: init_cont = einsum('abc,cde->abde',mps[site],mps[site+1]) old_stuff = [mps[site].copy(),mps[site+1].copy()] # Move the gauge out = move_gauge_right_tens(ten1,ten2, truncate_mbd=truncate_mbd, return_ent=return_ent, return_wgt=return_wgt, split_s=split_s) ten1,ten2 = out[0],out[1] # Put back into the mps mps[site] = ten1 mps[site+1] = ten2 if DEBUG: fin_cont = einsum('abc,cde->abde',mps[site],mps[site+1]) if fin_cont.sym is None: diff = init_cont.backend.sum(init_cont.backend.abs(init_cont.ten-fin_cont.ten)) mpiprint(0,'\tDifference before/after QR/SVD = {}'.format(diff)) else: diff = init_cont.backend.sum(init_cont.backend.abs(init_cont.ten.make_sparse()-fin_cont.ten.make_sparse())) mpiprint(0,'\tDifference before/after QR/SVD = {}'.format(diff)) #if diff > 1e-5: # print('\n\nOh No!'+'!'*100) # print('Ten1:') # print('\tshape: {}'.format(old_stuff[0].ten.array.shape)) # print('\tlegs: {}'.format(old_stuff[0].legs)) # print('\tsymmetry: {}'.format(old_stuff[0].sym)) # print('Ten2:') # print('\tshape: {}'.format(old_stuff[1].ten.array.shape)) # print('\tlegs: {}'.format(old_stuff[1].legs)) # print('\tsymmetry: {}'.format(old_stuff[1].sym)) # import sys # sys.exit() # Return results if return_wgt and return_ent: return mps,out[2],out[3],out[4] elif return_wgt: return mps,out[2] elif return_ent: return mps,out[2],out[3] else: return mps
def move_gauge_left_svd(ten1,ten2,truncate_mbd=1e100,split_s=False,return_ent=True,return_wgt=True): """ Move the gauge via svd from ten2 to ten1 Args: ten1 : np or ctf array The neighboring left site ten2 : np or ctf array The site currently holding the gauge Kwargs: truncate_mbd : int The Maximum retained Bond Dimension Returns: ten1 : np or ctf array The tensor now holding the gauge ten2 : np or ctf array The now isometrized tensor EE : float The von neumann entanglement entropy Only returned if return_ent == True EEs : 1D Array of floats The von neumann entanglement spectrum Only returned if return_ent == True wgt : float The sum of the discarded weigths Only returned if return_wgt == True """ if DEBUG: res0 = einsum('abc,cde->abde',ten1,ten2).make_sparse() # Perform the svd on the tensor #tmpprint('\t\t\t\t\tDoing SVD') out = ten2.svd(1, truncate_mbd=truncate_mbd, return_ent=return_ent, return_wgt=return_wgt) U,S,V = out[0],out[1],out[2] # Multiply remainder into neighboring site if split_s: #tmpprint('\t\t\t\t\tCombine S & V') ten2 = einsum('ca,apb->cpb',S.sqrt(),V) #tmpprint('\t\t\t\t\tCombine U & S') gauge = einsum('ab,bc->ac',U,S.sqrt()) #tmpprint('\t\t\t\t\tDetermine ten1') ten1 = einsum('apb,bc->apc',ten1,gauge) else: ten2 = V #tmpprint('\t\t\t\t\tCombine U & S') gauge = einsum('ab,bc->ac',U,S) #tmpprint('\t\t\t\t\tDetermine ten1') ten1 = einsum('apb,bc->apc',ten1,gauge) if DEBUG: res1 = einsum('abc,cde->abde',ten1,ten2).make_sparse() mpiprint(0,'SVD (left) Difference = {}'.format((res0-res1).abs().sum())) # Return Results if return_wgt and return_ent: return ten1,ten2,out[3],out[4],out[5] elif return_wgt: return ten1,ten2,out[3] elif return_ent: return ten1,ten2,out[3],out[4] else: return ten1,ten2
def test_energy_contraction_ones_z2(self): mpiprint( 0, '\n' + '=' * 50 + '\nPeps Energy (Ham=Identity, peps=ones, Z2 symmetry) calculation\n' + '-' * 50) # Create a PEPS from cyclopeps.tools.peps_tools import PEPS Nx = 2 Ny = 2 d = 2 D = 3 Zn = 2 backend = 'numpy' peps = PEPS(Nx=Nx, Ny=Ny, d=d, D=D, chi=1000, Zn=2, backend=backend, normalize=False) # Set all tensor values to 1 for xind in range(Nx): for yind in range(Ny): peps[xind][yind].fill_all(1.) # Get the Hamiltonian from cyclopeps.ops.identity import return_op ham = return_op(Nx, Ny, sym='Z2', backend=backend) # Calculate initial norm norm0 = peps.calc_norm() * 4. mpiprint(0, 'Norm (routine) = {}'.format(norm0)) # Perform the Exact energy calculation: bra = einsum('LDWCM,lMXcu->LDluWXCc', peps[0][0], peps[0][1]).remove_empty_ind(0).remove_empty_ind( 0).remove_empty_ind(0).remove_empty_ind(0) bra = einsum('WXCc,CdYRm->dRWXYcm', bra, peps[1][0]).remove_empty_ind(0).remove_empty_ind(0) bra = einsum('WXYcm,cmZru->ruWXYZ', bra, peps[1][1]).remove_empty_ind(0).remove_empty_ind(0) norm1 = einsum('WXYZ,WXYZ->', bra, bra.conj()) norm1 = norm1 * 4. mpiprint(0, 'Norm (explicit) = {}'.format(norm1)) tmp = einsum('WXYZ,wxYZ->WXwx', bra, bra.conj()) E1 = einsum('WXwx,WXwx->', tmp, ham[0][0][0]) tmp = einsum('WXYZ,wXyZ->WYwy', bra, bra.conj()) E1 += einsum('WYwy,WYwy->', tmp, ham[1][0][0]) tmp = einsum('WXYZ,WXyz->YZyz', bra, bra.conj()) E1 += einsum('YZyz,YZyz->', tmp, ham[0][1][0]) tmp = einsum('WXYZ,WxYz->XZxz', bra, bra.conj()) E1 += einsum('XZxz,XZxz->', tmp, ham[1][1][0]) mpiprint(0, 'Explicitly computed energy (not normalized) = {}'.format(E1)) # Contract Energy again E2 = peps.calc_op(ham, normalize=False) mpiprint(0, 'Energy via peps Method (not normalized) = {}'.format(E2)) self.assertTrue(abs((norm0 - norm1) / norm0) < 1e-10) print('Check here {}, {}, {}, {}'.format(norm0, norm1, E1, E2)) self.assertTrue(abs((norm0 - E1) / norm0) < 1e-10) self.assertTrue(abs((norm0 - E2) / norm0) < 1e-10) mpiprint(0, 'Passed\n' + '=' * 50)
def test_energy_itf_contraction(self): mpiprint( 0, '\n' + '=' * 50 + '\nPeps Energy (Ham=ITF, peps=random, no symmetry) calculation\n' + '-' * 50) # Create a PEPS from cyclopeps.tools.peps_tools import PEPS Nx = 2 Ny = 2 d = 2 D = 3 peps = PEPS(Nx=Nx, Ny=Ny, d=d, D=D, chi=1000) peps2 = PEPS(Nx=Nx, Ny=Ny, d=d, D=D, chi=1000) # Get the Hamiltonian from cyclopeps.ops.itf import return_op ham = return_op(Nx, Ny, (1., 2.)) # Perform the Exact energy calculation: bra = einsum('LDWCM,lMXcu->WXCc', peps[0][0], peps[0][1]) bra = einsum('WXCc,CdYRm->WXYcm', bra, peps[1][0]) bra = einsum('WXYcm,cmZru->WXYZ', bra, peps[1][1]) ket = einsum('LDWCM,lMXcu->WXCc', peps2[0][0], peps2[0][1]) ket = einsum('WXCc,CdYRm->WXYcm', ket, peps2[1][0]) ket = einsum('WXYcm,cmZru->WXYZ', ket, peps2[1][1]) norm1 = einsum('WXYZ,WXYZ->', bra, ket) tmp = einsum('WXYZ,wxYZ->WXwx', bra, ket) E1 = einsum('WXwx,WXwx->', tmp, ham[0][0][0]) tmp = einsum('WXYZ,wXyZ->WYwy', bra, ket) E2 = einsum('WYwy,WYwy->', tmp, ham[1][0][0]) tmp = einsum('WXYZ,WXyz->YZyz', bra, ket) E3 = einsum('YZyz,YZyz->', tmp, ham[0][1][0]) tmp = einsum('WXYZ,WxYz->XZxz', bra, ket) E4 = einsum('XZxz,XZxz->', tmp, ham[1][1][0]) E1 = E1 + E2 + E3 + E4 # Contract Energy again E2 = peps.calc_op(ham, normalize=False, chi=1e100, ket=peps2) mpiprint(0, 'Energy (exact) = {}'.format(E1)) mpiprint(0, 'Energy (routine) = {}'.format(E2)) self.assertTrue(abs((E2 - E1) / E1) < 1e-10) mpiprint(0, 'Passed\n' + '=' * 50)
def test_energy_contraction(self): mpiprint( 0, '\n' + '=' * 50 + '\nPeps Energy (Ham=Identity, peps=random, no symmetry) calculation\n' + '-' * 50) # Create a PEPS from cyclopeps.tools.peps_tools import PEPS Nx = 2 Ny = 2 d = 2 D = 3 peps = PEPS(Nx=Nx, Ny=Ny, d=d, D=D, chi=1000) # Get the Hamiltonian from cyclopeps.ops.identity import return_op ham = return_op(Nx, Ny) # Calculate initial norm norm0 = peps.calc_norm() * 4. # Perform the Exact energy calculation: bra = einsum('LDWCM,lMXcu->WXCc', peps[0][0], peps[0][1]) bra = einsum('WXCc,CdYRm->WXYcm', bra, peps[1][0]) bra = einsum('WXYcm,cmZru->WXYZ', bra, peps[1][1]) norm1 = einsum('WXYZ,WXYZ->', bra, bra.conj()) * 4. tmp = einsum('WXYZ,wxYZ->WXwx', bra, bra.conj()) E1 = einsum('WXwx,WXwx->', tmp, ham[0][0][0]) tmp = einsum('WXYZ,wXyZ->WYwy', bra, bra.conj()) E1 += einsum('WYwy,WYwy->', tmp, ham[1][0][0]) tmp = einsum('WXYZ,WXyz->YZyz', bra, bra.conj()) E1 += einsum('YZyz,YZyz->', tmp, ham[0][1][0]) tmp = einsum('WXYZ,WxYz->XZxz', bra, bra.conj()) E1 += einsum('XZxz,XZxz->', tmp, ham[1][1][0]) # Contract Energy again E2 = peps.calc_op(ham, normalize=False) self.assertTrue(abs((norm0 - norm1) / norm0) < 1e-10) mpiprint(0, 'Passed Norm1') self.assertTrue(abs((norm0 - E1) / norm0) < 1e-10) mpiprint(0, 'Passed E1') mpiprint(0, 'Norm from calc_norm = {}'.format(norm0)) mpiprint(0, 'Norm from exact contraction {}'.format(norm1)) mpiprint(0, 'Norm from Energy calc op = {}'.format(E2)) mpiprint(0, 'Norm from Energy exact contraction {}'.format(E1)) #mpiprint(0,norm1,E1,norm0,E2,abs((norm0-E2)/norm0)) self.assertTrue(abs((norm0 - E2) / norm0) < 1e-10) mpiprint(0, 'Passed\n' + '=' * 50)
def test_energy_itf_contraction_ones(self): mpiprint( 0, '\n' + '=' * 50 + '\nPeps Energy (Ham=ITF, peps=ones, no symmetry) calculation\n' + '-' * 50) # Create a PEPS from cyclopeps.tools.peps_tools import PEPS Nx = 2 Ny = 2 d = 2 D = 3 peps = PEPS(Nx=Nx, Ny=Ny, d=d, D=D, chi=1000, normalize=False) # Set all tensor values to 1 for xind in range(Nx): for yind in range(Ny): peps[xind][yind].fill_all(1.) # Get the Hamiltonian from cyclopeps.ops.itf import return_op ham = return_op(Nx, Ny, (1., 2.)) # Calculate initial norm norm0 = peps.calc_norm() mpiprint(0, 'Norm = {}'.format(norm0)) # Perform the Exact energy calculation: bra = einsum('LDWCM,lMXcu->WXCc', peps[0][0], peps[0][1]) bra = einsum('WXCc,CdYRm->WXYcm', bra, peps[1][0]) bra = einsum('WXYcm,cmZru->WXYZ', bra, peps[1][1]) norm1 = einsum('WXYZ,WXYZ->', bra, bra.conj()) mpiprint(0, 'Explicitly computed norm = {}'.format(norm1)) tmp = einsum('WXYZ,wxYZ->WXwx', bra, bra.conj()) E1 = einsum('WXwx,WXwx->', tmp, ham[0][0][0]) tmp = einsum('WXYZ,wXyZ->WYwy', bra, bra.conj()) E1 += einsum('WYwy,WYwy->', tmp, ham[1][0][0]) tmp = einsum('WXYZ,WXyz->YZyz', bra, bra.conj()) E1 += einsum('YZyz,YZyz->', tmp, ham[0][1][0]) tmp = einsum('WXYZ,WxYz->XZxz', bra, bra.conj()) E1 += einsum('XZxz,XZxz->', tmp, ham[1][1][0]) mpiprint(0, 'Explicitly computed energy (not normalized) = {}'.format(E1)) # Contract Energy again E2 = peps.calc_op(ham, normalize=False) mpiprint(0, 'Energy (routine) = {}'.format(E2)) self.assertTrue(abs((E2 - E1) / E1) < 1e-10) mpiprint(0, 'Passed\n' + '=' * 50)
def test_energy_contraction_heis_z2(self): mpiprint( 0, '\n' + '=' * 50 + '\nPeps Energy (Ham=Heisenberg, peps=random, Z2 symmetry) calculation\n' + '-' * 50) # Create a PEPS from cyclopeps.tools.peps_tools import PEPS Nx = 2 Ny = 2 d = 2 D = 3 Zn = 2 backend = 'ctf' peps = PEPS(Nx=Nx, Ny=Ny, d=d, D=D, chi=1000, Zn=2, backend=backend, normalize=False) # Get the Hamiltonian from cyclopeps.ops.heis import return_op ham = return_op(Nx, Ny, sym='Z2', backend=backend) # Calculate initial norm norm0 = peps.calc_norm() mpiprint(0, 'Norm (routine) = {}'.format(norm0)) # Perform the Exact energy calculation: bra = einsum('LDWCM,lMXcu->LDluWXCc', peps[0][0], peps[0][1]).remove_empty_ind(0).remove_empty_ind( 0).remove_empty_ind(0).remove_empty_ind(0) bra = einsum('WXCc,CdYRm->dRWXYcm', bra, peps[1][0]).remove_empty_ind(0).remove_empty_ind(0) bra = einsum('WXYcm,cmZru->ruWXYZ', bra, peps[1][1]).remove_empty_ind(0).remove_empty_ind(0) norm1 = einsum('WXYZ,WXYZ->', bra, bra.conj()) norm1 = norm1 mpiprint(0, 'Norm (explicit) = {}'.format(norm1)) #print(ham[0][0][0]) tmp = einsum('WXYZ,wxYZ->WXwx', bra, bra.conj()) E1 = einsum('WXwx,WXwx->', tmp, ham[0][0][0]) tmp = einsum('WXYZ,wXyZ->WYwy', bra, bra.conj()) E1 += einsum('WYwy,WYwy->', tmp, ham[1][0][0]) tmp = einsum('WXYZ,WXyz->YZyz', bra, bra.conj()) E1 += einsum('YZyz,YZyz->', tmp, ham[0][1][0]) tmp = einsum('WXYZ,WxYz->XZxz', bra, bra.conj()) E1 += einsum('XZxz,XZxz->', tmp, ham[1][1][0]) E1 = E1 mpiprint(0, 'Explicitly computed energy (not normalized) = {}'.format(E1)) # Contract Energy again E2 = peps.calc_op(ham, normalize=False) mpiprint(0, 'Energy via peps Method (not normalized) = {}'.format(E2)) self.assertTrue(abs((norm0 - norm1) / norm0) < 1e-10) self.assertTrue(abs((E2 - E1) / E1) < 1e-10) mpiprint(0, 'Passed\n' + '=' * 50)