Problem with npc.detect_legcharge

How do I use this algorithm? What does that parameter do?
Post Reply
vmen
Posts: 6
Joined: 21 Nov 2023, 03:25

Problem with npc.detect_legcharge

Post by vmen »

Hi,

This question deals with somewhat uncommon usage of the np_conserved module. I would greatly appreciate help, as I suspect there may be a bug with the npc.detect_legcharge function.

I am using the np_conserved module to create my own MPO. The purpose is to simulate a layer of two qubit gates. Suppose I have a 2 qubit unitary \(G\) acting on qubits q1 and q2. I SVD \(G = U\Lambda V\), then define \(A = U\Lambda \) and \(B = V\). Then I can define an MPO for the 2 qubit gate as

Python: Select all

A-I-I-I-I-..B
starting at q1, ending at q2, where \(I = \delta_{W_L ,W_R}\delta_{p,p*}\) adopting Tenpy notation for the bond and physical indices, and A and B have been appropriately reshaped to (wL,wR,p,p*) indices.

More generally, I can apply a layer of 2 qubit gates simultaneously in one MPO as long as the supports of the identity strings in between A and B do not overlap as:

Python: Select all

A1-I-I...-B1-i-i-A2-I-I-,,,-B2..-i-An ... -I-Bn
where the small i denotes \(I = \delta_{p,p*}\) since the bond variables are trivial on these sites.

I know that the two qubit gates G I am using preserve particle number, so I would like the MPO tensors to respect a U(1) charge conservation. In general, each of the sets of As and Bs could have different dimension after the SVD, so I do not want to pre-define leg charges for each tensor, and instead want to use

Python: Select all

npc.detect_legcharge()
.

The following code shows how I do this for a single two qubit gate U acting on sites (ind1, ind2) of the MPO/MPS.

Python: Select all


def two_q_gate_MPO(sites, U, ind1, ind2):
chinfo = npc.ChargeInfo([1], ['2*Sz'])
leg_charge_p1 = npc.LegCharge.from_qflat(chinfo, [[-1],[1]], qconj=1,)
leg_charge_p2 = npc.LegCharge.from_qflat(chinfo, [[-1], [1]], qconj=-1)
leg_charge_left = npc.LegCharge.from_qflat(chinfo, [[0]], qconj=1) #free to choose wL charge of left-most tensor

L = len(sites) 
Ws = [None for _ in range(L)]
Id = np.eye(2)

for i in range(ind1):
    if i == 0:
        charge_idL = npc.detect_legcharge(Id[None,None,:,:],chinfo,[leg_charge_left, None, leg_charge_p1, leg_charge_p2], qconj=-1) #use left-most charge freedom
    else:
        charge_idL = npc.detect_legcharge(Id[None,None,:,:],chinfo,[Ws[i-1].legs[1].conj(), None, leg_charge_p1, leg_charge_p2], qconj=-1) #detect charge of wR using p, p* known charges and wR.conj() charge of previous tensor
    Ws[i] = npc.Array.from_ndarray(Id[None,None,:,:], charge_idL, labels=['wL', 'wR', 'p', 'p*'])

U = np.reshape(U,(2,2,2,2)) 
U = np.reshape(np.transpose(U,[0,2,1,3]), (4,4)) #reshape to wL,p,wR,p* then split for SVD
u,s,vh = sp.linalg.svd(U, full_matrices=False)
sprime = np.trim_zeros(s)
uprime = u[:,:len(sprime)]
vhprime = vh[:len(sprime),:]

A = np.reshape(uprime@np.diag(sprime), (2,2,len(sprime)))
A = np.transpose(A, [2,0,1]) 
A = A[None, :, : , :] 
        
B = np.reshape(vhprime, (len(sprime),2,2))
B = B[:,None,:,:] 

assert A.shape[1] == B.shape[0]
bond_dim = A.shape[1]
I_tensor = (np.kron(np.eye(bond_dim), np.eye(2))).reshape(bond_dim,2,bond_dim,2).swapaxes(1,2) #kronecker delta on both physical and bond

#charge for A
if ind1 == 0:
     charge1  = npc.detect_legcharge(A,chinfo,[leg_charge_left, None, leg_charge_p1, leg_charge_p2], qconj=-1) #use detect leg charge with left_charge
else:
     charge1 = npc.detect_legcharge(A,chinfo,[Ws[ind1-1].legs[1].conj(), None, leg_charge_p1, leg_charge_p2], qconj=-1)
Ws[ind1] = npc.Array.from_ndarray(A, charge1, labels=['wL', 'wR', 'p', 'p*'])

#put I_tensor in between A and B
for i in range(ind1+1, ind2):
     charge_Itensor = npc.detect_legcharge(I_tensor,chinfo,[Ws[i-1].legs[1].conj(), None, leg_charge_p1, leg_charge_p2], qconj=-1) 
     Ws[i] = npc.Array.from_ndarray(I_tensor, charge_Itensor, labels=['wL', 'wR', 'p', 'p*'])

#charge for B
charge2 = npc.detect_legcharge(B,chinfo,[Ws[ind2-1].legs[1].conj(), None, leg_charge_p1, leg_charge_p2], qconj=-1)
Ws[ind2] = npc.Array.from_ndarray(B, charge2, labels=['wL', 'wR', 'p', 'p*'])

#identity on last sites from B to end
for i in range(ind2+1, len(sites)):
    charge_Idlast = npc.detect_legcharge(Id[None,None,:,:],chinfo,[Ws[i-1].legs[1].conj(), None, leg_charge_p1, leg_charge_p2], qconj=-1)
    Ws[i] = npc.Array.from_ndarray(Id[None,None,:,:], charge_Idlast, labels=['wL', 'wR', 'p', 'p*'])

return MPO(sites, Ws, 'finite', 0, -1)
This code works sometimes for certain U, however when I sometimes choose a different (but still U(1) conserving) U, I get the following error from the array creation line

Python: Select all

Ws[ind1] = npc.Array.from_ndarray(A, charge1, labels=['wL', 'wR', 'p', 'p*'])
for the A tensor:

Python: Select all

ValueError: wrong sector with non-zero entries
My understanding is that this should not happen - I am using detect_legcharge with 3 legs: the two physical legs that have fixed charge, and the one wL leg that is conjugated from the previous tensor's wR leg. Anyone know what could be happening here? For concreteness, U can be thought of as the time evolution of a certain two-site exchange Hamiltonian with disorder. For low disorders, the above code works fine for U. Somehow as I increase disorder I get the wrong sector error above.
Post Reply