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/__init__.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Class for GQME calculations
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import scipy.linalg as LA
|
|
7
|
+
from . import params as pa
|
|
8
|
+
from . import tt_tfd as tfd
|
|
9
|
+
from typing import Tuple, Optional
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DynamicsGQME:
|
|
16
|
+
"""
|
|
17
|
+
Class for Generalized Quantum Master Equation (GQME) calculation.
|
|
18
|
+
|
|
19
|
+
This class provides methods for calculating the memory kernel,
|
|
20
|
+
performing TT-TFD calculations, and solving the GQME for open quantum systems.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
Nsys (int): System Hilbert space dimension.
|
|
24
|
+
Hsys (np.ndarray): System Hamiltonian of shape (N, N).
|
|
25
|
+
rho0 (np.ndarray): Initial density matrix of shape (N, N).
|
|
26
|
+
vec_rho0 (np.ndarray): Vectorized initial density matrix of shape (N^2,).
|
|
27
|
+
Liouv (Optional[np.ndarray]): Liouvillian superoperator.
|
|
28
|
+
DT (Optional[float]): Time step for evolution.
|
|
29
|
+
TIME_STEPS (Optional[int]): Number of time steps.
|
|
30
|
+
time_array (Optional[np.ndarray]): Array of time points.
|
|
31
|
+
Gt (Optional[np.ndarray]): Time-evolution propagator.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, Nsys: int, Hsys: np.ndarray, rho0: np.ndarray) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Initialize the DynamicsGQME instance.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
Nsys (int): System Hilbert space dimension.
|
|
40
|
+
Hsys (np.ndarray): System Hamiltonian of shape (N, N).
|
|
41
|
+
rho0 (np.ndarray): Initial density matrix of shape (N, N).
|
|
42
|
+
"""
|
|
43
|
+
self.Nsys = Nsys
|
|
44
|
+
self.Hsys = Hsys
|
|
45
|
+
self.rho0 = rho0
|
|
46
|
+
self.vec_rho0 = rho0.reshape(Nsys**2)
|
|
47
|
+
|
|
48
|
+
#Liouvillian
|
|
49
|
+
self.Liouv = None
|
|
50
|
+
self.get_Liouvillian()
|
|
51
|
+
|
|
52
|
+
#time
|
|
53
|
+
self.DT = None
|
|
54
|
+
self.TIME_STEPS = None
|
|
55
|
+
self.time_array = None
|
|
56
|
+
|
|
57
|
+
#propagator
|
|
58
|
+
self.Gt = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_Liouvillian(self) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Construct the Liouvillian superoperator using the system Hamiltonian.
|
|
64
|
+
|
|
65
|
+
The Liouvillian is defined as:
|
|
66
|
+
L = H \otimes I - I \otimes H^T
|
|
67
|
+
"""
|
|
68
|
+
Isys = np.eye(self.Nsys)
|
|
69
|
+
self.Liouv = np.kron(self.Hsys,Isys) - np.kron(Isys,self.Hsys.T)
|
|
70
|
+
|
|
71
|
+
def setup_propagator(self, Gt: np.ndarray) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Set the time-evolution propagator.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
Gt (np.ndarray): Time-dependent propagator.
|
|
77
|
+
"""
|
|
78
|
+
self.Gt = Gt
|
|
79
|
+
|
|
80
|
+
def setup_timestep(self, DT: float, TIME_STEPS: int) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Set up the time discretization parameters.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
DT (float): Time step size.
|
|
86
|
+
TIME_STEPS (int): Number of time steps.
|
|
87
|
+
"""
|
|
88
|
+
self.DT = DT
|
|
89
|
+
self.TIME_STEPS = TIME_STEPS
|
|
90
|
+
self.time_array = np.linspace(0,(TIME_STEPS-1)*DT,TIME_STEPS)
|
|
91
|
+
|
|
92
|
+
def tt_tfd(
|
|
93
|
+
self,
|
|
94
|
+
initial_state: int = 0,
|
|
95
|
+
update_type: str = 'rk4',
|
|
96
|
+
rk4slices: int = 1,
|
|
97
|
+
mmax: int = 4,
|
|
98
|
+
RDO_arr_bench: Optional[np.ndarray] = None,
|
|
99
|
+
show_steptime: bool = False
|
|
100
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
Perform Tensor-Train Thermofield Dynamics (TT-TFD) calculation.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
initial_state (int, optional): Index of the initial state. Defaults to 0.
|
|
107
|
+
update_type (str, optional): Method for time evolution. Either 'rk4' or 'krylov'.
|
|
108
|
+
rk4slices (int, optional): Number of RK4 substeps if update_type is 'rk4'. Defaults to 1.
|
|
109
|
+
mmax (int, optional): Dimension of Krylov subspace if update_type is 'krylov'. Defaults to 4.
|
|
110
|
+
RDO_arr_bench (Optional[np.ndarray], optional): Benchmark RDO array to compute error at each step. Defaults to None.
|
|
111
|
+
show_steptime (bool, optional): Whether to print timing for each step. Defaults to False.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Tuple[np.ndarray, np.ndarray]: Time array and RDO (reduced density operator) array.
|
|
115
|
+
"""
|
|
116
|
+
time_arr, RDO_arr = tfd.tt_tfd(
|
|
117
|
+
initial_state,
|
|
118
|
+
update_type,
|
|
119
|
+
rk4slices=rk4slices,
|
|
120
|
+
mmax=mmax,
|
|
121
|
+
RDO_arr_bench=RDO_arr_bench,
|
|
122
|
+
show_steptime=show_steptime
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return time_arr, RDO_arr
|
|
126
|
+
|
|
127
|
+
def cal_propagator_tttfd(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
128
|
+
"""
|
|
129
|
+
Calculate the numerical exact propagator for Spin-Boson model using the TT-TFD method.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Tuple[np.ndarray, np.ndarray]: Time array and 3D propagator array.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
U = np.zeros((pa.TIME_STEPS, pa.DOF_E_SQ, pa.DOF_E_SQ), dtype=np.complex128)
|
|
136
|
+
|
|
137
|
+
# tt-tfd with initial state 0,1,2,3
|
|
138
|
+
# initial state |0> means donor state |D>, |3> means acceptor state |A>
|
|
139
|
+
# |1> is (|D> + |A>)/sqrt(2), |2> is (|D> + i|A>)/sqrt(2)
|
|
140
|
+
for i in range(4):
|
|
141
|
+
print(f"======== calculate the propagator, starting from state {i} ========")
|
|
142
|
+
t, U[:, :, i] = tfd.tt_tfd(i)
|
|
143
|
+
|
|
144
|
+
U_final = U.copy()
|
|
145
|
+
|
|
146
|
+
# the coherence elements that start at initial state |D><A| and |A><D|
|
|
147
|
+
# is the linear combination of above U results
|
|
148
|
+
# |D><A| = |1><1| + i * |2><2| - 1/2 * (1 + i) * (|0><0| + |3><3|)
|
|
149
|
+
U_final[:,:,1] = U[:,:,1] + 1.j * U[:,:,2] - 0.5 * (1. + 1.j) * (U[:,:,0] + U[:,:,3])
|
|
150
|
+
|
|
151
|
+
# |A><D| = |1><1| - i * |2><2| - 1/2 * (1 - i) * (|0><0| + |3><3|)
|
|
152
|
+
U_final[:,:,2] = U[:,:,1] - 1.j * U[:,:,2] - 0.5 * (1. - 1.j) * (U[:,:,0] + U[:,:,3])
|
|
153
|
+
|
|
154
|
+
self.setup_propagator(U_final)
|
|
155
|
+
print('========calculate the propagator done========')
|
|
156
|
+
|
|
157
|
+
return t,U_final
|
|
158
|
+
|
|
159
|
+
def prop_puresystem(self) -> np.ndarray:
|
|
160
|
+
"""
|
|
161
|
+
Propagate the pure system under the unitary evolution.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
np.ndarray: Vectorized density matrix at each time step (shape (TIME_STEPS, N^2)).
|
|
165
|
+
"""
|
|
166
|
+
Nstep = len(self.time_array)
|
|
167
|
+
vec_rho = np.zeros((Nstep, self.Nsys**2), dtype=np.complex128)
|
|
168
|
+
vec_rho[0] = self.vec_rho0.copy()
|
|
169
|
+
for i in range(1,Nstep):
|
|
170
|
+
vec_rho[i] = LA.expm(-1j*self.Liouv*(self.time_array[i]-self.time_array[i-1]))@vec_rho[i-1]
|
|
171
|
+
return vec_rho
|
|
172
|
+
|
|
173
|
+
def cal_F(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
174
|
+
"""
|
|
175
|
+
Compute the first and second order time derivatives of the propagator.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Tuple[np.ndarray, np.ndarray]: F and Fdot matrices (each of shape (TIME_STEPS, N^2, N^2)).
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
F = np.zeros((self.TIME_STEPS, self.Nsys**2, self.Nsys**2), dtype=np.complex128)
|
|
182
|
+
Fdot = np.zeros((self.TIME_STEPS, self.Nsys**2, self.Nsys**2), dtype=np.complex128)
|
|
183
|
+
|
|
184
|
+
if(self.Gt is None):
|
|
185
|
+
print('error in cal_F: please setup the pre-calculated propagator super-operator')
|
|
186
|
+
sys.exit()
|
|
187
|
+
|
|
188
|
+
for j in range(self.Nsys**2):
|
|
189
|
+
for k in range(self.Nsys**2):
|
|
190
|
+
# extracts real and imag parts of U element
|
|
191
|
+
Ureal = self.Gt[:,j,k].copy().real
|
|
192
|
+
Uimag = self.Gt[:,j,k].copy().imag
|
|
193
|
+
|
|
194
|
+
# F = i * d/dt U so Re[F] = -1 * d/dt Im[U] and Im[F] = d/dt Re[U]
|
|
195
|
+
Freal = -1. * np.gradient(Uimag.flatten(), self.DT, edge_order = 2)
|
|
196
|
+
Fimag = np.gradient(Ureal.flatten(), self.DT, edge_order = 2)
|
|
197
|
+
|
|
198
|
+
# Fdot = d/dt F so Re[Fdot] = d/dt Re[F] and Im[Fdot] = d/dt Im[F]
|
|
199
|
+
Fdotreal = np.gradient(Freal, self.DT)
|
|
200
|
+
Fdotimag = np.gradient(Fimag, self.DT)
|
|
201
|
+
|
|
202
|
+
F[:,j,k] = Freal[:] + 1.j * Fimag[:]
|
|
203
|
+
Fdot[:,j,k] = Fdotreal[:] + 1.j * Fdotimag[:]
|
|
204
|
+
|
|
205
|
+
return F,Fdot
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _CalculateIntegral(
|
|
209
|
+
self,
|
|
210
|
+
F: np.ndarray,
|
|
211
|
+
linearTerm: np.ndarray,
|
|
212
|
+
prevKernel: np.ndarray,
|
|
213
|
+
kernel: np.ndarray
|
|
214
|
+
) -> np.ndarray:
|
|
215
|
+
"""
|
|
216
|
+
Compute the Volterra integral using the trapezoidal rule.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
F (np.ndarray): Derivative of the propagator.
|
|
220
|
+
linearTerm (np.ndarray): Linear term from memory kernel equation.
|
|
221
|
+
prevKernel (np.ndarray): Kernel from previous iteration.
|
|
222
|
+
kernel (np.ndarray): Kernel to be updated.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
np.ndarray: Updated kernel.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
# time step loop starts at 1 because K is equal to linear part at t = 0
|
|
229
|
+
for n in range(1, self.TIME_STEPS):
|
|
230
|
+
kernel[n,:,:] = 0.
|
|
231
|
+
|
|
232
|
+
# f(a) and f(b) terms
|
|
233
|
+
kernel[n,:,:] += 0.5 * self.DT * F[n,:,:] @ kernel[0,:,:]
|
|
234
|
+
kernel[n,:,:] += 0.5 * self.DT * F[0,:,:] @ prevKernel[n,:,:]
|
|
235
|
+
|
|
236
|
+
# sum of f(a + kh) term
|
|
237
|
+
for c in range(1, n):
|
|
238
|
+
# since a new (supposed-to-be-better) guess for the
|
|
239
|
+
# kernel has been calculated for previous time steps,
|
|
240
|
+
# can use it rather than prevKernel
|
|
241
|
+
kernel[n,:,:] += self.DT * F[n - c,:,:] @ kernel[c,:,:]
|
|
242
|
+
|
|
243
|
+
# multiplies by i and adds the linear part
|
|
244
|
+
kernel[n,:,:] = 1.j * kernel[n,:,:] + linearTerm[n,:,:]
|
|
245
|
+
|
|
246
|
+
return kernel
|
|
247
|
+
|
|
248
|
+
def get_memory_kernel(self) -> np.ndarray:
|
|
249
|
+
"""
|
|
250
|
+
Compute the memory kernel using the Volterra scheme.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
np.ndarray: Memory kernel array (shape (TIME_STEPS, N^2, N^2)).
|
|
254
|
+
"""
|
|
255
|
+
F,Fdot = self.cal_F()
|
|
256
|
+
|
|
257
|
+
linearTerm = 1.j * Fdot.copy() # first term of the linear part
|
|
258
|
+
for l in range(self.TIME_STEPS):
|
|
259
|
+
# subtracts second term of linear part
|
|
260
|
+
linearTerm[l,:,:] -= 1./pa.HBAR * F[l,:,:] @ self.Liouv
|
|
261
|
+
|
|
262
|
+
START_TIME = time.time() # starts timing
|
|
263
|
+
# sets initial guess to the linear part
|
|
264
|
+
prevKernel = linearTerm.copy()
|
|
265
|
+
kernel = linearTerm.copy()
|
|
266
|
+
|
|
267
|
+
# loop for iterations
|
|
268
|
+
for numIter in range(1, pa.MAX_ITERS + 1):
|
|
269
|
+
|
|
270
|
+
iterStartTime = time.time() # starts timing of iteration
|
|
271
|
+
print("Iteration:", numIter)
|
|
272
|
+
|
|
273
|
+
# calculates kernel using prevKernel and trapezoidal rule
|
|
274
|
+
kernel = self._CalculateIntegral(F, linearTerm, prevKernel, kernel)
|
|
275
|
+
|
|
276
|
+
numConv = 0 # parameter used to check convergence of entire kernel
|
|
277
|
+
for i in range(self.Nsys**2):
|
|
278
|
+
for j in range(self.Nsys**2):
|
|
279
|
+
for n in range(self.TIME_STEPS):
|
|
280
|
+
# if matrix element and time step of kernel is converged, adds 1
|
|
281
|
+
if abs(kernel[n][i][j] - prevKernel[n][i][j]) <= pa.CONVERGENCE_PARAM:
|
|
282
|
+
numConv += 1
|
|
283
|
+
|
|
284
|
+
# if at max iters, prints which elements and time steps did not
|
|
285
|
+
# converge and prevKernel and kernel values
|
|
286
|
+
elif numIter == pa.MAX_ITERS:
|
|
287
|
+
print("\tK time step and matrix element that didn't converge: %s, %s%s"%(n,i,j))
|
|
288
|
+
|
|
289
|
+
print("\tIteration time:", time.time() - iterStartTime)
|
|
290
|
+
|
|
291
|
+
# enters if all times steps and matrix elements of kernel converged
|
|
292
|
+
if numConv == self.TIME_STEPS * self.Nsys**2 * self.Nsys**2:
|
|
293
|
+
# prints number of iterations and time necessary for convergence
|
|
294
|
+
print("Number of Iterations:", numIter, "\tVolterra time:", time.time() - START_TIME)
|
|
295
|
+
|
|
296
|
+
break # exits the iteration loop
|
|
297
|
+
|
|
298
|
+
# if not converged, stores kernel as prevKernel, zeros the kernel, and then
|
|
299
|
+
# sets kernel at t = 0 to linear part
|
|
300
|
+
prevKernel = kernel.copy()
|
|
301
|
+
kernel = linearTerm.copy()
|
|
302
|
+
|
|
303
|
+
# if max iters reached, prints lack of convergence
|
|
304
|
+
if numIter == pa.MAX_ITERS:
|
|
305
|
+
print("\tERROR: Did not converge for %s iterations"%pa.MAX_ITERS)
|
|
306
|
+
print("\tVolterra time:", print(time.time() - START_TIME))
|
|
307
|
+
|
|
308
|
+
return kernel
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def PropagateRK4(
|
|
312
|
+
self,
|
|
313
|
+
currentTime: float,
|
|
314
|
+
memTime: float,
|
|
315
|
+
kernel: np.ndarray,
|
|
316
|
+
sigma_hold: np.ndarray,
|
|
317
|
+
sigma: np.ndarray
|
|
318
|
+
) -> np.ndarray:
|
|
319
|
+
"""
|
|
320
|
+
Perform one 4th-order Runge-Kutta (RK4) integration step for GQME.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
currentTime (float): Current time.
|
|
324
|
+
memTime (float): Memory time cutoff.
|
|
325
|
+
kernel (np.ndarray): Memory kernel array (shape (TIME_STEPS, N^2, N^2)).
|
|
326
|
+
sigma_hold (np.ndarray): Current vectorized reduced density matrix (N^2,).
|
|
327
|
+
sigma (np.ndarray): Array of vectorized reduced density matrix up to current time (TIME_STEPS, N^2).
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
np.ndarray: Updated vectorized reduced density matrix after one RK4 step (N^2,).
|
|
331
|
+
"""
|
|
332
|
+
f_0 = self._Calculatef(currentTime, memTime,
|
|
333
|
+
kernel, sigma, sigma_hold)
|
|
334
|
+
|
|
335
|
+
k_1 = sigma_hold + self.DT * f_0 / 2.
|
|
336
|
+
f_1 = self._Calculatef(currentTime + self.DT / 2., memTime,
|
|
337
|
+
kernel, sigma, k_1)
|
|
338
|
+
|
|
339
|
+
k_2 = sigma_hold + self.DT * f_1 /2.
|
|
340
|
+
f_2 = self._Calculatef(currentTime + self.DT / 2., memTime,
|
|
341
|
+
kernel, sigma, k_2)
|
|
342
|
+
|
|
343
|
+
k_3 = sigma_hold + self.DT * f_2
|
|
344
|
+
f_3 = self._Calculatef(currentTime + self.DT, memTime,
|
|
345
|
+
kernel, sigma, k_3)
|
|
346
|
+
|
|
347
|
+
sigma_hold += self.DT / 6. * (f_0 + 2. * f_1 + 2. * f_2 + f_3)
|
|
348
|
+
|
|
349
|
+
return sigma_hold
|
|
350
|
+
|
|
351
|
+
def _Calculatef(
|
|
352
|
+
self,
|
|
353
|
+
currentTime: float,
|
|
354
|
+
memTime: float,
|
|
355
|
+
kernel: np.ndarray,
|
|
356
|
+
sigma_array: np.ndarray,
|
|
357
|
+
kVec: np.ndarray
|
|
358
|
+
) -> np.ndarray:
|
|
359
|
+
"""
|
|
360
|
+
Evaluate the time derivative in GQME.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
currentTime (float): Current time.
|
|
364
|
+
memTime (float): Memory time cutoff.
|
|
365
|
+
kernel (np.ndarray): Memory kernel (shape (TIME_STEPS, N^2, N^2)).
|
|
366
|
+
sigma_array (np.ndarray): History of vectorized system density matrix (TIME_STEPS, N^2).
|
|
367
|
+
kVec (np.ndarray): Vector to be evolved (N^2,).
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
np.ndarray: Time derivative vector f(t) (N^2,).
|
|
371
|
+
"""
|
|
372
|
+
memTimeSteps = int(memTime / self.DT)
|
|
373
|
+
currentTimeStep = int(currentTime / self.DT)
|
|
374
|
+
|
|
375
|
+
f_t = np.zeros(kVec.shape, dtype=np.complex128)
|
|
376
|
+
|
|
377
|
+
f_t -= 1.j / pa.HBAR * self.Liouv @ kVec
|
|
378
|
+
|
|
379
|
+
limit = memTimeSteps
|
|
380
|
+
if currentTimeStep < (memTimeSteps - 1):
|
|
381
|
+
limit = currentTimeStep
|
|
382
|
+
for l in range(limit):
|
|
383
|
+
f_t -= self.DT * kernel[l,:,:] @ sigma_array[currentTimeStep - l]
|
|
384
|
+
|
|
385
|
+
return f_t
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def solve_gqme(
|
|
389
|
+
self,
|
|
390
|
+
kernel: np.ndarray,
|
|
391
|
+
MEM_TIME: float,
|
|
392
|
+
dtype: str = "Density"
|
|
393
|
+
) -> np.ndarray:
|
|
394
|
+
"""
|
|
395
|
+
Solve the GQME using RK4 integration.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
kernel (np.ndarray): Memory kernel (shape (TIME_STEPS, N^2, N^2)).
|
|
399
|
+
MEM_TIME (float): Memory cutoff time.
|
|
400
|
+
dtype (str): Type of data to propagate: "Density" or "Propagator".
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
np.ndarray: Propagated state (shape depends on dtype).
|
|
404
|
+
"""
|
|
405
|
+
|
|
406
|
+
if(dtype=="Density"):
|
|
407
|
+
# array for reduced density matrix elements
|
|
408
|
+
sigma = np.zeros((self.TIME_STEPS, self.Nsys**2), dtype=np.complex128)
|
|
409
|
+
# array to hold copy of sigma
|
|
410
|
+
sigma_hold = np.zeros(self.Nsys**2, dtype = np.complex128)
|
|
411
|
+
|
|
412
|
+
# sets the initial state
|
|
413
|
+
sigma[0,:] = self.vec_rho0.copy()
|
|
414
|
+
sigma_hold = self.vec_rho0.copy()
|
|
415
|
+
elif(dtype == "Propagator"):
|
|
416
|
+
# array for reduced density matrix elements
|
|
417
|
+
sigma = np.zeros((self.TIME_STEPS, self.Nsys**2, self.Nsys**2), dtype=np.complex128)
|
|
418
|
+
# array to hold copy of sigma
|
|
419
|
+
sigma_hold = np.zeros(self.Nsys**2, dtype = np.complex128)
|
|
420
|
+
|
|
421
|
+
#time 0 propagator: identity superoperator
|
|
422
|
+
sigma[0] = np.eye(self.Nsys**2)
|
|
423
|
+
#array to hold copy of G propagator
|
|
424
|
+
sigma_hold = np.eye((self.Nsys**2), dtype=np.complex128)
|
|
425
|
+
else:
|
|
426
|
+
sys.exit('GQME input error, dtype should be "Density" or "Propagator"')
|
|
427
|
+
|
|
428
|
+
# loop to propagate sigma
|
|
429
|
+
print(">>> Starting GQME propagation, memory time =", MEM_TIME)
|
|
430
|
+
for l in range(self.TIME_STEPS - 1): # it propagates to the final time step
|
|
431
|
+
if l%100==0: print(l)
|
|
432
|
+
currentTime = l * self.DT
|
|
433
|
+
|
|
434
|
+
sigma_hold = self.PropagateRK4(currentTime, MEM_TIME, kernel, sigma_hold, sigma)
|
|
435
|
+
|
|
436
|
+
sigma[l + 1] = sigma_hold.copy()
|
|
437
|
+
|
|
438
|
+
return sigma
|
qflux/GQME/params.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
## Spin-Boson Model parameters
|
|
4
|
+
GAMMA_DA = 1 # diabatic coupling
|
|
5
|
+
EPSILON = 1
|
|
6
|
+
BETA = 5 # inverse finite temperature beta = 1 / (k_B * T)
|
|
7
|
+
XI = 0.1
|
|
8
|
+
OMEGA_C = 2
|
|
9
|
+
|
|
10
|
+
print("SPIN-BOSON Model parameter")
|
|
11
|
+
print(" epsilon =", EPSILON)
|
|
12
|
+
print(" xi =", XI)
|
|
13
|
+
print(" omega_c =", OMEGA_C)
|
|
14
|
+
|
|
15
|
+
# Spin-up and spin-down states
|
|
16
|
+
spin_up = np.array([1.0, 0.0], dtype=np.float64)
|
|
17
|
+
spin_down = np.array([0.0, 1.0], dtype=np.float64)
|
|
18
|
+
|
|
19
|
+
## General Constants for simulation
|
|
20
|
+
TIME_STEPS = 500 # number of time steps
|
|
21
|
+
au2ps = 0.00002418884254 # Conversion of attoseconds to atomic units
|
|
22
|
+
timeau = 12.409275
|
|
23
|
+
DT = 20 * au2ps * timeau # time step in au
|
|
24
|
+
|
|
25
|
+
FINAL_TIME = TIME_STEPS * DT # final time
|
|
26
|
+
DOF_E = 2 # number of electronic states
|
|
27
|
+
DOF_E_SQ = DOF_E * DOF_E
|
|
28
|
+
|
|
29
|
+
##Simulation Parameter for TT-TFD
|
|
30
|
+
# TFD parameter: for Discretized nuclear DOFs
|
|
31
|
+
DOF_N = 50 # number of nuclear DOF
|
|
32
|
+
OMEGA_MAX = 10
|
|
33
|
+
|
|
34
|
+
# TT constants
|
|
35
|
+
eps = 1e-12 # tt approx error
|
|
36
|
+
dim = DOF_N # number of coords
|
|
37
|
+
occ = 10 # maximum occupation number; low for harmonic systems
|
|
38
|
+
MAX_TT_RANK = 10
|
|
39
|
+
|
|
40
|
+
print(" omega_max =", OMEGA_MAX)
|
|
41
|
+
print(" time steps =", TIME_STEPS)
|
|
42
|
+
print(" DT =", DT)
|
|
43
|
+
print(" final time =", FINAL_TIME)
|
|
44
|
+
print(" DOF_E =", DOF_E)
|
|
45
|
+
print(" DOF_N =", DOF_N)
|
|
46
|
+
|
|
47
|
+
##Simulation Parameter for GQME
|
|
48
|
+
MEM_TIME = DT * TIME_STEPS
|
|
49
|
+
HBAR = 1
|
|
50
|
+
MAX_ITERS = 30
|
|
51
|
+
CONVERGENCE_PARAM = 10.**(-10.)
|
|
52
|
+
|
|
53
|
+
##Parameter for output files
|
|
54
|
+
PARAM_STR = "_Spin-Boson_Ohmic_TT-TFD_b%sG%s_e%s_"%(BETA, GAMMA_DA, EPSILON)
|
|
55
|
+
PARAM_STR += "xi%swc%s_wmax%s_dofn%s"%(XI, OMEGA_C, OMEGA_MAX, DOF_N)
|
|
56
|
+
|
|
57
|
+
# Pauli matrices
|
|
58
|
+
X = np.array([[0, 1], [1, 0]], dtype=np.complex128)
|
|
59
|
+
Y = np.array([[0, -1j], [1j, 0]], dtype=np.complex128)
|
|
60
|
+
Z = np.array([[1, 0], [0, -1]], dtype=np.complex128)
|
|
61
|
+
I = np.eye(2, dtype=np.complex128)
|
|
62
|
+
|
qflux/GQME/readwrite.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from . import params as pa
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
def output_superoper_array(
|
|
6
|
+
time: List[float],
|
|
7
|
+
s_array: np.ndarray,
|
|
8
|
+
prefix: str
|
|
9
|
+
) -> None:
|
|
10
|
+
"""
|
|
11
|
+
Write the time-dependent superoperator array to disk.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
time (List[float]): List of time values.
|
|
15
|
+
s_array (np.ndarray): Superoperator array (shape (T, N^2, N^2)).
|
|
16
|
+
prefix (str): Output file name prefix.
|
|
17
|
+
"""
|
|
18
|
+
Nlen = len(time)
|
|
19
|
+
for j in range(pa.DOF_E_SQ):
|
|
20
|
+
a, b = divmod(j, pa.DOF_E)
|
|
21
|
+
for k in range(pa.DOF_E_SQ):
|
|
22
|
+
c, d = divmod(k, pa.DOF_E)
|
|
23
|
+
|
|
24
|
+
filename = f"{prefix}{a}{b}{c}{d}{pa.PARAM_STR}.txt"
|
|
25
|
+
with open(filename, "w") as f:
|
|
26
|
+
for i in range(Nlen):
|
|
27
|
+
real_part = s_array[i, j, k].real
|
|
28
|
+
imag_part = s_array[i, j, k].imag
|
|
29
|
+
f.write(f"{time[i]}\t{real_part}\t{imag_part}\n")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def read_superoper_array(
|
|
33
|
+
Nlen: int,
|
|
34
|
+
prefix: str
|
|
35
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
36
|
+
"""
|
|
37
|
+
Read the time-dependent superoperator array from disk.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
Nlen (int): Number of time steps.
|
|
41
|
+
prefix (str): Input file name prefix.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Tuple[np.ndarray, np.ndarray]:
|
|
45
|
+
- time (array of shape (Nlen,))
|
|
46
|
+
- S_array (array of shape (Nlen, N^2, N^2))
|
|
47
|
+
"""
|
|
48
|
+
S_array = np.zeros((Nlen, pa.DOF_E_SQ, pa.DOF_E_SQ), dtype=np.complex128)
|
|
49
|
+
time = np.zeros(Nlen, dtype=np.float64)
|
|
50
|
+
|
|
51
|
+
for j in range(pa.DOF_E_SQ):
|
|
52
|
+
a, b = divmod(j, pa.DOF_E)
|
|
53
|
+
for k in range(pa.DOF_E_SQ):
|
|
54
|
+
c, d = divmod(k, pa.DOF_E)
|
|
55
|
+
|
|
56
|
+
filename = f"{prefix}{a}{b}{c}{d}{pa.PARAM_STR}.txt"
|
|
57
|
+
data = np.loadtxt(filename)
|
|
58
|
+
time_read, real_part, imag_part = np.hsplit(data, 3)
|
|
59
|
+
|
|
60
|
+
for i in range(Nlen):
|
|
61
|
+
time[i] = time_read[i]
|
|
62
|
+
S_array[i, j, k] = real_part[i] + 1j * imag_part[i]
|
|
63
|
+
|
|
64
|
+
return time, S_array
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def output_operator_array(
|
|
68
|
+
time: List[float],
|
|
69
|
+
sigma: np.ndarray,
|
|
70
|
+
prefix: str
|
|
71
|
+
) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Write the time-dependent vectorized operator array to disk.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
time (List[float]): List of time values.
|
|
77
|
+
sigma (np.ndarray): Operator array (shape (T, N^2)).
|
|
78
|
+
prefix (str): Output file name prefix.
|
|
79
|
+
"""
|
|
80
|
+
for j in range(pa.DOF_E_SQ):
|
|
81
|
+
a, b = divmod(j, pa.DOF_E)
|
|
82
|
+
filename = f"{prefix}{a}{b}{pa.PARAM_STR}.txt"
|
|
83
|
+
with open(filename, "w") as f:
|
|
84
|
+
for i in range(len(time)):
|
|
85
|
+
real_part = sigma[i, j].real
|
|
86
|
+
imag_part = sigma[i, j].imag
|
|
87
|
+
f.write(f"{time[i]}\t{real_part}\t{imag_part}\n")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def read_operator_array(
|
|
91
|
+
Nlen: int,
|
|
92
|
+
prefix: str
|
|
93
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
94
|
+
"""
|
|
95
|
+
Read the time-dependent vectorized operator array from disk.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
Nlen (int): Number of time steps.
|
|
99
|
+
prefix (str): Input file name prefix.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Tuple[np.ndarray, np.ndarray]:
|
|
103
|
+
- time (array of shape (Nlen,))
|
|
104
|
+
- O_array (array of shape (Nlen, N^2))
|
|
105
|
+
"""
|
|
106
|
+
O_array = np.zeros((Nlen, pa.DOF_E_SQ), dtype=np.complex128)
|
|
107
|
+
time = np.zeros(Nlen, dtype=np.float64)
|
|
108
|
+
|
|
109
|
+
for j in range(pa.DOF_E_SQ):
|
|
110
|
+
a, b = divmod(j, pa.DOF_E)
|
|
111
|
+
filename = f"{prefix}{a}{b}{pa.PARAM_STR}.txt"
|
|
112
|
+
data = np.loadtxt(filename)
|
|
113
|
+
time_read, real_part, imag_part = np.hsplit(data, 3)
|
|
114
|
+
|
|
115
|
+
for i in range(Nlen):
|
|
116
|
+
time[i] = time_read[i]
|
|
117
|
+
O_array[i, j] = real_part[i] + 1j * imag_part[i]
|
|
118
|
+
|
|
119
|
+
return time, O_array
|