qflux 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of qflux might be problematic. Click here for more details.
- qflux/GQME/__init__.py +7 -0
- qflux/GQME/dynamics_GQME.py +438 -0
- qflux/GQME/params.py +62 -0
- qflux/GQME/readwrite.py +119 -0
- qflux/GQME/tdvp.py +233 -0
- qflux/GQME/tt_tfd.py +448 -0
- qflux/__init__.py +5 -0
- qflux/closed_systems/__init__.py +17 -0
- qflux/closed_systems/classical_methods.py +427 -0
- qflux/closed_systems/custom_execute.py +22 -0
- qflux/closed_systems/hamiltonians.py +88 -0
- qflux/closed_systems/qubit_methods.py +266 -0
- qflux/closed_systems/spin_dynamics_oo.py +371 -0
- qflux/closed_systems/spin_propagators.py +300 -0
- qflux/closed_systems/utils.py +205 -0
- qflux/open_systems/__init__.py +2 -0
- qflux/open_systems/dilation_circuit.py +183 -0
- qflux/open_systems/numerical_methods.py +303 -0
- qflux/open_systems/params.py +29 -0
- qflux/open_systems/quantum_simulation.py +360 -0
- qflux/open_systems/trans_basis.py +121 -0
- qflux/open_systems/walsh_gray_optimization.py +311 -0
- qflux/typing/__init__.py +0 -0
- qflux/typing/examples.py +24 -0
- qflux/utils/__init__.py +0 -0
- qflux/utils/io.py +16 -0
- qflux/utils/logging_config.py +61 -0
- qflux/variational_methods/__init__.py +1 -0
- qflux/variational_methods/qmad/__init__.py +0 -0
- qflux/variational_methods/qmad/ansatz.py +64 -0
- qflux/variational_methods/qmad/ansatzVect.py +61 -0
- qflux/variational_methods/qmad/effh.py +75 -0
- qflux/variational_methods/qmad/solver.py +356 -0
- qflux-0.0.1.dist-info/METADATA +144 -0
- qflux-0.0.1.dist-info/RECORD +38 -0
- qflux-0.0.1.dist-info/WHEEL +5 -0
- qflux-0.0.1.dist-info/licenses/LICENSE +674 -0
- qflux-0.0.1.dist-info/top_level.txt +1 -0
qflux/GQME/tdvp.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import functools
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from mpsqd.utils import MPS
|
|
6
|
+
from mpsqd.utils.split import split_rq,split_qr
|
|
7
|
+
|
|
8
|
+
from mpsqd.tdvp import ttfunc as ttf
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
Modified from Mpsqd (https://github.com/qiangshi-group/MPSQD)
|
|
13
|
+
|
|
14
|
+
The MIT License (MIT)
|
|
15
|
+
Copyright (c) 2020 mpsqd team, Qiang Shi group
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
+
in the Software without restriction, including without limitation the rights
|
|
20
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
+
furnished to do so, subject to the following conditions:
|
|
23
|
+
|
|
24
|
+
The above copyright notice and this permission notice shall be included in all
|
|
25
|
+
copies or substantial portions of the Software.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|
|
34
|
+
|
|
35
|
+
By Weizhong Guan; Peng Bao; Jiawei Peng; Zhenggang Lan and Qiang Shi
|
|
36
|
+
J. Chem. Phys. 161, 122501 (2024)
|
|
37
|
+
Changes:
|
|
38
|
+
add rk4slices as argument (for update_type='rk4')
|
|
39
|
+
"""
|
|
40
|
+
def tdvp1site(rin,pall,dt,update_type='krylov',mmax=30,nsteps=1,rk4slices = 10):
|
|
41
|
+
argdict = {'dt':dt,'mmax':mmax,'nsteps':nsteps,'update_type':update_type, 'rk4slices':rk4slices}
|
|
42
|
+
try:
|
|
43
|
+
for key in ['nsteps','mmax']:
|
|
44
|
+
argdict[key] = int(argdict[key])
|
|
45
|
+
for key in ['dt']:
|
|
46
|
+
argdict[key] = float(argdict[key])
|
|
47
|
+
for key in ['dt','nsteps','mmax']:
|
|
48
|
+
if (argdict[key] <= 0):
|
|
49
|
+
print("Wrong value in tdvp: ",key," = ",argdict[key])
|
|
50
|
+
sys.exit()
|
|
51
|
+
key = 'update_type'
|
|
52
|
+
argdict[key] = argdict[key].lower()
|
|
53
|
+
if (not argdict[key] in ['krylov','rk4']):
|
|
54
|
+
print("Wrong value in tdvp: update_type = ",update_type)
|
|
55
|
+
sys.exit()
|
|
56
|
+
if(rin.__class__.__name__ != "MPS"):
|
|
57
|
+
print("Wrong value in tdvp: improper MPS")
|
|
58
|
+
sys.exit()
|
|
59
|
+
if(pall.__class__.__name__ != "MPO"):
|
|
60
|
+
print("Wrong value in tdvp: improper MPO")
|
|
61
|
+
sys.exit()
|
|
62
|
+
except (ValueError):
|
|
63
|
+
print("Wrong value in tdvp: ",key," = ",argdict[key])
|
|
64
|
+
sys.exit()
|
|
65
|
+
for istep in range(nsteps):
|
|
66
|
+
rin = _tdvp1(rin,pall,argdict['dt'],argdict['update_type'],argdict['mmax'],argdict['rk4slices'])
|
|
67
|
+
return rin
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
Modified from Mpsqd (https://github.com/qiangshi-group/MPSQD)
|
|
72
|
+
|
|
73
|
+
The MIT License (MIT)
|
|
74
|
+
Copyright (c) 2020 mpsqd team, Qiang Shi group
|
|
75
|
+
|
|
76
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
77
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
78
|
+
in the Software without restriction, including without limitation the rights
|
|
79
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
80
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
81
|
+
furnished to do so, subject to the following conditions:
|
|
82
|
+
|
|
83
|
+
The above copyright notice and this permission notice shall be included in all
|
|
84
|
+
copies or substantial portions of the Software.
|
|
85
|
+
|
|
86
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
87
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
88
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
89
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
90
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
91
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
92
|
+
SOFTWARE.
|
|
93
|
+
|
|
94
|
+
By Weizhong Guan; Peng Bao; Jiawei Peng; Zhenggang Lan and Qiang Shi
|
|
95
|
+
J. Chem. Phys. 161, 122501 (2024)
|
|
96
|
+
Changes:
|
|
97
|
+
add rk4slices as argument (for update_type='rk4')
|
|
98
|
+
"""
|
|
99
|
+
def _tdvp1(rin,pall,dt,update_type='krylov',mmax=30, rk4slices = 10):
|
|
100
|
+
|
|
101
|
+
dt2 = 0.5*dt
|
|
102
|
+
if(update_type=='krylov'):
|
|
103
|
+
update_v = functools.partial(ttf.expmv,mmax=mmax,dt=dt2)
|
|
104
|
+
else:
|
|
105
|
+
update_v = functools.partial(ttf.update_rk4,dt=dt2,rk4slices=rk4slices)
|
|
106
|
+
|
|
107
|
+
nlen = len(rin.nodes)
|
|
108
|
+
r1 = MPS(nlen,rin.nb)
|
|
109
|
+
r2 = MPS(nlen,rin.nb)
|
|
110
|
+
r3 = MPS(nlen,rin.nb)
|
|
111
|
+
|
|
112
|
+
phia = []
|
|
113
|
+
|
|
114
|
+
# phia[nlevel+2]
|
|
115
|
+
vtmp = np.ones((1,1,1),dtype=np.complex128)
|
|
116
|
+
phia.append(vtmp)
|
|
117
|
+
|
|
118
|
+
#======================================================
|
|
119
|
+
# ortho from right
|
|
120
|
+
r, q = split_rq(rin.nodes[nlen-1])
|
|
121
|
+
|
|
122
|
+
r1.nodes.append(q)
|
|
123
|
+
u1 = r
|
|
124
|
+
# phia[nlevel+1]
|
|
125
|
+
phia.append(ttf.phia_next(phia[0],pall.nodes[nlen-1],q,q,1))
|
|
126
|
+
#-------------------------------------------------------
|
|
127
|
+
# intermediate terms
|
|
128
|
+
for i in range(nlen-2,0,-1):
|
|
129
|
+
|
|
130
|
+
rtmp = np.tensordot(rin.nodes[i], u1, axes=((2),(0)))
|
|
131
|
+
|
|
132
|
+
r, q = split_rq(rtmp)
|
|
133
|
+
r1.nodes.append(q)
|
|
134
|
+
u1 = r
|
|
135
|
+
|
|
136
|
+
phia.append(ttf.phia_next(phia[nlen-1-i],pall.nodes[i],q,q,1))
|
|
137
|
+
|
|
138
|
+
# the left matrix
|
|
139
|
+
|
|
140
|
+
rtmp = np.tensordot(rin.nodes[0], u1, axes=((2),(0)))
|
|
141
|
+
r1.nodes.append(rtmp)
|
|
142
|
+
|
|
143
|
+
r1.nodes.reverse()
|
|
144
|
+
|
|
145
|
+
# add phia[0] and reverse to normal
|
|
146
|
+
vtmp = np.ones((1,1,1),dtype=np.complex128)
|
|
147
|
+
phia.append(vtmp)
|
|
148
|
+
phia.reverse()
|
|
149
|
+
|
|
150
|
+
#===============================================
|
|
151
|
+
###### the first part of KSL, from left to right
|
|
152
|
+
for i in range(nlen-1):
|
|
153
|
+
phi1 = phia[i]
|
|
154
|
+
phi2 = phia[i+1]
|
|
155
|
+
|
|
156
|
+
if (i == 0):
|
|
157
|
+
ksol = r1.nodes[i].copy()
|
|
158
|
+
else:
|
|
159
|
+
ksol = r2.nodes[i].copy()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
#for islice in range(pa.rk4slice):
|
|
163
|
+
ksol = update_v(yy=ksol, phi1=phi1, phi2=phi2, mat1=pall.nodes[i])
|
|
164
|
+
q, r = split_qr(ksol)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if (i == 0):
|
|
168
|
+
r2.nodes.append(q)
|
|
169
|
+
else:
|
|
170
|
+
r2.nodes[i] = q
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
phi1 = ttf.phia_next(phi1,pall.nodes[i],q,q,0)
|
|
174
|
+
phia[i+1] = phi1
|
|
175
|
+
|
|
176
|
+
# ?? need to copy
|
|
177
|
+
ssol = r
|
|
178
|
+
ssol = update_v(yy=ssol, phi1=phi1, phi2=phi2)
|
|
179
|
+
|
|
180
|
+
rtmp1 = np.tensordot(ssol,r1.nodes[i+1],axes=((1),(0)))
|
|
181
|
+
r2.nodes.append(rtmp1)
|
|
182
|
+
|
|
183
|
+
#--------------------------------------------------------
|
|
184
|
+
### right most part
|
|
185
|
+
|
|
186
|
+
phi1 = phia[nlen-1]
|
|
187
|
+
phi2 = phia[nlen]
|
|
188
|
+
ksol = r2.nodes[nlen-1].copy()
|
|
189
|
+
ksol = update_v(yy=ksol, phi1=phi1, phi2=phi2, mat1=pall.nodes[nlen-1])
|
|
190
|
+
|
|
191
|
+
r2.nodes[nlen-1] = ksol
|
|
192
|
+
#===================================================================
|
|
193
|
+
###### the second part of KSL, from right to left
|
|
194
|
+
for i in range(nlen-1,0,-1):
|
|
195
|
+
|
|
196
|
+
phi1 = phia[i]
|
|
197
|
+
phi2 = phia[i+1]
|
|
198
|
+
|
|
199
|
+
if (i==nlen-1):
|
|
200
|
+
ksol = r2.nodes[i].copy()
|
|
201
|
+
else:
|
|
202
|
+
ksol = r3.nodes[nlen-1-i].copy()
|
|
203
|
+
|
|
204
|
+
#for islice in range(pa.rk4slice):
|
|
205
|
+
ksol = update_v(yy=ksol, phi1=phi1, phi2=phi2, mat1=pall.nodes[i])
|
|
206
|
+
r, q = split_rq(ksol)
|
|
207
|
+
|
|
208
|
+
if (i==nlen-1):
|
|
209
|
+
r3.nodes.append(q)
|
|
210
|
+
else:
|
|
211
|
+
r3.nodes[nlen-1-i] = q
|
|
212
|
+
|
|
213
|
+
phi2 = ttf.phia_next(phi2,pall.nodes[i],q,q,1)
|
|
214
|
+
phia[i] = phi2
|
|
215
|
+
|
|
216
|
+
ssol = r
|
|
217
|
+
|
|
218
|
+
#for islice in range(pa.rk4slice):
|
|
219
|
+
ssol = update_v(yy=ssol, phi1=phi1, phi2=phi2)
|
|
220
|
+
|
|
221
|
+
rtmp1 = np.tensordot(r2.nodes[i-1],ssol,axes=((2),(0)))
|
|
222
|
+
r3.nodes.append(rtmp1)
|
|
223
|
+
|
|
224
|
+
r3.nodes.reverse()
|
|
225
|
+
### the left most matrix
|
|
226
|
+
phi1 = phia[0]
|
|
227
|
+
phi2 = phia[1]
|
|
228
|
+
ksol = r3.nodes[0].copy()
|
|
229
|
+
ksol = update_v(yy=ksol, phi1=phi1, phi2=phi2, mat1=pall.nodes[0])
|
|
230
|
+
|
|
231
|
+
r3.nodes[0] = ksol
|
|
232
|
+
|
|
233
|
+
return r3
|
qflux/GQME/tt_tfd.py
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This section uses some MPSQD functions to perform TT-TFD calculations for the Spin-Boson model dynamics.
|
|
3
|
+
Created by Xiaohan Dan
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from . import params as pa
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from mpsqd.utils import MPS,add_tensor, MPS2MPO, calc_overlap
|
|
11
|
+
from .tdvp import tdvp1site
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def initial(istate: int) -> MPS:
|
|
16
|
+
"""
|
|
17
|
+
Initialize the state in the tensor train format for TT-TFD calculation.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
istate (int): The initial state type to be used:
|
|
21
|
+
- 0: Spin-Up state.
|
|
22
|
+
- 1: Equal superposition of spin-Up and spin-Down.
|
|
23
|
+
- 2: Superposition of spin-Up and 1j * spin-Down.
|
|
24
|
+
- 3: Spin-Down state.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
MPS: The initialized MPS object with the specified initial state.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
#initial state
|
|
31
|
+
#Build initial ground state at spin-Up state
|
|
32
|
+
su = np.zeros((1,pa.DOF_E,pa.MAX_TT_RANK),dtype=np.complex128)
|
|
33
|
+
sd = np.zeros((1,pa.DOF_E,pa.MAX_TT_RANK),dtype=np.complex128)
|
|
34
|
+
|
|
35
|
+
su[0,:,0] = pa.spin_up
|
|
36
|
+
sd[0,:,0] = pa.spin_down
|
|
37
|
+
|
|
38
|
+
e1 = np.sqrt(0.5) * (su + sd)
|
|
39
|
+
e2 = np.sqrt(0.5) * (su + 1j * sd)
|
|
40
|
+
|
|
41
|
+
# initial MPS wavepacket
|
|
42
|
+
nbarr = np.ones((1+2*pa.DOF_N))*pa.occ
|
|
43
|
+
nbarr[0] = pa.DOF_E
|
|
44
|
+
y0 = MPS(1+2*pa.DOF_N,nb=nbarr)
|
|
45
|
+
|
|
46
|
+
if(istate==0):
|
|
47
|
+
y0.nodes.append(su)
|
|
48
|
+
elif(istate==1):
|
|
49
|
+
y0.nodes.append(e1)
|
|
50
|
+
elif(istate==2):
|
|
51
|
+
y0.nodes.append(e2)
|
|
52
|
+
elif(istate==3):
|
|
53
|
+
y0.nodes.append(sd)
|
|
54
|
+
|
|
55
|
+
gs = np.zeros((pa.MAX_TT_RANK,pa.occ,pa.MAX_TT_RANK),dtype=np.complex128)
|
|
56
|
+
gs[0,0,0] = 1.
|
|
57
|
+
for k in range(2 * pa.DOF_N-1): # double space formation
|
|
58
|
+
y0.nodes.append(gs)
|
|
59
|
+
gs = np.zeros((pa.MAX_TT_RANK,pa.occ,1),dtype=np.complex128)
|
|
60
|
+
gs[0,0,0] = 1.
|
|
61
|
+
y0.nodes.append(gs)
|
|
62
|
+
|
|
63
|
+
return y0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def construct_Hamil(eps: float = 1E-14) -> MPS:
|
|
67
|
+
"""
|
|
68
|
+
Construct the effective Hamiltonian -iH for TT-TFD calculation in the Spin-Boson model with an Ohmic spectral density.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
eps (float, optional): The truncation precision for the effective Hamiltonian. Default is 1E-14.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
MPS: The MPO object representing the effective Hamiltonian.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
om = pa.OMEGA_C / pa.DOF_N * (1 - np.exp(-pa.OMEGA_MAX/pa.OMEGA_C))
|
|
78
|
+
|
|
79
|
+
# initialize arrays for parameters
|
|
80
|
+
freq = np.zeros((pa.DOF_N)) # frequency
|
|
81
|
+
ck = np.zeros((pa.DOF_N)) # linear electron-phonon coupling constant
|
|
82
|
+
gk = np.zeros((pa.DOF_N)) # ck in occupation number representation
|
|
83
|
+
thetak = np.zeros((pa.DOF_N)) # temperature-dependent mixing parameter in TFD
|
|
84
|
+
sinhthetak = np.zeros((pa.DOF_N)) # sinh(theta)
|
|
85
|
+
coshthetak = np.zeros((pa.DOF_N)) # cosh(theta)
|
|
86
|
+
for i in range(pa.DOF_N):
|
|
87
|
+
freq[i] = -pa.OMEGA_C * np.log(1-(i+1) * om/(pa.OMEGA_C)) # Ohmic frequency
|
|
88
|
+
ck[i] = np.sqrt(pa.XI * om) * freq[i] #Ohmic coupling constant
|
|
89
|
+
gk[i] = -ck[i] / np.sqrt(2 * freq[i]) #Transfer ck to occ. num. representation
|
|
90
|
+
|
|
91
|
+
thetak[i] = np.arctanh(np.exp(-pa.BETA * freq[i]/2)) #theta, defined for harmonic models
|
|
92
|
+
sinhthetak[i] = np.sinh(thetak[i]) #sinh(theta)
|
|
93
|
+
coshthetak[i] = np.cosh(thetak[i]) #cosh(theta)
|
|
94
|
+
|
|
95
|
+
#MPO of identity for all nuclear DOFs
|
|
96
|
+
ident_nuclear = tt_eye(pa.DOF_N*2, pa.occ)
|
|
97
|
+
|
|
98
|
+
# constructing Pauli operators
|
|
99
|
+
px = np.array([[0.0,1],[1,0]],dtype=np.complex128)
|
|
100
|
+
pz = np.array([[1.0,0],[0,-1]],dtype=np.complex128)
|
|
101
|
+
# Build electronic site energy matrix
|
|
102
|
+
He = pa.EPSILON * pz + pa.GAMMA_DA * px
|
|
103
|
+
# TT-ize that energy matrix
|
|
104
|
+
tt_He = tt_matrix(He)
|
|
105
|
+
tt_He = tt_kron(tt_He, ident_nuclear)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Build number operator, corresponds to harmonic oscillator Hamiltonian
|
|
110
|
+
numoc = np.diag(np.arange(0, pa.occ, 1,dtype=np.complex128))
|
|
111
|
+
# Initiate the TT-ized number operator as a zero TT array with shape of occ^N
|
|
112
|
+
tt_numoc = tt_eye(pa.DOF_N,pa.occ)
|
|
113
|
+
for i in range(pa.DOF_N): tt_numoc.nodes[i] *= 0.0
|
|
114
|
+
|
|
115
|
+
# Construct number operator as TT
|
|
116
|
+
for k in range(pa.DOF_N):
|
|
117
|
+
tmp0 = tt_matrix(numoc)
|
|
118
|
+
tmp0.nodes[0] *= freq[k]
|
|
119
|
+
if k == 0:
|
|
120
|
+
tmp = tt_kron(tmp0, tt_eye(pa.DOF_N-1, pa.occ))
|
|
121
|
+
elif 0 < k < pa.DOF_N-1:
|
|
122
|
+
tmp = tt_kron(tt_eye(k-1,pa.occ), tmp0)
|
|
123
|
+
tmp = tt_kron(tmp,tt_eye(pa.DOF_N - k,pa.occ))
|
|
124
|
+
else:
|
|
125
|
+
tmp = tt_kron(tt_eye(k, pa.occ),tmp0)
|
|
126
|
+
tt_numoc = add_tensor(tt_numoc, tmp,small=eps)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# Ensure correct dimensionality
|
|
130
|
+
tt_Ie = tt_matrix(np.eye(2,dtype=np.complex128))
|
|
131
|
+
tt_systemnumoc = tt_kron(tt_Ie, tt_numoc)
|
|
132
|
+
tt_systemnumoc = tt_kron(tt_systemnumoc, tt_eye(pa.DOF_N,pa.occ))
|
|
133
|
+
|
|
134
|
+
# create a duplicate of number operator for the ficticious system
|
|
135
|
+
tt_tildenumoc = tt_kron(tt_Ie, tt_eye(pa.DOF_N,pa.occ))
|
|
136
|
+
tt_tildenumoc = tt_kron(tt_tildenumoc, tt_numoc)
|
|
137
|
+
|
|
138
|
+
thetak = np.zeros((pa.DOF_N)) #temperature-dependent mixing parameter in TFD
|
|
139
|
+
sinhthetak = np.zeros((pa.DOF_N)) #sinh(theta)
|
|
140
|
+
coshthetak = np.zeros((pa.DOF_N)) #cosh(theta)
|
|
141
|
+
for i in range(pa.DOF_N):
|
|
142
|
+
thetak[i] = np.arctanh(np.exp(-pa.BETA * freq[i]/2)) #theta, defined for harmonic models
|
|
143
|
+
sinhthetak[i] = np.sinh(thetak[i]) #sinh(theta)
|
|
144
|
+
coshthetak[i] = np.cosh(thetak[i]) #cosh(theta)
|
|
145
|
+
|
|
146
|
+
#Build displacement operator, corresponds to x operator in real space
|
|
147
|
+
eneroc = np.zeros((pa.occ, pa.occ),dtype=np.complex128)
|
|
148
|
+
for i in range(pa.occ - 1):
|
|
149
|
+
eneroc[i,i+1] = np.sqrt(i+1)
|
|
150
|
+
eneroc[i+1,i] = eneroc[i,i+1]
|
|
151
|
+
|
|
152
|
+
# initialize displacement operator
|
|
153
|
+
tt_energy = tt_eye(pa.DOF_N,pa.occ)
|
|
154
|
+
for i in range(pa.DOF_N): tt_energy.nodes[i] *= 0.0
|
|
155
|
+
|
|
156
|
+
for k in range(pa.DOF_N):
|
|
157
|
+
tmp0 = tt_matrix(eneroc)
|
|
158
|
+
tmp0.nodes[0] *= gk[k] * coshthetak[k]
|
|
159
|
+
if k == 0:
|
|
160
|
+
# coshtheta takes account for energy flow from real to ficticious system
|
|
161
|
+
# thus takes account for temperature effect
|
|
162
|
+
tmp = tt_kron(tmp0, tt_eye(pa.DOF_N-1, pa.occ))
|
|
163
|
+
elif 0 < k < pa.DOF_N - 1:
|
|
164
|
+
tmp = tt_kron(tt_eye(k-1,pa.occ), tmp0)
|
|
165
|
+
tmp = tt_kron(tmp,tt_eye(pa.DOF_N - k,pa.occ))
|
|
166
|
+
else:
|
|
167
|
+
tmp = tt_kron(tt_eye(k, pa.occ), tmp0)
|
|
168
|
+
|
|
169
|
+
tt_energy = add_tensor(tt_energy, tmp, small=eps)
|
|
170
|
+
|
|
171
|
+
tt_systemenergy = tt_kron(tt_matrix(pz), tt_energy)
|
|
172
|
+
tt_systemenergy = tt_kron(tt_systemenergy, tt_eye(pa.DOF_N, pa.occ))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# initialize displacement operator
|
|
176
|
+
tt_tilenergy = tt_eye(pa.DOF_N,pa.occ)
|
|
177
|
+
for i in range(pa.DOF_N): tt_tilenergy.nodes[i] *= 0.0
|
|
178
|
+
|
|
179
|
+
for k in range(pa.DOF_N):
|
|
180
|
+
tmp0 = tt_matrix(eneroc)
|
|
181
|
+
tmp0.nodes[0] *= gk[k] * sinhthetak[k]
|
|
182
|
+
if k == 0:
|
|
183
|
+
tmp = tt_kron(tmp0 , tt_eye(pa.DOF_N-1, pa.occ))
|
|
184
|
+
elif 0 < k < pa.DOF_N - 1:
|
|
185
|
+
tmp = tt_kron(tt_eye(k-1,pa.occ), tmp0)
|
|
186
|
+
tmp = tt_kron(tmp, tt_eye(pa.DOF_N - k,pa.occ))
|
|
187
|
+
else:
|
|
188
|
+
tmp = tt_kron(tt_eye(k, pa.occ), tmp0)
|
|
189
|
+
tt_tilenergy = add_tensor(tt_tilenergy, tmp,small=eps)
|
|
190
|
+
|
|
191
|
+
tt_tildeenergy = tt_kron(tt_matrix(pz), tt_eye(pa.DOF_N, pa.occ))
|
|
192
|
+
tt_tildeenergy = tt_kron(tt_tildeenergy, tt_tilenergy)
|
|
193
|
+
|
|
194
|
+
#The total propogation Hamiltonian
|
|
195
|
+
# Note that ficticious Harmonic oscillators carry negative sign
|
|
196
|
+
H = add_tensor(tt_He, tt_systemnumoc, small=eps)
|
|
197
|
+
H = add_tensor(H, tt_tildenumoc, coeff = -1.0, small=eps)
|
|
198
|
+
H = add_tensor(H, tt_systemenergy, coeff = 1.0, small=eps)
|
|
199
|
+
H = add_tensor(H, tt_tildeenergy, coeff = 1.0, small=eps)
|
|
200
|
+
|
|
201
|
+
# Construct propagation operator, d/dt psi(t0)=-1j H psi(t0)
|
|
202
|
+
H.nodes[0] *= -1j
|
|
203
|
+
|
|
204
|
+
# convert to MPO
|
|
205
|
+
A = MPS2MPO(H).truncation(small=eps)
|
|
206
|
+
return A
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def tt_eye(length: int, mode_dim: int) -> MPS:
|
|
210
|
+
"""
|
|
211
|
+
Generate an identity MPO (in the form of MPS) with rank-1.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
length (int): The number of MPS nodes.
|
|
215
|
+
mode_dim (int): The size of the physical index.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
MPS: The resulting MPS representation of the identity matrix.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
nb_arr = np.ones(length,dtype=int)*mode_dim
|
|
222
|
+
y0 = MPS(length,nb_arr**2)
|
|
223
|
+
|
|
224
|
+
for i in range(length):
|
|
225
|
+
identy = np.eye(nb_arr[i],dtype=np.complex128)
|
|
226
|
+
y0.nodes.append(identy.reshape((1,nb_arr[i]**2,1),order='F'))
|
|
227
|
+
|
|
228
|
+
return y0
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def tt_matrix(array: np.ndarray) -> MPS:
|
|
232
|
+
"""
|
|
233
|
+
Generate a tensor train (in the form of MPS) from the input array.
|
|
234
|
+
Now the input array should be a square matrix.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
array (np.ndarray): A 2D square matrix to be converted into a tensor train.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
MPS: The resulting tensor train (MPS) representation of the input array.
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
SystemExit: If the input array is not square.
|
|
244
|
+
"""
|
|
245
|
+
M, N = array.shape
|
|
246
|
+
if(M!=N):
|
|
247
|
+
print("array shape not matched")
|
|
248
|
+
sys.exit()
|
|
249
|
+
|
|
250
|
+
y0 = MPS(1,np.array([M**2],dtype=int))
|
|
251
|
+
y0.nodes.append(array.reshape((1,M**2,1),order='F'))
|
|
252
|
+
|
|
253
|
+
return y0
|
|
254
|
+
|
|
255
|
+
def tt_kron(mps1: MPS, mps2: MPS) -> MPS:
|
|
256
|
+
"""
|
|
257
|
+
Compute the Kronecker product of two MPS (Matrix Product States).
|
|
258
|
+
|
|
259
|
+
This function combines two MPS objects into a new MPS which represents the Kronecker
|
|
260
|
+
product of the two input MPS objects.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
mps1 (MPS): The first Matrix Product State.
|
|
264
|
+
mps2 (MPS): The second Matrix Product State.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
MPS: A new MPS object which is the Kronecker product of mps1 and mps2.
|
|
268
|
+
"""
|
|
269
|
+
new_length = mps1.length + mps2.length
|
|
270
|
+
|
|
271
|
+
new_nb = np.empty(new_length,dtype=int)
|
|
272
|
+
|
|
273
|
+
new_mps = MPS(new_length,new_nb)
|
|
274
|
+
|
|
275
|
+
for i in range(mps1.length):
|
|
276
|
+
new_mps.nb[i] = mps1.nb[i]
|
|
277
|
+
new_mps.nodes.append(mps1.nodes[i])
|
|
278
|
+
for i in range(mps2.length):
|
|
279
|
+
new_mps.nb[i+mps1.length] = mps2.nb[i]
|
|
280
|
+
new_mps.nodes.append(mps2.nodes[i])
|
|
281
|
+
|
|
282
|
+
return new_mps
|
|
283
|
+
|
|
284
|
+
def tt_ones(length: int, mode_dim: int) -> MPS:
|
|
285
|
+
"""
|
|
286
|
+
Generate an MPS with rank-1, where all the elements are set to one.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
length (int): The number of MPS nodes (the length of the tensor chain).
|
|
290
|
+
mode_dim (int): The size of the physical index for each node in the MPS.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
MPS: The MPS object with the specified number of nodes, each having rank-1 and elements initialized to one.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
nb_arr = np.ones(length,dtype=int)*mode_dim
|
|
297
|
+
y0 = MPS(length,nb=nb_arr)
|
|
298
|
+
|
|
299
|
+
for i in range(length):
|
|
300
|
+
tmp = np.ones((1,nb_arr[i],1),dtype=np.complex128)
|
|
301
|
+
y0.nodes.append(tmp)
|
|
302
|
+
|
|
303
|
+
return y0
|
|
304
|
+
|
|
305
|
+
def cal_property(mps0: MPS) -> np.ndarray:
|
|
306
|
+
"""
|
|
307
|
+
Calculate the population and coherence for the spin-boson model.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
mps0 (MPS): The wavefunction MPS representing the state.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
np.ndarray: An array containing the population and coherence values.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
# Initialize the sigma array to hold the results
|
|
317
|
+
sigma_arr = np.zeros(pa.DOF_E_SQ,dtype=np.complex128)
|
|
318
|
+
|
|
319
|
+
# Define the spin-up and spin-down states
|
|
320
|
+
su = pa.spin_up
|
|
321
|
+
sd = pa.spin_down
|
|
322
|
+
|
|
323
|
+
# Create copies of the original MPS for the calculations
|
|
324
|
+
mps_up = mps0.copy()
|
|
325
|
+
mps_down = mps0.copy()
|
|
326
|
+
|
|
327
|
+
#ul = np.array([[1,0],[0,0]])
|
|
328
|
+
ur = np.array([[0,1],[0,0]])
|
|
329
|
+
mps_ur = mps0.copy()
|
|
330
|
+
|
|
331
|
+
# Calculate the overlaps for population and coherence
|
|
332
|
+
mps_up.nodes[0] = np.multiply(mps_up.nodes[0], su.reshape((1,-1,1)))
|
|
333
|
+
mps_down.nodes[0] = np.multiply(mps_down.nodes[0], sd.reshape(1,-1,1))
|
|
334
|
+
mps_ur.nodes[0] = (np.tensordot(ur, mps_ur.nodes[0],axes=((1),(1)))).transpose(1,0,2)
|
|
335
|
+
sigma_arr[0] = calc_overlap(mps_up, mps_up)
|
|
336
|
+
sigma_arr[3] = calc_overlap(mps_down, mps_down)
|
|
337
|
+
sigma_arr[2] = calc_overlap(mps_up,mps_ur)
|
|
338
|
+
sigma_arr[1] = calc_overlap(mps_ur,mps_up)
|
|
339
|
+
return sigma_arr
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def multiply_mps(mps1: MPS, mps2: MPS) -> MPS:
|
|
343
|
+
"""
|
|
344
|
+
Perform element-wise multiplication of two MPS.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
mps1 (MPS): The first MPS object.
|
|
348
|
+
mps2 (MPS): The second MPS object.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
MPS: A new MPS object resulting from the element-wise multiplication of the two MPS.
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
nlen1 = len(mps1.nodes)
|
|
355
|
+
nlen2 = len(mps2.nodes)
|
|
356
|
+
if(nlen1!=nlen2):
|
|
357
|
+
raise ValueError('MPS lengths do not match! Unable to perform element-wise multiplication.')
|
|
358
|
+
nb1 = mps1.nb
|
|
359
|
+
nb2 = mps2.nb
|
|
360
|
+
|
|
361
|
+
new_mps = MPS(nlen1,nb1)
|
|
362
|
+
|
|
363
|
+
for i in range(nlen1):
|
|
364
|
+
if(nb1[i]!=nb2[i]):
|
|
365
|
+
raise ValueError(f'Physical indices do not match for node {i}!')
|
|
366
|
+
ra1,ra2,ra3 = mps1.nodes[i].shape
|
|
367
|
+
rb1,rb2,rb3 = mps2.nodes[i].shape
|
|
368
|
+
|
|
369
|
+
array_tmp = np.einsum('ijk,ljm->iljkm',mps1.nodes[i],mps2.nodes[i]).reshape((ra1*rb1,nb1[i],ra3*rb3))
|
|
370
|
+
|
|
371
|
+
new_mps.nodes.append(array_tmp)
|
|
372
|
+
return new_mps.truncation(small=pa.eps)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def tt_tfd(
|
|
377
|
+
initial_state: int,
|
|
378
|
+
update_type: str = 'rk4',
|
|
379
|
+
rk4slices: int = 1,
|
|
380
|
+
mmax: int = 4,
|
|
381
|
+
RDO_arr_bench: np.ndarray = None,
|
|
382
|
+
show_steptime: bool = False
|
|
383
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
384
|
+
"""
|
|
385
|
+
Perform the TT-TFD calculation.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
initial_state (int): The initial state of the system. This determines the starting state for the calculation.
|
|
389
|
+
update_type (str, optional): The method used for updating each core of the MPS. Can be either 'rk4' (Runge-Kutta 4th order) or 'krylov'. Default is 'rk4'.
|
|
390
|
+
rk4slices (int, optional): The number of time slices for 'rk4' method when performing the update. Default is 1.
|
|
391
|
+
mmax (int, optional): The size of the Krylov subspace when using the 'krylov' method. Default is 4.
|
|
392
|
+
RDO_arr_bench (np.ndarray, optional): If provided, the function will compare the calculated reduced density matrix (RDO) at each step with this benchmark array. Default is None.
|
|
393
|
+
show_steptime (bool, optional): If True, the function will print the time taken for each propagation step. Default is False.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
tuple[np.ndarray, np.ndarray]:
|
|
397
|
+
A tuple containing:
|
|
398
|
+
- A time array (`t`) representing the simulation times.
|
|
399
|
+
- The reduced density matrix (`RDO_arr`) over time.
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
y0 = initial(initial_state)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
A = construct_Hamil(eps=pa.eps)
|
|
406
|
+
|
|
407
|
+
RDO_arr = np.zeros((pa.TIME_STEPS, pa.DOF_E_SQ), dtype=np.complex128)
|
|
408
|
+
t = np.arange(0, pa.TIME_STEPS * pa.DT, pa.DT)
|
|
409
|
+
|
|
410
|
+
# Propagation loop
|
|
411
|
+
START_TIME = time.time()
|
|
412
|
+
print('Start doing propagation')
|
|
413
|
+
|
|
414
|
+
for ii in range(pa.TIME_STEPS):
|
|
415
|
+
|
|
416
|
+
print(ii,t[ii])
|
|
417
|
+
STEP_TIME = time.time()
|
|
418
|
+
|
|
419
|
+
#Doing TDVP
|
|
420
|
+
y0 = tdvp1site(y0, A, pa.DT, update_type=update_type,mmax=mmax,rk4slices=rk4slices)
|
|
421
|
+
#Calculating the Reduced density matrix
|
|
422
|
+
RDO_arr[ii] = cal_property(y0)
|
|
423
|
+
|
|
424
|
+
STEP_TIME2 = time.time()
|
|
425
|
+
if(RDO_arr_bench is not None):
|
|
426
|
+
compare_diff(RDO_arr[ii], RDO_arr_bench[ii])
|
|
427
|
+
|
|
428
|
+
if(show_steptime):
|
|
429
|
+
print('timefor tdvp:',STEP_TIME2-STEP_TIME)
|
|
430
|
+
|
|
431
|
+
print("\tPropagation time:", time.time() - START_TIME)
|
|
432
|
+
|
|
433
|
+
return t,RDO_arr
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def compare_diff(vec1,vec2):
|
|
438
|
+
"""
|
|
439
|
+
testing function for comparing the two vectors
|
|
440
|
+
"""
|
|
441
|
+
|
|
442
|
+
sr = 0.0; si = 0.0
|
|
443
|
+
sr = np.max(np.abs(vec1.real-vec2.real))
|
|
444
|
+
si = np.max(np.abs(vec1.imag-vec2.imag))
|
|
445
|
+
print('error, real', sr, 'imag', si)
|
|
446
|
+
#print('vec1',vec1)
|
|
447
|
+
#print('vec2',vec2)
|
|
448
|
+
|
qflux/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
qflux v.0.1.0
|
|
4
|
+
|
|
5
|
+
__version__ = 0.1.0
|
|
6
|
+
|
|
7
|
+
Description:
|
|
8
|
+
|
|
9
|
+
classical_methods.py --> DynamicsCS : Closed-System dynamics
|
|
10
|
+
qubit_methods.py --> QubitDynamicsCS : Qubit-based dynamics
|
|
11
|
+
spin_dynamics_oo.py --> SpinDynamicsS, SpinDynamicsH : Statevector and hadamard-test implementations (respectively) for spin-chain systems
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .classical_methods import DynamicsCS
|
|
16
|
+
from .qubit_methods import QubitDynamicsCS
|
|
17
|
+
from .spin_dynamics_oo import SpinDynamicsS, SpinDynamicsH
|