-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcascade.py
209 lines (179 loc) · 6.2 KB
/
cascade.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
"""
This module contains
- A wrapper class around qutip's Qobj class to allow for some tensor
manipulations. Mostly to be able to perform generalized partial trace.
- Functions to integrate the master equation for k cascaded identical systems.
"""
import numpy as np
import scipy as sp
import qutip as qt
# Flag to decide if we are going to use cython for fast computation of
# tensor network generalized partial trace.
usecython = True
if usecython:
import tnintegrate_c
class TensorQobj(qt.Qobj):
"""
A wrapper around qutip Qobj to be able to view it as a tensor.
This class is meant for representing super-operators.
Each index of the tensor is a "double index" of dimension
2*dim(H), i.e., the dimension of L(H). This convention
is chosen to make it easier to work with qutip Qobj's of
"super" type, representing superoperators. For example, this
is consistent with how qutip's reshuffle works.
"""
@property
def nsys(self):
# number of subsystems for the underlying Hilbert space
return len(self.reshuffle().dims[0])
@property
def rank(self):
# rank of tensor
# each index is really a "double index"
return self.nsys*2
@property
def sysdim(self):
# dim of H
return self.dims[0][0][0]
@property
def superdim(self):
# dim of L(H)
return self.sysdim**2
def __mul__(self, other):
return TensorQobj(super(TensorQobj,self).__mul__(other))
def __rmul__(self, other):
return TensorQobj(super(TensorQobj,self).__rmul__(other))
def reshuffle(self):
return TensorQobj(qt.reshuffle(self))
def getmatrixindex(self,indices):
# returns matrix indices of T given tensor indices
# each subsystem has dimension self.superdim (double index)
# indices = [i1,j1,...,iM,jM]
if not len(indices) == self.rank:
raise ValueError("number of indices do not match rank of tensor")
ii = 0
jj = 0
idx = list(indices)
for l in range(len(indices)/2):
j = idx.pop()
i = idx.pop()
ii += i*self.superdim**l
jj += j*self.superdim**l
return ii,jj
def gettensorelement(self,indices):
# return element given tensor indices
return self.reshuffle()[self.getmatrixindex(indices)]
def loop(self):
# return T reduced by one subsystem by summing over 2 indices
out = TensorQobj(dims=[[[self.sysdim]*(self.nsys-1)]*2]*2)
idx = [0]*out.rank
indices = []
sumindices = []
for cnt in range(out.superdim**(out.rank)):
indices.append(list(idx))
sumindices.append(list(idx[0:-1] + [0,0] + idx[-1:]))
idx[0] += 1
for i in range(len(idx)-1):
if idx[i] > self.superdim-1:
idx[i] = 0
idx[i+1] += 1
out2 = out.reshuffle()
if usecython:
indices = np.array(indices,dtype=np.int_)
sumindices = np.array(sumindices,dtype=np.int_)
indata = np.array(self.reshuffle().data.toarray(),dtype=np.complex_)
data = tnintegrate_c.loop(indata,out2.shape,
indices,sumindices,self.superdim)
out2 = TensorQobj(data,dims=out2.dims)
else:
for idx in indices:
i,j = out.getmatrixindex(idx)
for k in range(self.superdim):
sumidx = idx[0:-1] + [k,k] + idx[-1:]
out2.data[i,j] += self.gettensorelement(sumidx)
return out2.reshuffle()
def generator(k,H,L1,L2):
"""
Create the generator for the cascaded chain of k system copies
"""
# create bare operators
id = qt.qeye(H.dims[0][0])
Id = qt.spre(id)*qt.spost(id)
Hlist = []
L1list = []
L2list = []
for l in range(1,k+1):
h = H
l1 = L1
l2 = L2
for i in range(1,l):
h = qt.tensor(h,id)
l1 = qt.tensor(l1,id)
l2 = qt.tensor(l2,id)
for i in range(l+1,k+1):
h = qt.tensor(id,h)
l1 = qt.tensor(id,l1)
l2 = qt.tensor(id,l2)
Hlist.append(h)
L1list.append(l1)
L2list.append(l2)
# create Lindbladian
L = qt.Qobj()
H0 = 0.5*Hlist[0]
L0 = L2list[0]
#L0 = 0.*L2list[0]
L += qt.liouvillian(H0,[L0])
E0 = Id
for l in range(k-1):
E0 = qt.composite(Id,E0)
Hl = 0.5*(Hlist[l]+Hlist[l+1]+1j*(L1list[l].dag()*L2list[l+1]
-L2list[l+1].dag()*L1list[l]))
Ll = L1list[l] + L2list[l+1]
L += qt.liouvillian(Hl,[Ll])
Hk = 0.5*Hlist[k-1]
Hk = 0.5*Hlist[k-1]
Lk = L1list[k-1]
L += qt.liouvillian(Hk,[Lk])
E0.dims = L.dims
return L,E0
def integrate(L,E0,ti,tf,opt=qt.Options()):
"""
Basic ode integrator
"""
def _rhs(t,y,L):
ym = y.reshape(L.shape)
return (L*ym).flatten()
from qutip.superoperator import vec2mat
r = sp.integrate.ode(_rhs)
r.set_f_params(L.data)
initial_vector = E0.data.toarray().flatten()
r.set_integrator('zvode', method=opt.method, order=opt.order,
atol=opt.atol, rtol=opt.rtol, nsteps=opt.nsteps,
first_step=opt.first_step, min_step=opt.min_step,
max_step=opt.max_step)
r.set_initial_value(initial_vector, ti)
r.integrate(tf)
if not r.successful():
raise Exception("ODE integration error: Try to increase "
"the allowed number of substeps by increasing "
"the nsteps parameter in the Options class.")
return qt.Qobj(vec2mat(r.y)).trans()
def rhot(rho0,t,tau,H_S,L1,L2,Id,options=qt.Options()):
"""
Compute rho(t)
"""
k= int(t/tau)+1
s = t-(k-1)*tau
rhovec = qt.operator_to_vector(rho0)
G1,E0 = generator(k,H_S,L1,L2)
E = integrate(G1,E0,0.,s,opt=options)
if k>1:
G2,null = generator(k-1,H_S,L1,L2)
G2 = qt.composite(Id,G2)
E = integrate(G2,E,s,tau,opt=options)
E.dims = E0.dims
E = TensorQobj(E)
for l in range(k-1):
E = E.loop()
sol = qt.vector_to_operator(E*rhovec)
return sol