Problem with npc.detect_legcharge
Posted: 28 Jun 2024, 16:11
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 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:
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.
The following code shows how I do this for a single two qubit gate U acting on sites (ind1, ind2) of the MPO/MPS.
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 for the A tensor:
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.
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
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
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)
Python: Select all
Ws[ind1] = npc.Array.from_ndarray(A, charge1, labels=['wL', 'wR', 'p', 'p*'])
Python: Select all
ValueError: wrong sector with non-zero entries