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
@@ -0,0 +1,427 @@
1
+ # Class for classical propagation methods
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib import axes
5
+ from tqdm.auto import trange
6
+ import qutip as qt
7
+ from typing import Callable
8
+ from .utils import *
9
+
10
+
11
+ class DynamicsCS:
12
+ """
13
+ Class for closed-system dynamics. **All input parameters must be in
14
+ atomic units to ensure consistency. Please be sure to convert your
15
+ parameters to atomic units prior to instantiation.**
16
+
17
+ """
18
+
19
+ def __init__(self, n_basis: int = 128, xo: float = 1.0, po: float = 0.0, mass: float = 1.0,
20
+ omega: float = 1.0) -> None:
21
+ """
22
+ Args:
23
+ n_basis (int): Number of states to include in the chosen representation. If basis
24
+ = 'ladder', this is the Fock cutoff and defines the number of states
25
+ used for representing the ladder operators. If basis = 'coordinate',
26
+ this defines the number of points for the position and momenta.
27
+
28
+ xo (float, optional): Defines the displacement of the initial state in the position
29
+ coordinate. Default is 1.0 Bohr.
30
+
31
+ po (float, optional): Defines the displacement of the initial state in the position
32
+ coordinate. Default is 1.0 au.
33
+
34
+ mass (float, optional): Defines the mass of the particle/system of interest.
35
+ Default is 1.0 au.
36
+
37
+ omega (float, optional): Frequency of harmonic oscillator.
38
+ Default is 1.0 au.
39
+
40
+ """
41
+ #--------- Required Attributes Populated During Execution ----------#
42
+ self.n_basis = n_basis
43
+ self.xo = xo
44
+ self.po = po
45
+ self.mass = mass
46
+ self.hbar = 1.0
47
+ self.omega = omega
48
+ #--------- Below are Attributes Populated During Execution ---------#
49
+ self.total_time = 0.
50
+ self.n_tsteps = 0.
51
+ self._KE_op = None
52
+ self._PE_op = None
53
+ self.H_op = None
54
+ self.prop_KE_op = None
55
+ self.prop_PE_op = None
56
+ self.prop_H_op = None
57
+ # Grid operators
58
+ self._KE_grid = None
59
+ self._PE_grid = None
60
+ self.H_grid = None
61
+ self.PE_prop_grid = None
62
+ self.KE_prop_grid = None
63
+
64
+
65
+ def _get_xgrid(self, x_min: float, x_max: float) -> None:
66
+ """
67
+ Populate the `self.x_grid` and `self.dx` attributes. This function
68
+ generates an array of `self.n_basis` evenly spaced values between
69
+ `x_min` and `x_max`.
70
+
71
+ Args:
72
+ x_min (float): Minimum value of x-coordinates
73
+ x_max (float): Maximum value of x-coordinates
74
+
75
+ Returns:
76
+ self.dx (float): Spacing between points in the x-coordinate grid.
77
+ self.xgrid (array_like): Array of grid points from x_min to x_max with spacing of dx
78
+ """
79
+ dx = (x_max - x_min) / self.n_basis
80
+ x_grid = np.arange(-self.n_basis / 2, self.n_basis / 2) * dx
81
+ self.dx = dx
82
+ self.x_grid = x_grid
83
+ return
84
+
85
+
86
+ def _get_pgrid(self, x_min: float, x_max: float, reorder: bool = True) -> None:
87
+ """
88
+ Populate the `self.p_grid` and `self.dp` attributes. This function
89
+ generates an array of `self.n_basis` evenly spaced values.
90
+
91
+ Args:
92
+ x_min (float): Minimum value of x-coordinates
93
+ x_max (float): Maximum value of x-coordinates
94
+ reorder (bool): Boolean flag to determine whether points should be reordered to be
95
+ compatible with the FFT routine or not.
96
+
97
+ Returns:
98
+ self.dp (float): Spacing between points in the p-coordinate grid.
99
+ self.pgrid (array_like): Array of momentum grid points
100
+ """
101
+ dp = 2 * np.pi / (x_max - x_min)
102
+ pmin = -dp * self.n_basis / 2
103
+ pmax = dp * self.n_basis / 2
104
+ plus_pgrid = np.linspace(0, pmax, self.n_basis // 2 + 1)
105
+ minus_pgrid = - np.flip(np.copy(plus_pgrid))
106
+ if reorder:
107
+ p_grid = np.concatenate((plus_pgrid[:-1], minus_pgrid[:-1]))
108
+ else:
109
+ p_grid = np.concatenate((minus_pgrid, plus_pgrid))
110
+ self.p_grid = p_grid
111
+ self.dp = dp
112
+ return
113
+
114
+
115
+ def set_coordinate_operators(self, x_min: float = -7., x_max: float = 7., reorder_p: bool = True) -> None:
116
+ """
117
+ Populate the `self.x_grid`, `self.p_grid`, `self.dx`, and `self.dp`
118
+ attributes. This functions generates an array of `self.n_basis`
119
+ evenly spaced values.
120
+
121
+ Args:
122
+ x_min : float
123
+ Minimum value of x-coordinates
124
+ x_max : float
125
+ Maximum value of x-coordinates
126
+ reorder_p : bool
127
+ Boolean flag to determine whether momentum values should be
128
+ reordered to be compatible with the FFT routine or not.
129
+
130
+ Returns:
131
+ self.dx : float
132
+ Spacing between points in the x-coordinate grid.
133
+ self.xgrid : array_like
134
+ Array of x-values
135
+ self.dp : float
136
+ Spacing between points in the p-coordinate grid.
137
+ self.pgrid : array_like
138
+ Array of p-values
139
+ """
140
+ self._get_xgrid(x_min, x_max)
141
+ self._get_pgrid(x_min, x_max, reorder=reorder_p)
142
+ return
143
+
144
+
145
+ def initialize_operators(self):
146
+ """
147
+ Function to initialize core operators in the chosen basis.
148
+
149
+ """
150
+
151
+ self.a_op = qt.destroy(self.n_basis)
152
+ self.x_op = qt.position(self.n_basis)
153
+ self.p_op = qt.momentum(self.n_basis)
154
+ return
155
+
156
+
157
+ def _set_hamiltonian_grid(self, potential_type: str = 'harmonic', **kwargs):
158
+ if potential_type == 'harmonic':
159
+
160
+ # Set attributes for the coordinate basis
161
+ self._PE_grid = self.mass * self.omega ** 2 * self.x_grid ** 2 / 2.
162
+ self._KE_grid = self.p_grid ** 2 / (2. * self.mass)
163
+ self.H_grid = self._PE_grid + self._KE_grid
164
+ elif potential_type == 'quartic':
165
+ if kwargs:
166
+ if 'a0' in kwargs:
167
+ a0 = kwargs['a0']
168
+ if 'a1' in kwargs:
169
+ a1 = kwargs['a1']
170
+ if 'a2' in kwargs:
171
+ a2 = kwargs['a2']
172
+ if 'a3' in kwargs:
173
+ a3 = kwargs['a3']
174
+ if 'a4' in kwargs:
175
+ a4 = kwargs['a4']
176
+ if 'x0' in kwargs:
177
+ x0 = kwargs['x0']
178
+ # Assume that all inputs have the proper atomic units:
179
+ cf = 1.0
180
+ xi = self.x_op
181
+
182
+ else:
183
+ # Define relevant parameters
184
+ cf = convert_eV_to_au(1.)
185
+ x0 = 1.9592
186
+ a0 = 0.0
187
+ a1 = 0.429
188
+ a2 = -1.126
189
+ a3 = -0.143
190
+ a4 = 0.563
191
+ # Do calculation for ladder basis
192
+ xi = self.x_grid / x0
193
+ self._PE_grid = cf * (a0 + a1 * xi + a2 * xi ** 2 + a3 * xi ** 3 + a4 * xi ** 4)
194
+ self._KE_grid = self.p_grid ** 2 / (2. * self.mass)
195
+ self.H_grid = self._PE_grid + self._KE_grid
196
+ return
197
+
198
+
199
+ def _set_hamiltonian_qt(self, potential_type: str = 'harmonic', **kwargs):
200
+ if potential_type == 'harmonic':
201
+ # Set attributes for the ladder basis
202
+ self.H_op = self.hbar * self.omega * (self.a_op.dag() * self.a_op + 0.5)
203
+ self._KE_op = self.p_op ** 2 / (2. * self.mass)
204
+ self._PE_op = self.mass * self.omega ** 2 * self.x_op ** 2 / 0.5
205
+ self.H_xp_op = self._PE_op + self._KE_op
206
+ elif potential_type == 'quartic':
207
+ if kwargs:
208
+ if 'a0' in kwargs:
209
+ a0 = kwargs['a0']
210
+ if 'a1' in kwargs:
211
+ a1 = kwargs['a1']
212
+ if 'a2' in kwargs:
213
+ a2 = kwargs['a2']
214
+ if 'a3' in kwargs:
215
+ a3 = kwargs['a3']
216
+ if 'a4' in kwargs:
217
+ a4 = kwargs['a4']
218
+ if 'x0' in kwargs:
219
+ x0 = kwargs['x0']
220
+ # Assume that all inputs have the proper atomic units:
221
+ cf = 1.0
222
+ xi = self.x_op
223
+
224
+ else:
225
+ # Define relevant parameters
226
+ cf = convert_eV_to_au(1.)
227
+ x0 = 1.9592
228
+ a0 = 0.0
229
+ a1 = 0.429
230
+ a2 = -1.126
231
+ a3 = -0.143
232
+ a4 = 0.563
233
+ # Do calculation for ladder basis
234
+ xi = self.x_op / x0
235
+ self.x0 = x0
236
+ self._PE_op = cf * (a0 + a1 * xi + a2 * xi ** 2 + a3 * xi ** 3 + a4 * xi ** 4)
237
+ self._KE_op = self.p_op ** 2 / (2. * self.mass)
238
+ self.H_op = self._PE_op + self._KE_op
239
+ return
240
+
241
+ def set_hamiltonian(self, potential_type: str = 'harmonic', **kwargs):
242
+ """
243
+ Function to define Hamiltonian.
244
+
245
+ Args:
246
+ potential_type : str
247
+ String defining the type of potential energy surface.
248
+ Available options are: ('harmonic', 'quartic', ...)
249
+
250
+ Note: You can manually define your potential energy using the functions:
251
+ - set_H_grid_with_custom_potential
252
+ - set_H_op_with_custom_potential
253
+
254
+ """
255
+
256
+ if potential_type == 'harmonic':
257
+ self._set_hamiltonian_grid(potential_type=potential_type, **kwargs)
258
+ self._set_hamiltonian_qt(potential_type=potential_type, **kwargs)
259
+ elif potential_type == 'quartic':
260
+ self._set_hamiltonian_grid(potential_type=potential_type, **kwargs)
261
+ self._set_hamiltonian_qt(potential_type=potential_type, **kwargs)
262
+ else:
263
+ print('Error, this potential type has not yet been implemented!')
264
+ print('Set your parameters with the custom functions!')
265
+ return
266
+
267
+
268
+ def set_H_grid_with_custom_potential(self, custom_function: Callable, **kwargs):
269
+ """
270
+ Function to allow for user-defined potential defined by custom_function. Must be a function of qutip operators.
271
+
272
+ Args:
273
+ custom_function (Callable): Function that defines the custom potential
274
+ energy. Must return an array
275
+
276
+ """
277
+ potential = custom_function(**kwargs)
278
+ self._PE_grid = potential
279
+ self._KE_grid = self.p_grid ** 2 / (2. * self.mass)
280
+ self.H_grid = self._PE_grid + self._KE_grid
281
+ return
282
+
283
+
284
+ def set_H_op_with_custom_potential(self, custom_function: Callable, **kwargs):
285
+ """
286
+ Function to allow for user-defined potential defined by custom_function. Must be a function of qutip operators.
287
+
288
+ Args:
289
+ custom_function (Callable): Function that defines the potential
290
+ energy in terms of qutip QObj operators. Must return a qutip.Qobj
291
+ """
292
+ potential = custom_function(**kwargs)
293
+ self._PE_op = potential
294
+ self._KE_op = self.p_op ** 2 / (2. * self.mass)
295
+ self.H_op = self._PE_op + self._KE_op
296
+ return
297
+
298
+
299
+ def set_initial_state(self, wfn_omega: float = 1.0):
300
+ """
301
+ Function to define the initial state. By default, a coherent state is
302
+ used as the initial state defined in the basis chosen upon instantiation
303
+
304
+ Args:
305
+ wfn_omega (float, optional): Defines the frequency/width of the initial state.
306
+ Default is 1.0 au.
307
+ """
308
+
309
+ alpha_val = (self.xo + 1j * self.po) / np.sqrt(2)
310
+ psio = qt.coherent(self.n_basis, alpha=alpha_val)
311
+ # Now populate the initial state in the grid basis
312
+ normalization = (self.mass * wfn_omega / np.pi / self.hbar) ** (0.25)
313
+ exponential = np.exp(-1 * (self.mass * wfn_omega / self.hbar / 2) *
314
+ ((self.x_grid - self.xo) ** 2)
315
+ + 1j * self.po * self.x_grid / self.hbar
316
+ )
317
+
318
+ coherent_state = normalization * exponential
319
+ # Set the attributes
320
+ self.psio_grid = coherent_state
321
+ self.psio_op = psio
322
+ return
323
+
324
+
325
+
326
+ def custom_grid_state_initialization(self, function_name: Callable, **kwargs):
327
+ """
328
+ Function to allow for customized grid state initialization.
329
+
330
+ Args:
331
+ function_name (Callable): name of user-defined function that returns
332
+ the initial state. Must return an array
333
+ """
334
+
335
+ self.psio_grid = function_name(**kwargs)
336
+ return
337
+
338
+ def custom_ladder_state_initialization(self, function_name: Callable, **kwargs):
339
+ """
340
+ Function to allow for customized ladder state initialization.
341
+
342
+ Args:
343
+ function_name (Callable): name of user-defined function that returns
344
+ the initial state. Must return a qutip.Qobj.
345
+ """
346
+
347
+ self.psio_op = function_name(**kwargs)
348
+ return
349
+
350
+ def set_propagation_time(self, total_time: float, n_tsteps: int):
351
+ """
352
+ Function to define the propagation time, an array of times from
353
+ t=0 to total_time, with n_tsteps equally-spaced steps.
354
+
355
+ Args:
356
+ total_time : float
357
+ The total time for which we wish to compute the dynamics.
358
+ n_tsteps : int
359
+ The number of equally-spaced time steps used to compute the dynamics
360
+
361
+ Returns:
362
+ self.tlist : array-like
363
+
364
+ """
365
+
366
+ self.tlist = np.linspace(0., total_time, n_tsteps+1)
367
+ self.dt = self.tlist[1] - self.tlist[0]
368
+ self.n_tsteps = n_tsteps
369
+ return
370
+
371
+
372
+ def propagate_qt(self, solver_options : dict = None):
373
+ """
374
+ Function used to propagate with qutip.
375
+
376
+ Args:
377
+ solver_options (dict): A dictionary of arguments to pass to the qutip.sesolve function
378
+
379
+ Returns:
380
+ dynamics_results (array-like): array containing the propagated state
381
+
382
+ """
383
+
384
+ options = {'nsteps': len(self.tlist),
385
+ 'progress_bar': True}
386
+
387
+ if solver_options:
388
+ for key in solver_options:
389
+ options[key] = solver_options[key]
390
+
391
+ results = qt.sesolve(self.H_op, self.psio_op, self.tlist,
392
+ options=options)
393
+
394
+ self.dynamics_results_op = results.states
395
+ return
396
+
397
+
398
+ def propagate_SOFT(self):
399
+ """
400
+ Function used to propagate with the 2nd-Order Trotter Expansion.
401
+
402
+ $$
403
+ e^{- \frac{i}{\\hbar} H t} \approx e^{- \frac{i}{\\hbar} V t/2} e^{- \frac{i}{\\hbar} T t} e^{- \frac{i}{\\hbar} V t/2} + \\mathcal{O}^{3}
404
+ $$
405
+
406
+ Returns:
407
+ dynamics_results_grid (array-like): array containing the propagated state
408
+ shape (n_tsteps x self.n_basis)
409
+
410
+ """
411
+ self.tau = self.tlist[1] - self.tlist[0]
412
+ PE_prop = np.exp(-1.0j * self._PE_grid / 2 * self.tau / self.hbar)
413
+ KE_prop = np.exp(-1.0j * self._KE_grid * self.tau / self.hbar)
414
+
415
+ self.PE_prop_grid = PE_prop
416
+ self.KE_prop_grid = KE_prop
417
+
418
+ propagated_states = [self.psio_grid]
419
+ psi_t = self.psio_grid
420
+ for ii in range(1, len(self.tlist)):
421
+ psi_t_position_grid = PE_prop * psi_t
422
+ psi_t_momentum_grid = KE_prop * np.fft.fft(psi_t_position_grid, norm="ortho")
423
+ psi_t = PE_prop * np.fft.ifft(psi_t_momentum_grid, norm="ortho")
424
+ propagated_states.append(psi_t)
425
+
426
+ self.dynamics_results_grid = np.asarray(propagated_states)
427
+ return
@@ -0,0 +1,22 @@
1
+ # Utility Functions/Patches:
2
+ from qiskit import transpile
3
+
4
+
5
+ def execute(QCircuit, backend=None, shots=None):
6
+ '''
7
+ Function to replace the now-deprecated Qiskit
8
+ `QuantumCircuit.execute()` method.
9
+
10
+ Input:
11
+ - `QCircuit`: qiskit.QuantumCircuit object
12
+ - `Backend`: qiskit.Backend instance
13
+ - `shots`: int specifying the number of shots
14
+ '''
15
+ # Transpile circuit with statevector backend
16
+ tmp_circuit = transpile(QCircuit, backend)
17
+ # Run the transpiled circuit
18
+ if shots:
19
+ job = backend.run(tmp_circuit, n_shots=shots)
20
+ else:
21
+ job = backend.run(tmp_circuit)
22
+ return(job)
@@ -0,0 +1,88 @@
1
+ from qiskit.quantum_info import SparsePauliOp
2
+
3
+
4
+ # -----------------------------------------------------------------
5
+ # Hamiltonian Functions
6
+ # -----------------------------------------------------------------
7
+ def get_hamiltonian_n_site_terms(n, coeff, n_qubits):
8
+ '''
9
+ Assembles each term in the Hamiltonian based on their Pauli string
10
+ representation and multiplying by the respective coefficient.
11
+ '''
12
+ XX_coeff = coeff[0]
13
+ YY_coeff = coeff[1]
14
+ ZZ_coeff = coeff[2]
15
+ Z_coeff = coeff[3]
16
+
17
+ XX_term = SparsePauliOp(("I" * n + "XX" + "I" * (n_qubits - 2 - n)))
18
+ XX_term *= XX_coeff
19
+ YY_term = SparsePauliOp(("I" * n + "YY" + "I" * (n_qubits - 2 - n)))
20
+ YY_term *= YY_coeff
21
+ ZZ_term = SparsePauliOp(("I" * n + "ZZ" + "I" * (n_qubits - 2 - n)))
22
+ ZZ_term *= ZZ_coeff
23
+ Z_term = SparsePauliOp(("I" * n + "Z" + "I" * (n_qubits - 1 - n)))
24
+ Z_term *= Z_coeff
25
+
26
+ return (XX_term + YY_term + ZZ_term + Z_term)
27
+
28
+
29
+ def get_heisenberg_hamiltonian(n_qubits, coeff=None):
30
+ r'''
31
+ Takes an integer number corresponding to number of spins/qubits
32
+ and a list of sublists containing the necessary coefficients
33
+ to assemble the complete Hamiltonian:
34
+ $$
35
+ H = \sum _i ^N h_z Z_i
36
+ + \sum _i ^{N-1} (h_xx X_iX_{i+1}
37
+ + h_yy Y_iY_{i+1}
38
+ + h_zz Z_iZ_{i+1}
39
+ )
40
+ $$
41
+ Each sublist contains the [XX, YY, ZZ, Z] coefficients in this order.
42
+ The last sublist should have the same shape, but only the Z component
43
+ is used.
44
+ If no coefficient list is provided, all are set to 1.
45
+ '''
46
+
47
+ # Three qubits because for 2 we get H_O = 0
48
+ assert n_qubits >= 3
49
+
50
+ if coeff == None:
51
+ 'Setting default values for the coefficients'
52
+ coeff = [[1.0, 1.0, 1.0, 1.0] for i in range(n_qubits)]
53
+
54
+ # Even terms of the Hamiltonian
55
+ # (summing over individual pair-wise elements)
56
+ H_E = sum((get_hamiltonian_n_site_terms(i, coeff[i], n_qubits)
57
+ for i in range(0, n_qubits-1, 2)))
58
+
59
+ # Odd terms of the Hamiltonian
60
+ # (summing over individual pair-wise elements)
61
+ H_O = sum((get_hamiltonian_n_site_terms(i, coeff[i], n_qubits)
62
+ for i in range(1, n_qubits-1, 2)))
63
+
64
+ # adding final Z term at the Nth site
65
+ final_term = SparsePauliOp("I" * (n_qubits - 1) + "Z")
66
+ final_term *= coeff[n_qubits-1][3]
67
+ if (n_qubits % 2) == 0:
68
+ H_E += final_term
69
+ else:
70
+ H_O += final_term
71
+
72
+ # Returns the list of the two sets of terms
73
+ return [H_E, H_O]
74
+
75
+
76
+ if __name__ == '__main__':
77
+ num_q = 3
78
+ # XX YY ZZ, Z
79
+ ham_coeffs = ([[0.75/2, 0.75/2, 0.0, 0.65]]
80
+ + [[0.5, 0.5, 0.0, 1.0]
81
+ for i in range(num_q-1)])
82
+
83
+ spin_chain_hamiltonian = get_heisenberg_hamiltonian(num_q,
84
+ ham_coeffs)
85
+ print('Hamiltonian separated into even and odd components:')
86
+ print(spin_chain_hamiltonian)
87
+ print('Hamiltonian combining even and odd components:')
88
+ print(sum(spin_chain_hamiltonian))