Trajectree 0.0.0__py3-none-any.whl → 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.
Files changed (122) hide show
  1. trajectree/__init__.py +3 -0
  2. trajectree/fock_optics/devices.py +1 -1
  3. trajectree/fock_optics/light_sources.py +2 -2
  4. trajectree/fock_optics/measurement.py +3 -3
  5. trajectree/fock_optics/utils.py +6 -6
  6. trajectree/quimb/docs/_pygments/_pygments_dark.py +118 -0
  7. trajectree/quimb/docs/_pygments/_pygments_light.py +118 -0
  8. trajectree/quimb/docs/conf.py +158 -0
  9. trajectree/quimb/docs/examples/ex_mpi_expm_evo.py +62 -0
  10. trajectree/quimb/quimb/__init__.py +507 -0
  11. trajectree/quimb/quimb/calc.py +1491 -0
  12. trajectree/quimb/quimb/core.py +2279 -0
  13. trajectree/quimb/quimb/evo.py +712 -0
  14. trajectree/quimb/quimb/experimental/__init__.py +0 -0
  15. trajectree/quimb/quimb/experimental/autojittn.py +129 -0
  16. trajectree/quimb/quimb/experimental/belief_propagation/__init__.py +109 -0
  17. trajectree/quimb/quimb/experimental/belief_propagation/bp_common.py +397 -0
  18. trajectree/quimb/quimb/experimental/belief_propagation/d1bp.py +316 -0
  19. trajectree/quimb/quimb/experimental/belief_propagation/d2bp.py +653 -0
  20. trajectree/quimb/quimb/experimental/belief_propagation/hd1bp.py +571 -0
  21. trajectree/quimb/quimb/experimental/belief_propagation/hv1bp.py +775 -0
  22. trajectree/quimb/quimb/experimental/belief_propagation/l1bp.py +316 -0
  23. trajectree/quimb/quimb/experimental/belief_propagation/l2bp.py +537 -0
  24. trajectree/quimb/quimb/experimental/belief_propagation/regions.py +194 -0
  25. trajectree/quimb/quimb/experimental/cluster_update.py +286 -0
  26. trajectree/quimb/quimb/experimental/merabuilder.py +865 -0
  27. trajectree/quimb/quimb/experimental/operatorbuilder/__init__.py +15 -0
  28. trajectree/quimb/quimb/experimental/operatorbuilder/operatorbuilder.py +1631 -0
  29. trajectree/quimb/quimb/experimental/schematic.py +7 -0
  30. trajectree/quimb/quimb/experimental/tn_marginals.py +130 -0
  31. trajectree/quimb/quimb/experimental/tnvmc.py +1483 -0
  32. trajectree/quimb/quimb/gates.py +36 -0
  33. trajectree/quimb/quimb/gen/__init__.py +2 -0
  34. trajectree/quimb/quimb/gen/operators.py +1167 -0
  35. trajectree/quimb/quimb/gen/rand.py +713 -0
  36. trajectree/quimb/quimb/gen/states.py +479 -0
  37. trajectree/quimb/quimb/linalg/__init__.py +6 -0
  38. trajectree/quimb/quimb/linalg/approx_spectral.py +1109 -0
  39. trajectree/quimb/quimb/linalg/autoblock.py +258 -0
  40. trajectree/quimb/quimb/linalg/base_linalg.py +719 -0
  41. trajectree/quimb/quimb/linalg/mpi_launcher.py +397 -0
  42. trajectree/quimb/quimb/linalg/numpy_linalg.py +244 -0
  43. trajectree/quimb/quimb/linalg/rand_linalg.py +514 -0
  44. trajectree/quimb/quimb/linalg/scipy_linalg.py +293 -0
  45. trajectree/quimb/quimb/linalg/slepc_linalg.py +892 -0
  46. trajectree/quimb/quimb/schematic.py +1518 -0
  47. trajectree/quimb/quimb/tensor/__init__.py +401 -0
  48. trajectree/quimb/quimb/tensor/array_ops.py +610 -0
  49. trajectree/quimb/quimb/tensor/circuit.py +4824 -0
  50. trajectree/quimb/quimb/tensor/circuit_gen.py +411 -0
  51. trajectree/quimb/quimb/tensor/contraction.py +336 -0
  52. trajectree/quimb/quimb/tensor/decomp.py +1255 -0
  53. trajectree/quimb/quimb/tensor/drawing.py +1646 -0
  54. trajectree/quimb/quimb/tensor/fitting.py +385 -0
  55. trajectree/quimb/quimb/tensor/geometry.py +583 -0
  56. trajectree/quimb/quimb/tensor/interface.py +114 -0
  57. trajectree/quimb/quimb/tensor/networking.py +1058 -0
  58. trajectree/quimb/quimb/tensor/optimize.py +1818 -0
  59. trajectree/quimb/quimb/tensor/tensor_1d.py +4778 -0
  60. trajectree/quimb/quimb/tensor/tensor_1d_compress.py +1854 -0
  61. trajectree/quimb/quimb/tensor/tensor_1d_tebd.py +662 -0
  62. trajectree/quimb/quimb/tensor/tensor_2d.py +5954 -0
  63. trajectree/quimb/quimb/tensor/tensor_2d_compress.py +96 -0
  64. trajectree/quimb/quimb/tensor/tensor_2d_tebd.py +1230 -0
  65. trajectree/quimb/quimb/tensor/tensor_3d.py +2869 -0
  66. trajectree/quimb/quimb/tensor/tensor_3d_tebd.py +46 -0
  67. trajectree/quimb/quimb/tensor/tensor_approx_spectral.py +60 -0
  68. trajectree/quimb/quimb/tensor/tensor_arbgeom.py +3237 -0
  69. trajectree/quimb/quimb/tensor/tensor_arbgeom_compress.py +565 -0
  70. trajectree/quimb/quimb/tensor/tensor_arbgeom_tebd.py +1138 -0
  71. trajectree/quimb/quimb/tensor/tensor_builder.py +5411 -0
  72. trajectree/quimb/quimb/tensor/tensor_core.py +11179 -0
  73. trajectree/quimb/quimb/tensor/tensor_dmrg.py +1472 -0
  74. trajectree/quimb/quimb/tensor/tensor_mera.py +204 -0
  75. trajectree/quimb/quimb/utils.py +892 -0
  76. trajectree/quimb/tests/__init__.py +0 -0
  77. trajectree/quimb/tests/test_accel.py +501 -0
  78. trajectree/quimb/tests/test_calc.py +788 -0
  79. trajectree/quimb/tests/test_core.py +847 -0
  80. trajectree/quimb/tests/test_evo.py +565 -0
  81. trajectree/quimb/tests/test_gen/__init__.py +0 -0
  82. trajectree/quimb/tests/test_gen/test_operators.py +361 -0
  83. trajectree/quimb/tests/test_gen/test_rand.py +296 -0
  84. trajectree/quimb/tests/test_gen/test_states.py +261 -0
  85. trajectree/quimb/tests/test_linalg/__init__.py +0 -0
  86. trajectree/quimb/tests/test_linalg/test_approx_spectral.py +368 -0
  87. trajectree/quimb/tests/test_linalg/test_base_linalg.py +351 -0
  88. trajectree/quimb/tests/test_linalg/test_mpi_linalg.py +127 -0
  89. trajectree/quimb/tests/test_linalg/test_numpy_linalg.py +84 -0
  90. trajectree/quimb/tests/test_linalg/test_rand_linalg.py +134 -0
  91. trajectree/quimb/tests/test_linalg/test_slepc_linalg.py +283 -0
  92. trajectree/quimb/tests/test_tensor/__init__.py +0 -0
  93. trajectree/quimb/tests/test_tensor/test_belief_propagation/__init__.py +0 -0
  94. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d1bp.py +39 -0
  95. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_d2bp.py +67 -0
  96. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hd1bp.py +64 -0
  97. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_hv1bp.py +51 -0
  98. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l1bp.py +142 -0
  99. trajectree/quimb/tests/test_tensor/test_belief_propagation/test_l2bp.py +101 -0
  100. trajectree/quimb/tests/test_tensor/test_circuit.py +816 -0
  101. trajectree/quimb/tests/test_tensor/test_contract.py +67 -0
  102. trajectree/quimb/tests/test_tensor/test_decomp.py +40 -0
  103. trajectree/quimb/tests/test_tensor/test_mera.py +52 -0
  104. trajectree/quimb/tests/test_tensor/test_optimizers.py +488 -0
  105. trajectree/quimb/tests/test_tensor/test_tensor_1d.py +1171 -0
  106. trajectree/quimb/tests/test_tensor/test_tensor_2d.py +606 -0
  107. trajectree/quimb/tests/test_tensor/test_tensor_2d_tebd.py +144 -0
  108. trajectree/quimb/tests/test_tensor/test_tensor_3d.py +123 -0
  109. trajectree/quimb/tests/test_tensor/test_tensor_arbgeom.py +226 -0
  110. trajectree/quimb/tests/test_tensor/test_tensor_builder.py +441 -0
  111. trajectree/quimb/tests/test_tensor/test_tensor_core.py +2066 -0
  112. trajectree/quimb/tests/test_tensor/test_tensor_dmrg.py +388 -0
  113. trajectree/quimb/tests/test_tensor/test_tensor_spectral_approx.py +63 -0
  114. trajectree/quimb/tests/test_tensor/test_tensor_tebd.py +270 -0
  115. trajectree/quimb/tests/test_utils.py +85 -0
  116. trajectree/trajectory.py +2 -2
  117. {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/METADATA +2 -2
  118. trajectree-0.0.1.dist-info/RECORD +126 -0
  119. trajectree-0.0.0.dist-info/RECORD +0 -16
  120. {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/WHEEL +0 -0
  121. {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/licenses/LICENSE +0 -0
  122. {trajectree-0.0.0.dist-info → trajectree-0.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,712 @@
1
+ """Easy and efficient time evolutions.
2
+
3
+ Contains an evolution class, Evolution to easily and efficiently manage time
4
+ evolution of quantum states according to the Schrodinger equation,
5
+ and related functions.
6
+ """
7
+
8
+ import functools
9
+
10
+ import numpy as np
11
+ from scipy.integrate import complex_ode
12
+ from scipy.sparse.linalg import LinearOperator
13
+
14
+ from .core import (
15
+ dag,
16
+ dot,
17
+ explt,
18
+ eye,
19
+ isop,
20
+ issparse,
21
+ ldmul,
22
+ make_immutable,
23
+ qarray,
24
+ qu,
25
+ rdmul,
26
+ )
27
+ from .linalg.base_linalg import eigh, norm, expm_multiply, Lazy
28
+ from .linalg.approx_spectral import norm_fro_approx
29
+ from .utils import continuous_progbar, progbar, ensure_dict
30
+
31
+
32
+ CALLABLE_TIME_INDEP_CLASSES = (LinearOperator, Lazy)
33
+
34
+
35
+ # --------------------------------------------------------------------------- #
36
+ # Quantum evolution equations #
37
+ # --------------------------------------------------------------------------- #
38
+ #
39
+ # This are mostly just to be used internally with the integrators
40
+
41
+
42
+ def schrodinger_eq_ket(ham):
43
+ """Wavefunction schrodinger equation.
44
+
45
+ Parameters
46
+ ----------
47
+ ham : operator
48
+ Time-independant Hamiltonian governing evolution.
49
+
50
+ Returns
51
+ -------
52
+ psi_dot(t, y) : callable
53
+ Function to calculate psi_dot(t) at psi(t).
54
+ """
55
+
56
+ def psi_dot(_, y):
57
+ return -1.0j * dot(ham, y)
58
+
59
+ return psi_dot
60
+
61
+
62
+ def schrodinger_eq_ket_timedep(ham):
63
+ """Wavefunction time dependent schrodinger equation.
64
+
65
+ Parameters
66
+ ----------
67
+ ham : callable
68
+ Time-dependant Hamiltonian governing evolution, such that ``ham(t)``
69
+ returns an operator representation of the Hamiltonian at time ``t``.
70
+
71
+ Returns
72
+ -------
73
+ psi_dot(t, y) : callable
74
+ Function to calculate psi_dot(t) at psi(t).
75
+ """
76
+
77
+ def psi_dot(t, y):
78
+ return -1.0j * dot(ham(t), y)
79
+
80
+ return psi_dot
81
+
82
+
83
+ def schrodinger_eq_dop(ham):
84
+ """Density operator schrodinger equation, but with flattened input/output.
85
+
86
+ Note that this assumes both `ham` and `rho` are hermitian in order to speed
87
+ up the commutator, non-hermitian hamiltonians as used to model loss should
88
+ be treated explicilty or with `schrodinger_eq_dop_vectorized`.
89
+
90
+ Parameters
91
+ ----------
92
+ ham : operator
93
+ Time-independant Hamiltonian governing evolution.
94
+
95
+ Returns
96
+ -------
97
+ rho_dot(t, y) : callable
98
+ Function to calculate rho_dot(t) at rho(t), input and
99
+ output both in ravelled (1D form).
100
+ """
101
+ d = ham.shape[0]
102
+
103
+ def rho_dot(_, y):
104
+ hrho = dot(ham, y.reshape(d, d))
105
+ return -1.0j * (hrho - hrho.T.conj()).reshape(-1)
106
+
107
+ return rho_dot
108
+
109
+
110
+ def schrodinger_eq_dop_timedep(ham):
111
+ """Time dependent density operator schrodinger equation, but with flattened
112
+ input/output.
113
+
114
+ Note that this assumes both `ham(t)` and `rho` are hermitian in order to
115
+ speed up the commutator, non-hermitian hamiltonians as used to model loss
116
+ should be treated explicilty or with `schrodinger_eq_dop_vectorized`.
117
+
118
+ Parameters
119
+ ----------
120
+ ham : callable
121
+ Time-dependant Hamiltonian governing evolution, such that ``ham(t)``
122
+ returns an operator representation of the Hamiltonian at time ``t``.
123
+
124
+ Returns
125
+ -------
126
+ rho_dot(t, y) : callable
127
+ Function to calculate rho_dot(t) at rho(t), input and
128
+ output both in ravelled (1D form).
129
+ """
130
+ d = ham(0).shape[0]
131
+
132
+ def rho_dot(t, y):
133
+ hrho = dot(ham(t), y.reshape(d, d))
134
+ return -1.0j * (hrho - hrho.T.conj()).reshape(-1)
135
+
136
+ return rho_dot
137
+
138
+
139
+ def schrodinger_eq_dop_vectorized(ham):
140
+ """Density operator schrodinger equation, but with flattened input/output
141
+ and vectorised superoperator mode (no reshaping required).
142
+
143
+ Note that this is probably only more efficient for sparse Hamiltonians.
144
+
145
+ Parameters
146
+ ----------
147
+ ham: time-independant hamiltonian governing evolution
148
+
149
+ Returns
150
+ -------
151
+ rho_dot(t, y) : callable
152
+ Function to calculate rho_dot(t) at rho(t), input and
153
+ output both in ravelled (1D form).
154
+ """
155
+ d = ham.shape[0]
156
+ sparse = issparse(ham)
157
+ idt = eye(d, sparse=sparse)
158
+ evo_superop = -1.0j * ((ham & idt) - (idt & ham.T))
159
+
160
+ def rho_dot(_, y):
161
+ return dot(evo_superop, y)
162
+
163
+ return rho_dot
164
+
165
+
166
+ def lindblad_eq(ham, ls, gamma):
167
+ """Lindblad equation, but with flattened input/output.
168
+
169
+ Parameters
170
+ ----------
171
+ ham : operator
172
+ Time-independant hamiltonian governing evolution.
173
+ ls : sequence of matrices
174
+ Lindblad operators.
175
+ gamma : float
176
+ Dampening strength.
177
+
178
+ Returns
179
+ -------
180
+ rho_dot(t, y) : callable
181
+ Function to calculate rho_dot(t) at rho(t), input and
182
+ output both in ravelled (1D form).
183
+ """
184
+ d = ham.shape[0]
185
+ lls = tuple(dot(dag(l), l) for l in ls)
186
+
187
+ def gen_l_terms(rho):
188
+ for l, ll in zip(ls, lls):
189
+ yield (
190
+ dot(l, dot(rho, dag(l))) - 0.5 * (dot(rho, ll) + dot(ll, rho))
191
+ )
192
+
193
+ def rho_dot(_, y):
194
+ rho = y.reshape(d, d)
195
+ rho_d = dot(ham, rho)
196
+ rho_d -= rho_d.T.conj()
197
+ rho_d *= -1.0j
198
+ rho_d += gamma * sum(gen_l_terms(rho))
199
+ return np.asarray(rho_d).reshape(-1)
200
+
201
+ return rho_dot
202
+
203
+
204
+ def lindblad_eq_vectorized(ham, ls, gamma, sparse=False):
205
+ """Lindblad equation, but with flattened input/output and vectorised
206
+ superoperation mode (no reshaping required).
207
+
208
+ Parameters
209
+ ----------
210
+ ham : operator
211
+ Time-independant hamiltonian governing evolution.
212
+ ls : sequence of matrices
213
+ Lindblad operators.
214
+ gamma : float
215
+ Dampening strength.
216
+
217
+ Returns
218
+ -------
219
+ rho_dot(t, y) : callable
220
+ Function to calculate rho_dot(t) at rho(t), input and
221
+ output both in ravelled (1D form).
222
+ """
223
+ d = ham.shape[0]
224
+ ham_sparse = issparse(ham) or sparse
225
+ idt = eye(d, sparse=ham_sparse)
226
+ evo_superop = -1.0j * ((ham & idt) - (idt & ham.T))
227
+
228
+ def gen_lb_terms():
229
+ for l in ls:
230
+ lb_sparse = issparse(l) or sparse
231
+ idt = eye(d, sparse=lb_sparse)
232
+ yield (
233
+ (l & l.conj())
234
+ - 0.5 * ((idt & dot(dag(l), l).T) + (dot(dag(l), l) & idt))
235
+ )
236
+
237
+ evo_superop += gamma * sum(gen_lb_terms())
238
+
239
+ def rho_dot(_, y):
240
+ return dot(evo_superop, y)
241
+
242
+ return rho_dot
243
+
244
+
245
+ def _calc_evo_eq(isdop, issparse, isopen=False, timedep=False):
246
+ """Choose an appropirate dynamical equation to evolve with."""
247
+ eq_chooser = {
248
+ (0, 0, 0, 0): schrodinger_eq_ket,
249
+ (0, 1, 0, 0): schrodinger_eq_ket,
250
+ (1, 0, 0, 0): schrodinger_eq_dop,
251
+ (1, 1, 0, 0): schrodinger_eq_dop_vectorized,
252
+ (1, 0, 1, 0): lindblad_eq,
253
+ (1, 1, 1, 0): lindblad_eq_vectorized,
254
+ # time-dependent
255
+ (0, 0, 0, 1): schrodinger_eq_ket_timedep,
256
+ (0, 1, 0, 1): schrodinger_eq_ket_timedep,
257
+ (1, 0, 0, 1): schrodinger_eq_dop_timedep,
258
+ (1, 1, 0, 1): schrodinger_eq_dop_timedep,
259
+ }
260
+ return eq_chooser[(isdop, issparse, isopen, timedep)]
261
+
262
+
263
+ class Try2Then3Args:
264
+ def __init__(self, fn):
265
+ self.fn = fn
266
+ self.num_args = None
267
+
268
+ def first_call(self, t, p, H):
269
+ try:
270
+ res = self.fn(t, p)
271
+ self.num_args = 2
272
+ except TypeError as e:
273
+ if "positional" in e.args[0]:
274
+ res = self.fn(t, p, H)
275
+ self.num_args = 3
276
+ else:
277
+ raise
278
+ return res
279
+
280
+ def __call__(self, t, p, H):
281
+ if self.num_args is None:
282
+ return self.first_call(t, p, H)
283
+ elif self.num_args == 2:
284
+ return self.fn(t, p)
285
+ elif self.num_args == 3:
286
+ return self.fn(t, p, H)
287
+
288
+
289
+ # --------------------------------------------------------------------------- #
290
+ # Quantum Evolution Class #
291
+ # --------------------------------------------------------------------------- #
292
+
293
+
294
+ class Evolution(object):
295
+ """A class for evolving quantum systems according to Schrodinger equation.
296
+
297
+ The evolution can be performed in a number of ways:
298
+
299
+ - diagonalise the Hamiltonian (or use already diagonalised system).
300
+ - integrate the complex ODE, that is, the Schrodinger equation, using
301
+ scipy. Here either a mid- or high-order Dormand-Prince adaptive
302
+ time stepping scheme is used (see
303
+ :class:`scipy.integrate.complex_ode`).
304
+
305
+ Parameters
306
+ ----------
307
+ p0 : quantum state
308
+ Inital state, either vector or operator. If vector, converted to ket.
309
+ ham : operator, tuple (1d array, operator), or callable
310
+ Governing Hamiltonian, if tuple then assumed to contain
311
+ ``(eigvals, eigvecs)`` of presolved system. If callable (but not a
312
+ SciPy ``LinearOperator``), assume a time-dependent hamiltonian such
313
+ that ``ham(t)`` is the Hamiltonian at time ``t``. In this case, the
314
+ latest call to ``ham`` will be cached (and made immutable) in case it
315
+ is needed by callbacks passed to ``compute``.
316
+ t0 : float, optional
317
+ Initial time (i.e. time of state ``p0``), defaults to zero.
318
+ compute : callable, or dict of callable, optional
319
+ Function(s) to compute on the state at each time step. Function(s)
320
+ should take args (t, pt) or (t, pt, ham) if the Hamiltonian is
321
+ required. If ham is required, it will be passed in to the function
322
+ exactly as given to this ``Evolution`` instance, except if ``method``
323
+ is ``'solve'``, in which case it will be passed in as the solved system
324
+ ``(eigvals, eigvecs)``. If supplied with:
325
+
326
+ - single callable : ``Evolution.results`` will contain the results
327
+ as a list,
328
+ - dict of callables : ``Evolution.results`` will contain the
329
+ results as a dict of lists with corresponding keys to those
330
+ given in ``compute``.
331
+
332
+ int_stop : callable, optional
333
+ A condition to terminate the integration early if ``method`` is
334
+ ``'integrate'``. This callable is called at every successful
335
+ integration step and should take args (t, pt) or (t, pt, ham) similar
336
+ to the function(s) in the ``compute`` argument. It should return
337
+ ``-1`` to stop the integration, otherwise it should return ``None``
338
+ or ``0``.
339
+
340
+ method : {'integrate', 'solve', 'expm'}
341
+ How to evolve the system:
342
+
343
+ - ``'integrate'``: use definite integration. Get system at each
344
+ time step, only need action of Hamiltonian on state. Generally
345
+ efficient.
346
+ - ``'solve'``: diagonalise dense hamiltonian. Best for small
347
+ systems and allows arbitrary time steps without loss of
348
+ precision.
349
+ - ``'expm'``: compute the evolved state using the action of the
350
+ operator exponential in a 'single shot' style. Only needs action
351
+ of Hamiltonian, for very large systems can use distributed MPI.
352
+
353
+ int_small_step : bool, optional
354
+ If ``method='integrate'``, whether to use a low or high order
355
+ integrator to give naturally small or large steps.
356
+ expm_backend : {'auto', 'scipy', 'slepc'}
357
+ How to perform the expm_multiply function if ``method='expm'``. Can
358
+ further specifiy ``'slepc-krylov'``, or ``'slepc-expokit'``.
359
+ expm_opts : dict
360
+ Supplied to :func:`~quimb.linalg.base_linalg.expm_multiply`
361
+ function if ``method='expm'``.
362
+ progbar : bool, optional
363
+ Whether to show a progress bar when calling ``at_times`` or integrating
364
+ with the ``update_to`` method.
365
+ """
366
+
367
+ def __init__(
368
+ self,
369
+ p0,
370
+ ham,
371
+ t0=0,
372
+ compute=None,
373
+ int_stop=None,
374
+ method="integrate",
375
+ int_small_step=False,
376
+ expm_backend="AUTO",
377
+ expm_opts=None,
378
+ progbar=False,
379
+ ):
380
+ self._p0 = qu(p0)
381
+ self._t = self.t0 = t0
382
+ self._isdop = isop(self._p0) # Density operator evolution?
383
+ self._d = p0.shape[0] # Hilbert space dimension
384
+ self._progbar = progbar
385
+
386
+ self._timedep = callable(ham) and not isinstance(
387
+ ham, CALLABLE_TIME_INDEP_CLASSES
388
+ )
389
+
390
+ if self._timedep:
391
+ # cache the time-dependent Hamiltonian in case callbacks use it
392
+ noncacheing_ham = ham
393
+
394
+ @functools.lru_cache(1)
395
+ def ham(t):
396
+ Ht = noncacheing_ham(t)
397
+ if not isinstance(Ht, LinearOperator):
398
+ make_immutable(Ht)
399
+ return Ht
400
+
401
+ if (int_stop is not None) and (method != "integrate"):
402
+ raise ValueError(
403
+ "You can't provide an integration stopping "
404
+ "condition (int_stop) if the method is not "
405
+ "'integrate'"
406
+ )
407
+
408
+ self._setup_callback(compute, int_stop)
409
+ self._method = method
410
+
411
+ if method == "solve" or isinstance(ham, (tuple, list)):
412
+ if isinstance(ham, LinearOperator):
413
+ raise TypeError(
414
+ "You can't use the 'solve' method "
415
+ "with an abstract linear operator "
416
+ "Hamiltonian."
417
+ )
418
+ elif self._timedep:
419
+ raise TypeError(
420
+ "You can't use the 'solve' method "
421
+ "with a time-dependent Hamiltonian."
422
+ )
423
+ self._ham = ham
424
+ self._setup_solved_ham()
425
+
426
+ elif method == "integrate":
427
+ self._start_integrator(ham, int_small_step)
428
+ self._ham = ham
429
+ elif method == "expm":
430
+ if isinstance(ham, LinearOperator):
431
+ raise TypeError(
432
+ "You can't use the 'expm' method "
433
+ "with an abstract linear operator "
434
+ "Hamiltonian."
435
+ )
436
+ elif self._timedep:
437
+ raise TypeError(
438
+ "You can't use the 'expm' method "
439
+ "with a time-dependent Hamiltonian."
440
+ )
441
+ self._update_method = self._update_to_expm_ket
442
+ self._pt = self._p0
443
+ self.expm_backend = expm_backend
444
+ self.expm_opts = ensure_dict(expm_opts)
445
+ self._ham = ham
446
+ else:
447
+ raise ValueError(
448
+ f"Did not understand evolution method: '{method}'."
449
+ )
450
+
451
+ def _setup_callback(self, fn, int_stop):
452
+ """Setup callbacks in the correct place to compute into _results"""
453
+ # if fn is None there is no callback
454
+ if fn is None:
455
+ step_callback = None
456
+ # else fn is a dict of callbacks or a single callback
457
+ else:
458
+ # dict of funcs input -> dict of funcs output
459
+ if isinstance(fn, dict):
460
+ fn_try2then3args = {k: Try2Then3Args(v) for k, v in fn.items()}
461
+ self._results = {k: [] for k in fn}
462
+
463
+ def step_callback(t, pt, H):
464
+ for k, v in fn_try2then3args.items():
465
+ fn_result = v(t, pt, H)
466
+ self._results[k].append(fn_result)
467
+
468
+ # else results -> single list of outputs of fn
469
+ else:
470
+ fn_try2then3args = Try2Then3Args(fn)
471
+ self._results = []
472
+
473
+ def step_callback(t, pt, H):
474
+ fn_result = fn_try2then3args(t, pt, H)
475
+ self._results.append(fn_result)
476
+
477
+ # For the integration callback, additionally need to convert
478
+ # back to 'quantum' (column vector) form
479
+
480
+ # if no compute callback, check if there is an int_stop callback
481
+ if step_callback is None:
482
+ # if there is only an int_stop callback, set this up
483
+ if int_stop is not None:
484
+ int_stop_try2then3args = Try2Then3Args(int_stop)
485
+
486
+ def int_step_callback(t, y, H):
487
+ pt = qarray(y.reshape(self._d, -1))
488
+ return int_stop_try2then3args(t, pt, H)
489
+
490
+ # else if there is neither kind of callback but a progbar is
491
+ # needed, set up a dummy callback so it gets updated
492
+ elif self._progbar:
493
+
494
+ def int_step_callback(t, y, H):
495
+ pass
496
+
497
+ # else there are no callbacks and no progbar
498
+ else:
499
+ int_step_callback = None
500
+
501
+ # else there is compute callback, but may need to add int_stop callback
502
+ else:
503
+ # if both kinds of callback, combine them
504
+ if int_stop is not None:
505
+ int_stop_try2then3args = Try2Then3Args(int_stop)
506
+
507
+ def int_step_callback(t, y, H):
508
+ # For the integration callback, additionally need to
509
+ # convert back to 'quantum' (column vector) form
510
+ pt = qarray(y.reshape(self._d, -1))
511
+ step_callback(t, pt, H)
512
+ return int_stop_try2then3args(t, pt, H)
513
+
514
+ # else no int_stop callback, so just set up compute callback
515
+ else:
516
+
517
+ def int_step_callback(t, y, H):
518
+ # For the integration callback, additionally need to
519
+ # convert back to 'quantum' (column vector) form
520
+ pt = qarray(y.reshape(self._d, -1))
521
+ step_callback(t, pt, H)
522
+
523
+ self._step_callback = step_callback
524
+ self._int_step_callback = int_step_callback
525
+
526
+ def _setup_solved_ham(self):
527
+ """Solve the hamiltonian if needed and find the initial state
528
+ in the energy eigenbasis for quick evolution later.
529
+ """
530
+ # See if already solved from tuple
531
+ try:
532
+ evals, evecs = self._ham
533
+ self._method = "solve"
534
+ except ValueError:
535
+ evals, evecs = eigh(self._ham.toarray())
536
+ self._ham = (evals, evecs)
537
+
538
+ # Find initial state in energy eigenbasis at t0
539
+ if self._isdop:
540
+ self.pe0 = dot(dag(evecs), dot(self._p0, evecs))
541
+ self._update_method = self._update_to_solved_dop
542
+ else:
543
+ self.pe0 = dot(dag(evecs), self._p0)
544
+ self._update_method = self._update_to_solved_ket
545
+
546
+ # Current state (start with same as initial)
547
+ self._pt = self._p0
548
+
549
+ def _start_integrator(self, ham, small_step):
550
+ """Initialize a stepping integrator."""
551
+ if self._timedep:
552
+ H0 = ham(0.0)
553
+ else:
554
+ H0 = ham
555
+
556
+ # set complex ode with governing equation
557
+ evo_eq = _calc_evo_eq(self._isdop, issparse(H0), False, self._timedep)
558
+
559
+ self._stepper = complex_ode(evo_eq(ham))
560
+
561
+ # 5th order stpper or 8th order stepper
562
+ int_mthd, step_fct = ("dopri5", 150) if small_step else ("dop853", 50)
563
+ if isinstance(H0, LinearOperator):
564
+ # approx norm doesn't need to be very accurate
565
+ nrm0 = norm_fro_approx(H0, tol=0.1)
566
+ else:
567
+ nrm0 = norm(H0, "f")
568
+ first_step = nrm0 / step_fct
569
+
570
+ self._stepper.set_integrator(int_mthd, nsteps=0, first_step=first_step)
571
+
572
+ # Set step_callback to be evaluated with args (t, y) at each step
573
+ if self._int_step_callback is not None:
574
+
575
+ def solout(t, y):
576
+ res = self._int_step_callback(t, y, self._ham)
577
+ return res
578
+
579
+ self._stepper.set_solout(solout)
580
+
581
+ self._stepper.set_initial_value(
582
+ self._p0.toarray().reshape(-1), self.t0
583
+ )
584
+
585
+ # assign the correct update_to method
586
+ self._update_method = self._update_to_integrate
587
+
588
+ # Methods for updating the simulation ----------------------------------- #
589
+
590
+ def _update_to_expm_ket(self, t):
591
+ """Update the simulation to time ``t``, without explicitly computing
592
+ the operator exponential itself.
593
+ """
594
+ factor = -1j * (t - self.t)
595
+ self._pt = expm_multiply(
596
+ factor * self._ham,
597
+ self._pt,
598
+ backend=self.expm_backend,
599
+ **self.expm_opts,
600
+ )
601
+ self._t = t
602
+
603
+ # compute any callbacks into -> self._results
604
+ if self._step_callback is not None:
605
+ self._step_callback(t, self._pt, self._ham)
606
+
607
+ def _update_to_solved_ket(self, t):
608
+ """Update simulation consisting of a solved hamiltonian and a
609
+ wavefunction to time `t`.
610
+ """
611
+ self._t = t
612
+ evals, evecs = self._ham
613
+ lt = explt(evals, t - self.t0)
614
+ self._pt = evecs @ ldmul(lt, self.pe0)
615
+
616
+ # compute any callbacks into -> self._results
617
+ if self._step_callback is not None:
618
+ self._step_callback(t, self._pt, self._ham)
619
+
620
+ def _update_to_solved_dop(self, t):
621
+ """Update simulation consisting of a solved hamiltonian and a
622
+ density operator to time `t`.
623
+ """
624
+ self._t = t
625
+ evals, evecs = self._ham
626
+ lt = explt(evals, t - self.t0)
627
+ lvpvl = rdmul(ldmul(lt, self.pe0), lt.conj())
628
+ self._pt = evecs @ (lvpvl @ dag(evecs))
629
+
630
+ # compute any callbacks into -> self._results
631
+ if self._step_callback is not None:
632
+ self._step_callback(t, self._pt, self._ham)
633
+
634
+ def _update_to_integrate(self, t):
635
+ """Update simulation consisting of unsolved hamiltonian."""
636
+ self._stepper.integrate(t)
637
+
638
+ def update_to(self, t):
639
+ """Update the simulation to time ``t`` using relevant method.
640
+
641
+ Parameters
642
+ ----------
643
+ t : float
644
+ Time to update the evolution to.
645
+ """
646
+ if self._progbar and hasattr(self, "_stepper"):
647
+ with continuous_progbar(self.t, t) as pbar:
648
+ if self._int_step_callback is not None:
649
+
650
+ def solout(t, y):
651
+ int_stop_res = self._int_step_callback(t, y, self._ham)
652
+ pbar.cupdate(t)
653
+ return int_stop_res
654
+ else:
655
+
656
+ def solout(t, _):
657
+ pbar.cupdate(t)
658
+
659
+ self._stepper.set_solout(solout)
660
+ self._update_method(t)
661
+ else:
662
+ self._update_method(t)
663
+
664
+ def at_times(self, ts):
665
+ """Generator expression to yield state af list of times.
666
+
667
+ Parameters
668
+ ----------
669
+ ts : sequence of floats
670
+ Times at which to evolve to, then yield the state.
671
+
672
+ Yields
673
+ ------
674
+ pt : quantum state
675
+ Quantum state of evolution at next time in ``ts``.
676
+
677
+ Notes
678
+ -----
679
+ If integrating, currently any compute callbacks will be called at every
680
+ *integration* step, not just the times `ts` -- i.e. in general
681
+ len(Evolution.results) != len(ts) and if the adaptive step times are
682
+ needed they should be added as a callback, e.g.
683
+ ``compute['t'] = lambda t, _: return t``.
684
+ """
685
+ if self._progbar:
686
+ ts = progbar(ts)
687
+
688
+ for t in ts:
689
+ self._update_method(t)
690
+ yield self.pt
691
+
692
+ # Simulation properties ------------------------------------------------- #
693
+
694
+ @property
695
+ def t(self):
696
+ """float : Current time of simulation."""
697
+ return self._stepper.t if self._method == "integrate" else self._t
698
+
699
+ @property
700
+ def pt(self):
701
+ """quantum state : State of the system at the current time (t)."""
702
+ if self._method == "integrate":
703
+ return qarray(self._stepper.y.reshape(self._d, -1))
704
+ else:
705
+ return self._pt
706
+
707
+ @property
708
+ def results(self):
709
+ """list, or dict of lists, optional : Results of the compute
710
+ callback(s) for each time step.
711
+ """
712
+ return self._results
File without changes