= 5 #sites S
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import expm
from scipy.linalg import logm
%config InlineBackend.figure_format = "svg"
import matplotlib.colors as clr
= clr.LinearSegmentedColormap.from_list("",[(0,"#011F5B"),(0.5,"#EEEEEE"),(1,"#990000")]) my_cmap
= lambda u : np.conj(u.T) adj
= np.array([[1,0],[0,1]],dtype=complex)
s0 = np.array([[0,1],[1,0]],dtype=complex)
sx = np.array([[0,-1j],[1j,0]],dtype=complex)
sy = np.array([[1,0],[0,-1]],dtype=complex)
sz
= {"0" : s0,
pauli "x" : sx,
"y" : sy,
"z" : sz,
"+" : (sx+1j*sy)/2,
"-" : (sx-1j*sy)/2}
def spin(polarization,s,S):
if(s==0):
= pauli[polarization]
temp else:
= s0
temp for i in range(1,S,1):
if(i==s):
= np.kron(temp,pauli[polarization])
temp else:
= np.kron(temp,s0)
temp return temp
def vacuum(S):
= np.zeros(2**S,dtype=complex)
vac -1] = 1
vac[return vac
def random_state(S): #random state--not in a particular number sector!
= vacuum(S)
state = bin(2**S+np.random.randint(0,2**S))
b for i in range(1,S+1,1):
if(int(b[-i])==1):
= spin("+",i-1,S)@state
state return state
def H_s(S,dir): #open boundaries
= np.zeros((2**S,2**S),dtype=complex)
H for i in range(0,S-1,1):
+= spin(dir,i,S)@spin(dir,i+1,S)
H return H
= H_s(S,"x")
HX = H_s(S,"y")
HY = H_s(S,"z")
HZ
def h_s(S,dir):
#create a dictionary for the Sz operators
= {}
numbers for i in range(0,S,1):
str(i)] = spin(dir,i,S)
numbers[return numbers
= h_s(S,"x")
hX = h_s(S,"y")
hY = h_s(S,"z")
hZ
def Ham(S,Jx=1,Jy=0,Jz=0,hx=0,hy=0,hz=0):
#build the Hamiltonian
= Jx*HX + Jy*HY + Jz*HZ
H for i in range(0,S,1): #we could do a random transverse field here
+= hx*hX[str(i)] + hy*hY[str(i)] + hz*hZ[str(i)]
H return H
= 20
tmax = 10001 #more steps makes the naive method better
steps = tmax/(steps-1)
dt = ["x","y","z"]
dirs
= vacuum(S) #or make another state
psi0
= Ham(S,Jx=1,hz=0.5)
H = spin("+",0,S) #raising operator at left boundary
J1 = spin("-",S-1,S) #lowering operator at right boundary
J2 = 0.1
G1 = 0.1 G2
Vectorize the density matrix by columns $$ \rho = \begin{pmatrix} \rho_{11}&\rho_{21}\\ \rho_{12}&\rho_{22} \end{pmatrix} \quad \mapsto \quad \vec{\rho} = \begin{pmatrix} \rho_{11}\\ \rho_{12}\\ \rho_{21}\\ \rho_{22} \end{pmatrix} $$
Time evolution is now an eigenvalue operator (not superoperator) equation Lvecρ⃗ = i∂tρ⃗ with
$$ L_\text{vec} = 1\otimes H - H^\top \otimes 1 -i\sum_m \frac{\gamma_m}{2} ((J_m^\dagger J_m)^\top\otimes 1 + 1 \otimes J_m^\dagger J_m - 2(J_m^\dagger)^\top \otimes J_m) $$ where we note the switched order of the dagger term versus the master equation. With U(δt) = e−iδtLvec we have ρ⃗(t+δt) = U(δt)ρ⃗(t) where here δt need not be small.
%%time
= np.identity(2**S)
one
= np.kron(one,H) - np.kron(H.T,one)\
L_vec - 1j*G1/2 * (np.kron(one,adj(J1)@J1) + np.kron((adj(J1)@J1).T,one) - 2*np.kron((adj(J1)).T,J1))\
- 1j*G2/2 * (np.kron(one,adj(J2)@J2) + np.kron((adj(J2)@J2).T,one) - 2*np.kron((adj(J2)).T,J2))
= np.einsum("a,b->ab",adj(psi0),psi0)
rho = np.zeros(2**S * 2**S,dtype=complex)
rho_vec for c in range(2**S):
*2**S:(c+1)*2**S] = rho[:,c]
rho_vec[c
= np.zeros((steps,3))
mags = expm(-1j*dt*L_vec)
U
for t in range(steps):
= U@rho_vec
rho_vec for c in range(2**S):
= rho_vec[c*2**S:(c+1)*2**S]
rho[:,c]
#expectation values
for s in range(S):
for d in range(3):
+= np.real(np.trace(rho@spin(dirs[d],s,S))) / S
mags[t,d] = mags mags_vec
CPU times: user 2min 28s, sys: 26.6 s, total: 2min 55s
Wall time: 15.2 s
Checks: Tr[ρ] = 1? ρ = ρ†? ρ ≽ 0?
print(np.trace(rho))
print(np.linalg.norm(rho-adj(rho)))
print(np.linalg.eigvalsh(rho))
(1.0000000000000329-1.1616479830397412e-15j)
3.934527700058645e-14
[0.00090453 0.00165106 0.00197228 0.00224695 0.00289197 0.00360004
0.00410139 0.00489935 0.00527876 0.00630579 0.00670563 0.00718395
0.00894287 0.01151006 0.01223989 0.01311299 0.01462125 0.01566423
0.01665747 0.02143925 0.02668842 0.02859218 0.03040516 0.03632071
0.03913343 0.04674714 0.05325733 0.06629681 0.08532835 0.09721151
0.11612475 0.21196447]
z-magnetization
0,tmax,steps),mags[:,2])
plt.plot(np.linspace(-1.1,1.1)
plt.ylim("t")
plt.xlabel(r"$\langle S^z\rangle$") plt.ylabel(
Text(0, 0.5, '$\\langle S^z\\rangle$')
We can numerically solve Lvecρ⃗ss = 0 for ρ⃗ss.
=".")
plt.scatter(np.real(np.linalg.eigvals(L_vec)),np.imag(np.linalg.eigvals(L_vec)),marker"Re(eig)")
plt.xlabel("Im(eig)") plt.ylabel(
Text(0, 0.5, 'Im(eig)')
= np.linalg.eig(L_vec)[0]
vals = np.argmin(np.abs(vals))
idx abs(vals)),marker=".")
plt.plot(np.sort(np.-1,10)
plt.xlim(-0.01,0.1)
plt.ylim("eigenvalue index")
plt.xlabel("|eig|") plt.ylabel(
Text(0, 0.5, '|eig|')
Find and construct ρss
= np.linalg.eig(L_vec)[0]
vals = np.argmin(np.abs(vals)) #careful--check for uniqueness
idx print(vals[idx])
= np.linalg.eig(L_vec)[1][:,idx]
vec_ss = np.zeros((2**S,2**S),dtype=complex)
rho_ss for c in range(2**S):
= vec_ss[c*2**S:(c+1)*2**S]
rho_ss[:,c] print(np.trace(rho_ss))
= rho_ss/np.trace(rho_ss)
rho_ss print(np.linalg.norm(rho_ss-adj(rho_ss)))
print(np.linalg.eigh(rho_ss)[0])
(3.210023821369188e-16-9.727698635837807e-16j)
(5.60115637700194+1.0076661727254077e-13j)
1.1424715863040574e-14
[0.02355502 0.02355502 0.02602636 0.02602636 0.02602636 0.02602636
0.02800245 0.02800245 0.02800245 0.02800245 0.02875699 0.02875699
0.03094041 0.03094041 0.03094041 0.03094041 0.03094041 0.03094041
0.03094041 0.03094041 0.0332896 0.0332896 0.03418661 0.03418661
0.03418661 0.03418661 0.03678227 0.03678227 0.03678227 0.03678227
0.04064139 0.04064139]
Steady state magnetization ⟨Sz⟩ and connected correlation function ⟨(Sz)2⟩ − ⟨Sz⟩2
= 0
mz for i in range(0,S,1):
+= np.trace(rho_ss@spin("z",i,S)) / S
mz print(np.real(mz))
1.1311481265541268e-15
= 0
mz2 for i in range(0,S,1):
+= np.trace(rho_ss@spin("z",i,S))**2 / S
mz2 print(np.real(mz2-mz**2))
2.481374156352871e-06
Numerically integrate the master equation in time using the Euler method $$ \rho(t+\delta t) = \rho(t) - i\, \delta t \bigg([H,\rho(t)]-i\sum_m \frac{\gamma_m}{2} (J_m^\dagger J_m\rho + \rho J_m^\dagger J_m - 2J_m\rho J_m^\dagger)\bigg) $$
Making the timestep smaller seems to help.
%%time
= np.einsum("a,b->ab",adj(psi0),psi0)
rho = np.zeros((steps,3))
mags = np.zeros(steps)
trace for t in range(steps):
= rho + -1j*dt * ((H@rho-rho@H)\
rho -1j*G1/2*(adj(J1)@J1@rho + rho@adj(J1)@J1 - 2*J1@rho@adj(J1))\
-1j*G2/2*(adj(J2)@J2@rho + rho@adj(J2)@J2 - 2*J2@rho@adj(J2)))
for s in range(S):
for d in range(3):
+= np.real(np.trace(rho@spin(dirs[d],s,S))) / S mags[t,d]
CPU times: user 8.99 s, sys: 2.12 s, total: 11.1 s
Wall time: 8.86 s
Checks: Tr[ρ] = 1? ρ = ρ†? ρ ≽ 0?
print(np.trace(rho))
print(np.linalg.norm(rho-adj(rho)))
print(np.linalg.eigvalsh(rho))
(0.9999999999999996+0j)
0.0
[-0.02554943 -0.01387799 -0.0023756 -0.00128043 -0.00126105 -0.00071998
0.00099114 0.00129039 0.0015456 0.00256821 0.0028678 0.00484358
0.00543601 0.00681364 0.00999824 0.01104068 0.01243214 0.01567089
0.01695945 0.02029302 0.02106511 0.03038169 0.03169766 0.03605861
0.03941125 0.04855301 0.05924942 0.07005057 0.09096301 0.10952534
0.13837507 0.25698296]
We can make this a little less naive by imposing hermiticity, positivity, and trace preservation at each time step
Fixing the trace ρ → ρ/Tr[ρ] and hemiticity ρ → (ρ+ρ†)/2 is straightforward.
Throwing out the negative eigenvalues can be done using a spectral decomposition ρ = ∑ipi|ui⟩⟨ui| so ρ ≈ ∑i : pi > 0pi|ui⟩⟨ui| where we have that ρ|ui⟩=pi|ui⟩.
%%time
= np.einsum("a,b->ab",adj(psi0),psi0)
rho = np.zeros((steps,3))
mags_positive = np.zeros(steps)
trace for t in range(steps):
= rho + -1j*dt * ((H@rho-rho@H)\
rho -1j*G1/2*(adj(J1)@J1@rho + rho@adj(J1)@J1 - 2*J1@rho@adj(J1))\
-1j*G2/2*(adj(J2)@J2@rho + rho@adj(J2)@J2 - 2*J2@rho@adj(J2)))
= (rho + adj(rho))/2
rho = np.linalg.eigh(rho)
vals, vecs = np.zeros(np.shape(rho),dtype=complex)
temp for i in range(np.shape(vals)[0]):
if(vals[i] > 0):
+= vals[i] * np.outer(vecs[:,i],adj(vecs[:,i]))
temp = temp
rho /= np.trace(rho)
rho for s in range(S):
for d in range(3):
+= np.real(np.trace(rho@spin(dirs[d],s,S))) / S
mags_positive[t,d]
CPU times: user 22.6 s, sys: 97.3 ms, total: 22.7 s
Wall time: 11.4 s
Checks: Tr[ρ] = 1? ρ = ρ†? ρ ≽ 0?
print(np.trace(rho))
print(np.linalg.norm(rho-adj(rho)))
print(np.linalg.eigvalsh(rho))
(1+0j)
0.0
[4.02368706e-18 9.94160060e-18 4.09618525e-04 4.39644257e-04
6.94372323e-04 1.11037775e-03 1.31602362e-03 1.33881574e-03
1.80087585e-03 3.38834871e-03 3.54193264e-03 5.51638934e-03
6.54383536e-03 6.86132610e-03 1.01220575e-02 1.06866405e-02
1.24388192e-02 1.52986279e-02 1.69047108e-02 1.95855626e-02
2.03256785e-02 2.94157579e-02 3.11941022e-02 3.49363718e-02
3.78293450e-02 4.69500689e-02 5.58588975e-02 6.79659109e-02
8.76700049e-02 1.03419199e-01 1.28444810e-01 2.37991874e-01]
z-magnetization
0,tmax,steps),mags_vec[:,2],label="exact vectorized")
plt.plot(np.linspace(0,tmax,steps),mags[:,2],ls="dotted",label="naive integrate")
plt.plot(np.linspace(0,tmax,steps),mags_positive[:,2],ls="dashed",label="integrate (positive)")
plt.plot(np.linspace(-1.1,0.4)
plt.ylim(
plt.legend()"t")
plt.xlabel(r"$\langle S^z\rangle$") plt.ylabel(
Text(0, 0.5, '$\\langle S^z\\rangle$')