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.

Files changed (38) hide show
  1. qflux/GQME/__init__.py +7 -0
  2. qflux/GQME/dynamics_GQME.py +438 -0
  3. qflux/GQME/params.py +62 -0
  4. qflux/GQME/readwrite.py +119 -0
  5. qflux/GQME/tdvp.py +233 -0
  6. qflux/GQME/tt_tfd.py +448 -0
  7. qflux/__init__.py +5 -0
  8. qflux/closed_systems/__init__.py +17 -0
  9. qflux/closed_systems/classical_methods.py +427 -0
  10. qflux/closed_systems/custom_execute.py +22 -0
  11. qflux/closed_systems/hamiltonians.py +88 -0
  12. qflux/closed_systems/qubit_methods.py +266 -0
  13. qflux/closed_systems/spin_dynamics_oo.py +371 -0
  14. qflux/closed_systems/spin_propagators.py +300 -0
  15. qflux/closed_systems/utils.py +205 -0
  16. qflux/open_systems/__init__.py +2 -0
  17. qflux/open_systems/dilation_circuit.py +183 -0
  18. qflux/open_systems/numerical_methods.py +303 -0
  19. qflux/open_systems/params.py +29 -0
  20. qflux/open_systems/quantum_simulation.py +360 -0
  21. qflux/open_systems/trans_basis.py +121 -0
  22. qflux/open_systems/walsh_gray_optimization.py +311 -0
  23. qflux/typing/__init__.py +0 -0
  24. qflux/typing/examples.py +24 -0
  25. qflux/utils/__init__.py +0 -0
  26. qflux/utils/io.py +16 -0
  27. qflux/utils/logging_config.py +61 -0
  28. qflux/variational_methods/__init__.py +1 -0
  29. qflux/variational_methods/qmad/__init__.py +0 -0
  30. qflux/variational_methods/qmad/ansatz.py +64 -0
  31. qflux/variational_methods/qmad/ansatzVect.py +61 -0
  32. qflux/variational_methods/qmad/effh.py +75 -0
  33. qflux/variational_methods/qmad/solver.py +356 -0
  34. qflux-0.0.1.dist-info/METADATA +144 -0
  35. qflux-0.0.1.dist-info/RECORD +38 -0
  36. qflux-0.0.1.dist-info/WHEEL +5 -0
  37. qflux-0.0.1.dist-info/licenses/LICENSE +674 -0
  38. qflux-0.0.1.dist-info/top_level.txt +1 -0
qflux/GQME/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+
2
+ import sys
3
+ import os
4
+
5
+ ROOT_DIR = os.path.abspath('..')
6
+ if ROOT_DIR not in sys.path:
7
+ sys.path.append(ROOT_DIR)
@@ -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
+
@@ -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