1"A collection of evolver classes."
2
3import numpy as np
4
5class pybind11_object: pass
6
7def _set_threads(threads: int):
8 """
9 Sets the number of threads to be used during multithreading.
10
11 Parameters
12 ----------
13 threads : int
14 The number of threads to be used during mutlithreading
15
16 Warning
17 -------
18 The number of threads should be less than or equal to the number of
19 physical cores and you should not attempt to make use of hyperthreading
20 by using twice the number of physical cores. PySTE uses Eigen for
21 multithreading which provides the following warning:
22
23 Admonition
24 ----------
25 On most OS it is very important to limit the number of threads to
26 the number of physical cores, otherwise significant slowdowns are
27 expected
28
29 For more details see
30 https://eigen.tuxfamily.org/dox/TopicMultiThreading.html.
31
32 See Also
33 --------
34 :func:`get_threads()`
35 """
36 pass
37
38def _get_threads() -> int:
39 """
40 Gets the number of threads that will be used during multithreading.
41
42 Returns
43 -------
44 int
45 The number of threads that will be used during multithreading.
46
47 See Also
48 --------
49 :func:`set_threads()`
50 """
51 pass
52
[docs]
53class UnitaryEvolver(pybind11_object):
54 """
55 A base class for
56 :class:`DenseUnitaryEvolver`
57 and
58 :class:`SparseUnitaryEvolver`
59 with no implemented functionality.
60
61 The purpose of ``UnitaryEvolver`` is to allow for checks such as::
62
63 isinstance(evolver, evolvers.UnitaryEvolver)
64
65 See Also
66 --------
67 * :class:`DenseUnitaryEvolver`
68 * :class:`SparseUnitaryEvolver`
69
70
71 ---
72 """
73 pass
74
[docs]
75class DenseUnitaryEvolver(UnitaryEvolver):
76 r"""
77 A class to store the diagonalised drift and control Hamiltonians with dense
78 matrices and dynamic values of ``n_ctrl`` and ``dim``. On initialisation the
79 Hamiltonians are diagonalised and the eigenvectors and values stored as
80 dense matrices. This initial diagonalisation may be slow and takes
81 $O(\textrm{dim}^3)$
82 time for a
83 $\textrm{dim}\times \textrm{dim}$
84 Hamiltonian. However, it allows each step of the Suzuki-Trotter expansion
85 to be implimented in
86 $O(\textrm{dim}^2)$
87 time with matrix multiplication and only scalar exponentiation opposed to
88 matrix exponentiation which takes
89 $O(\textrm{dim}^3)$
90 time.
91
92 Note
93 ----
94 This class is a Python wrapper around the C++ struct:
95
96 .. code-block:: cpp
97
98 Suzuki_Trotter_Evolver::UnitaryEvolver<Dynamic, Dynamic, DMatrix<Dynamic, Dynamic>>
99
100 from
101 `Suzuki-Trotter-Evolver <https://github.com/Christopher-K-Long/Suzuki-Trotter-Evolver>`__.
102
103 Note
104 ----
105 Unlike :class:`DenseUnitaryEvolver_nctrl_dim`, :attr:`n_ctrl` and :attr:`dim`
106 dynamically determined at runtime. :class:`DenseUnitaryEvolver_nctrl_dim` is
107 typically more efficient as the values for :attr:`n_ctrl` and :attr:`dim`
108 are baked in at compile time.
109
110 See Also
111 --------
112 * :class:`DenseUnitaryEvolver_nctrl_dim`
113 * :class:`SparseUnitaryEvolver`
114
115
116 ---
117 """
118
119 n_ctrl: int = -1
120 r"""
121 The number of control Hamiltonians used to compile the C++ backend. A value
122 of ``-1`` implies the value is not precompiled in the C++. This was the
123 value at compile time while :attr:`length` is the value at runtime time.
124 """
125
126 dim: int = -1
127 r"""
128 The dimension of the vector space the Hamiltonians act upon used to compile
129 the C++ backend. A value of ``-1`` implies the value is not precompiled in
130 the C++.
131 """
132
133 dim_x_n_ctrl: int = -1
134 r"""
135 The dimension of rows in each control Hamiltonian multiplied by the
136 number of control Hamiltonians upon used to compile the C++ backend. This is
137 the number of rows for the ``control_hamiltonians`` argument for :meth:`__init__()`. A value
138 of ``-1`` implies the value is not precompiled in the C++.
139
140 Note
141 ----
142 This is a wrapper around the C++ member
143 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::dim_x_n_ctrl`.
144 """
145
146 length: int
147 r"""
148 The number of control Hamiltonians. This is the value at runtime while
149 :attr:`n_ctrl` was the value at compile time. These two values will be equal
150 unless ``n_ctrl==-1``. If ``n_ctrl==-1`` then `length` is the actual number
151 of control Hamiltonians.
152
153 Note
154 ----
155 This is a wrapper around the C++ member
156 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::length`.
157 """
158
159 d0: np.ndarray
160 r"""
161 The eigenvalues,
162 $\operatorname{diag}(D_0)$,
163 of the drift Hamiltonian:
164 $H_0=U_0D_0U_0^\dagger$.
165
166 Note
167 ----
168 This is a wrapper around the C++ member
169 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::d0`.
170
171 See Also
172 --------
173 * :attr:`ds`
174 * :attr:`u0`
175 * :attr:`u0_inverse`
176 """
177
178 ds: list[np.ndarray]
179 r"""
180 The eigenvalues,
181 $\left(\operatorname{diag}(D_i)\right)_{i=1}^{\textrm{length}}$,
182 of the control Hamiltonians:
183 $H_i=U_iD_iU_i^\dagger$
184 for all
185 $i\in\left[\textrm{length}\right]$.
186
187 Note
188 ----
189 This is a wrapper around the C++ member
190 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::ds`.
191
192 See Also
193 --------
194 * :attr:`d0`
195 * :attr:`us_individual`
196 * :attr:`us_inverse_individual`
197 * :attr:`hs`
198 """
199
200 u0: np.ndarray
201 r"""
202 The unitary transformation,
203 $U_0$,
204 that diagonalises the drift Hamiltonian:
205 $H_0=U_0D_0U_0^\dagger$.
206
207 Note
208 ----
209 This is a wrapper around the C++ member
210 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::u0`.
211
212 See Also
213 --------
214 * :attr:`us_individual`
215 * :attr:`d0`
216 * :attr:`u0_inverse`
217 """
218
219 u0_inverse: np.ndarray
220 r"""
221 The inverse of the unitary transformation,
222 $U_0^\dagger$,
223 that diagonalises the drift Hamiltonian:
224 $H_0=U_0D_0U_0^\dagger$.
225
226 Note
227 ----
228 This is a wrapper around the C++ member
229 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::u0_inverse`.
230
231 See Also
232 --------
233 * :attr:`us_inverse_individual`
234 * :attr:`u0`
235 * :attr:`d0`
236 """
237
238 us: list[np.ndarray]
239 r"""
240 The unitary transformations,
241 $(U_i^\dagger U_{i-1})_{i=1}^{\textrm{length}}$,
242 from the eigen basis of
243 $H_{i-1}$
244 to the eigen basis of
245 $H_i$.
246
247 Note
248 ----
249 This is a wrapper around the C++ member
250 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::us`.
251
252 See Also
253 --------
254 * :attr:`us_individual`
255 * :attr:`us_inverse_individual`
256 """
257
258 us_individual: list[np.ndarray]
259 r"""
260 The unitary transformations,
261 $\left(U_i\right)_{i=1}^{\textrm{length}}$,
262 that diagonalise the control Hamiltonians:
263 $H_i=U_iD_iU_i^\dagger$
264 for all
265 $i\in\left[\textrm{length}\right]$.
266
267 Note
268 ----
269 This is a wrapper around the C++ member
270 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::us_individual`.
271
272 See Also
273 --------
274 * :attr:`us_inverse_individual`
275 * :attr:`us`
276 * :attr:`ds`
277 * :attr:`hs`
278 """
279
280 us_inverse_individual: list[np.ndarray]
281 r"""
282 The inverse of the unitary transformations,
283 $(U_i^\dagger)_{i=1}^{\textrm{length}}$,
284 that diagonalise the control Hamiltonians:
285 $H_i=U_iD_iU_i^\dagger$
286 for all
287 $i\in\left[\textrm{length}\right]$.
288
289 Note
290 ----
291 This is a wrapper around the C++ member
292 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::us_inverse_individual`.
293
294 See Also
295 --------
296 * :attr:`us_individual`
297 * :attr:`us`
298 * :attr:`ds`
299 * :attr:`hs`
300 """
301
302 hs: list[np.ndarray]
303 r"""
304 The control Hamiltonians:
305 $H_i$
306 for all
307 $i\in\left[\textrm{length}\right]$.
308
309 Note
310 ----
311 This is a wrapper around the C++ member
312 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::hs`.
313
314 See Also
315 --------
316 * :attr:`us_individual`
317 * :attr:`us_inverse_individual`
318 * :attr:`ds`
319 """
320
321 u0_inverse_u_last: list[np.ndarray]
322 r"""
323 The unitary transformation,
324 $U_0^\dagger U_{\textrm{length}}$,
325 from the eigen basis of
326 $H_{\textrm{length}}$
327 to the eigen basis of
328 $H_0$.
329
330 Note
331 ----
332 This is a wrapper around the C++ member
333 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::u0_inverse_u_last`.
334
335 See Also
336 --------
337 * :attr:`us_individual`
338 * :attr:`us`
339 * :attr:`u0_inverse`
340 """
341
[docs]
342 def __init__(self,
343 drift_hamiltonian: np.ndarray[np.complex128],
344 control_hamiltonians: np.ndarray[np.complex128]):
345 r"""
346 Initialises a new unitary evolver with the Hamiltonian
347 $$
348 H(t)=H_0+\sum_{j=1}^{\textrm{length}}a_j(t)H_j,
349 $$
350 where $H_0$ is the drift Hamiltonian and $H_j$ are the control
351 Hamiltonians modulated by control amplitudes $a_j(t)$ which need
352 not be specified during initialisation.
353
354 Parameters
355 ----------
356 drift_hamiltonian : NDArray[Shape[runtime_dim, runtime_dim], complex128]
357 The drift Hamiltonian.
358 control_hamiltonians : NDArray[Shape[runtime_dim * :attr:`length`, runtime_dim], complex128]
359 The control Hamiltonians.
360 """
361 pass
[docs]
362 def propagate(self,
363 ctrl_amp: np.ndarray[np.complex128],
364 state: np.ndarray[np.complex128],
365 dt: float
366 ) -> np.ndarray[np.complex128]:
367 r"""
368 Propagates the state vector using the first-order Suzuki-Trotter
369 expansion. More precisely, a state vector,
370 $\psi(0)$,
371 is evolved under the differential equation
372 $$
373 \dot\psi=-iH\psi
374 $$
375 using the first-order Suzuki-Trotter expansion:
376 $$
377 \begin{align}
378 \psi(N\Delta t)&=\prod_{i=1}^N\prod_{j=0}^{\textrm{length}}
379 e^{-ia_{ij}H_j\Delta t}\psi(0)+\mathcal E\\
380 &=\prod_{i=1}^N\prod_{j=0}^{\textrm{length}}
381 U_je^{-ia_{ij}D_j\Delta t}U_j^\dagger\psi(0)+\mathcal E.
382 \end{align}
383 $$
384 where
385 $a_{nj}\coloneqq a(n\Delta t)$,
386 we set
387 $a_{n0}=1$
388 for notational ease, and the additive error
389 $\mathcal E$
390 is
391 $$
392 \begin{align}
393 \mathcal E&=\mathcal O\left(
394 \Delta t^2\left[\sum_{i=1}^N\sum_{j=1}^{\textrm{length}}\dot a_{ij}
395 \norm{H_j}
396 +\sum_{i=1}^N\sum_{j,k=0}^{\textrm{length}}a_{ij}a_{ik}
397 \norm{[H_j,H_k]}\right]
398 \right)\\
399 &=\mathcal O\left(
400 N\Delta t^2\textrm{length}\left[\omega E+\alpha^2+E^2\right]
401 \right)
402 \end{align}
403 $$
404 where $\dot a_{nj}\coloneqq\dot a_j(n\Delta t)$ and
405 $$
406 \begin{align}
407 \omega&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
408 j\in\left[1,\textrm{length}\right]}}\left|\dot a_{ij}\right|,\\
409 \alpha&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
410 j\in\left[0,\textrm{length}\right]}}\left|a_{ij}\right|,\\
411 E&\coloneqq\max_{j\in\left[0,\textrm{length}\right]}\norm{H_j}.
412 \end{align}
413 $$
414 Note the error is quadratic in $\Delta t$ but linear in $N$.
415 We can also view this as being linear in $\Delta t$ and linear in
416 total evolution time $N\Delta t$. Additionally, by Nyquist's theorem
417 this asymptotic error scaling will not be achieved until the time step
418 $\Delta t$ is smaller than $\frac{1}{2\Omega}$ where
419 $\Omega$ is the largest energy or frequency in the system.
420
421 Parameters
422 ----------
423 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
424 $\left(a_{ij}\right)$ The control amplitudes at each time step
425 expressed as an $N\times\textrm{length}$ matrix where the element
426 $a_{ij}$ corresponds to the control amplitude of the $j$th control
427 Hamiltonian at the $i$th time step.
428 state : NDArray[Shape[runtime_dim], complex128]
429 $\left[\psi(0)\right]$ The state vector to propagate.
430 dt : float
431 ($\Delta t$) The time step to propagate by.
432
433 Returns
434 -------
435 NDArray[Shape[runtime_dim], complex128]
436 The propagated state vector, $\psi(N\Delta t)$.
437
438 Note
439 ----
440 This function is a wrapper around the C++ function
441 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::propagate()`.
442
443 See Also
444 --------
445 * :meth:`propagate_collection()`
446 * :meth:`propagate_all()`
447 """
448 pass
[docs]
449 def propagate_collection(self,
450 ctrl_amp: np.ndarray[np.complex128],
451 states: np.ndarray[np.complex128],
452 dt: float
453 ) -> np.ndarray[np.complex128]:
454 r"""
455 Propagates a collection of state vectors using the first-order
456 Suzuki-Trotter expansion. More precisely, a collection of state vectors,
457 $\left(\psi_k(0)\right)_{k}$,
458 are evolved under the differential equation
459 $$
460 \dot\psi_k=-iH\psi_k
461 $$
462 using the first-order Suzuki-Trotter expansion:
463 $$
464 \begin{align}
465 \psi_k(N\Delta t)&=\prod_{i=1}^N\prod_{j=0}^{\textrm{length}}
466 e^{-ia_{ij}H_j\Delta t}\psi_k(0)+\mathcal E\\
467 &=\prod_{i=1}^N\prod_{j=0}^{\textrm{length}}
468 U_je^{-ia_{ij}D_j\Delta t}U_j^\dagger\psi_k(0)+\mathcal E.
469 \end{align}
470 $$
471 where
472 $a_{nj}\coloneqq a(n\Delta t)$,
473 we set
474 $a_{n0}=1$
475 for notational ease, and the addative error
476 $\mathcal E$
477 is
478 $$
479 \begin{align}
480 \mathcal E&=\mathcal O\left(
481 \Delta t^2\left[\sum_{i=1}^N\sum_{j=1}^{\textrm{length}}\dot a_{ij}
482 \norm{H_j}
483 +\sum_{i=1}^N\sum_{j,k=0}^{\textrm{length}}a_{ij}a_{ik}
484 \norm{[H_j,H_k]}\right]
485 \right)\\
486 &=\mathcal O\left(
487 N\Delta t^2\textrm{length}\left[\omega E+\alpha^2+E^2\right]
488 \right)
489 \end{align}
490 $$
491 where $\dot a_{nj}\coloneqq\dot a_j(n\Delta t)$ and
492 $$
493 \begin{align}
494 \omega&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
495 j\in\left[1,\textrm{length}\right]}}\left|\dot a_{ij}\right|,\\
496 \alpha&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
497 j\in\left[0,\textrm{length}\right]}}\left|a_{ij}\right|,\\
498 E&\coloneqq\max_{j\in\left[0,\textrm{length}\right]}\norm{H_j}.
499 \end{align}
500 $$
501 Note the error is quadratic in $\Delta t$ but linear in $N$.
502 We can also view this as being linear in $\Delta t$ and linear in
503 total evolution time $N\Delta t$. Additionally, by Nyquist's theorem
504 this asymptotic error scaling will not be achieved until the time step
505 $\Delta t$ is smaller than $\frac{1}{2\Omega}$ where
506 $\Omega$ is the largest energy or frequency in the system.
507
508 Parameters
509 ----------
510 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
511 $\left(a_{ij}\right)$ The control amplitudes at each time step
512 expressed as an $N\times\textrm{length}$ matrix where the element
513 $a_{ij}$ corresponds to the control amplitude of the $j$th control
514 Hamiltonian at the $i$th time step.
515 states : NDArray[Shape[runtime_dim, number_of_states], complex128]
516 $\left[\left(\psi(0)\right)_{k}\right]$ A collection of state
517 vectors to propagate expressed as a matrix with each column
518 corresponding to a state vector.
519 dt : float
520 ($\Delta t$) The time step to propagate by.
521
522 Returns
523 -------
524 NDArray[Shape[runtime_dim, number_of_states], complex128]
525 The propagated state vectors, $\left(\psi_k(N\Delta t)\right)_k$.
526
527 Note
528 ----
529 This function is a wrapper around the C++ function
530 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::propagate_collection()`.
531
532 See Also
533 --------
534 * :meth:`propagate()`
535 * :meth:`propagate_all()`
536 """
537 pass
[docs]
538 def propagate_all(self,
539 ctrl_amp: np.ndarray[np.complex128],
540 state: np.ndarray[np.complex128],
541 dt: float
542 ) -> np.ndarray[np.complex128]:
543 r"""
544 Propagates the state vector using the first-order Suzuki-Trotter
545 expansion and returns the resulting state vector at every time step.
546 More precisely, a state vector,
547 $\psi(0)$,
548 is evolved under the differential equation
549 $$
550 \dot\psi=-iH\psi
551 $$
552 using the first-order Suzuki-Trotter expansion:
553 $$
554 \begin{align}
555 \psi(n\Delta t)&=\prod_{i=1}^n\prod_{j=0}^{\textrm{length}}
556 e^{-ia_{ij}H_j\Delta t}\psi(0)+\mathcal E
557 \quad\forall n\in\left[0, N\right]\\
558 &=\prod_{i=1}^n\prod_{j=0}^{\textrm{length}}
559 U_je^{-ia_{ij}D_j\Delta t}U_j^\dagger\psi(0)+\mathcal E
560 \quad\forall n\in\left[0, N\right].
561 \end{align}
562 $$
563 where
564 $a_{nj}\coloneqq a(n\Delta t)$,
565 we set
566 $a_{n0}=1$
567 for notational ease, and the additive error
568 $\mathcal E$
569 is
570 $$
571 \begin{align}
572 \mathcal E&=\mathcal O\left(
573 \Delta t^2\left[\sum_{i=1}^N\sum_{j=1}^{\textrm{length}}\dot a_{ij}
574 \norm{H_j}
575 +\sum_{i=1}^N\sum_{j,k=0}^{\textrm{length}}a_{ij}a_{ik}
576 \norm{[H_j,H_k]}\right]
577 \right)\\
578 &=\mathcal O\left(
579 N\Delta t^2\textrm{length}\left[\omega E+\alpha^2+E^2\right]
580 \right)
581 \end{align}
582 $$
583 where $\dot a_{nj}\coloneqq\dot a_j(n\Delta t)$ and
584 $$
585 \begin{align}
586 \omega&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
587 j\in\left[1,\textrm{length}\right]}}\left|\dot a_{ij}\right|,\\
588 \alpha&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
589 j\in\left[0,\textrm{length}\right]}}\left|a_{ij}\right|,\\
590 E&\coloneqq\max_{j\in\left[0,\textrm{length}\right]}\norm{H_j}.
591 \end{align}
592 $$
593 Note the error is quadratic in $\Delta t$ but linear in $N$.
594 We can also view this as being linear in $\Delta t$ and linear in
595 total evolution time $N\Delta t$. Additionally, by Nyquist's theorem
596 this asymptotic error scaling will not be achieved until the time step
597 $\Delta t$ is smaller than $\frac{1}{2\Omega}$ where
598 $\Omega$ is the largest energy or frequency in the system.
599
600 Parameters
601 ----------
602 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
603 $\left(a_{ij}\right)$ The control amplitudes at each time step
604 expressed as an $N\times\textrm{length}$ matrix where the element
605 $a_{ij}$ corresponds to the control amplitude of the $j$th control
606 Hamiltonian at the $i$th time step.
607 state : NDArray[Shape[runtime_dim], complex128]
608 $\left[\psi(0)\right]$ The state vector to propagate.
609 dt : float
610 ($\Delta t$) The time step to propagate by.
611
612 Returns
613 -------
614 NDArray[Shape[runtime_dim, time_steps + 1], complex128]
615 The propagated state vector at each time step,
616 $\left(\psi(n\Delta t)\right)_{n=0}^N$.
617
618 Note
619 ----
620 This function is a wrapper around the C++ function
621 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::propagate_all()`.
622
623 See Also
624 --------
625 * :meth:`propagate()`
626 * :meth:`propagate_collection()`
627 """
628 pass
[docs]
629 def evolved_expectation_value(self,
630 ctrl_amp: np.ndarray[np.complex128],
631 state: np.ndarray[np.complex128],
632 dt: float,
633 observable: np.ndarray[np.complex128]
634 ) -> complex:
635 r"""
636 Calculates the expectation value with respect to an observable of an
637 evolved state vector evolved under a control Hamiltonian modulated by
638 the control amplitudes. The integration is performed using
639 :meth:`propagate()`.
640
641 Parameters
642 ----------
643 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
644 $\left(a_{ij}\right)$ The control amplitudes at each time step
645 expressed as an $N\times\textrm{length}$ matrix where the element
646 $a_{ij}$ corresponds to the control amplitude of the $j$th control
647 Hamiltonian at the $i$th time step.
648 state : NDArray[Shape[runtime_dim], complex128]
649 $\left[\psi(0)\right]$ The state vector to propagate.
650 dt : float
651 ($\Delta t$) The time step to propagate by.
652 observable : NDArray[Shape[runtime_dim, runtime_dim], complex128]
653 $(\hat O)$ The observable to calculate the expectation value of.
654
655 Returns
656 -------
657 complex
658 The expectation value of the observable, $\langle\hat O\rangle
659 \equiv\psi^\dagger(N\Delta t)\hat O\psi(N\Delta t)$.
660
661 Note
662 ----
663 This function is a wrapper around the C++ function
664 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::evolved_expectation_value()`.
665
666 See Also
667 --------
668 * :meth:`evolved_expectation_value_all()`
669 """
670 pass
[docs]
671 def evolved_expectation_value_all(self,
672 ctrl_amp: np.ndarray[np.complex128],
673 state: np.ndarray[np.complex128],
674 dt: float,
675 observable: np.ndarray[np.complex128]
676 ) -> np.ndarray[np.complex128]:
677 r"""
678 Calculates the expectation values with respect to an observable of a
679 time series of state vectors evolved under a control Hamiltonian
680 modulated by the control amplitudes. The integration is performed using
681 :meth:`propagate_all()`.
682
683 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
684 $\left(a_{ij}\right)$ The control amplitudes at each time step
685 expressed as an $N\times\textrm{length}$ matrix where the element
686 $a_{ij}$ corresponds to the control amplitude of the $j$th control
687 Hamiltonian at the $i$th time step.
688 state : NDArray[Shape[runtime_dim], complex128]
689 $\left[\psi(0)\right]$ The state vector to propagate.
690 dt : float
691 ($\Delta t$) The time step to propagate by.
692 observable : NDArray[Shape[runtime_dim, runtime_dim], complex128]
693 $(\hat O)$ The observable to calculate the expectation value of.
694
695 Returns
696 -------
697 NDArray[Shape[time_steps + 1], complex128]
698 The expectation value of the observable,
699 $\left(\psi^\dagger(n\Delta t)\hat O\psi(N\Delta t)\right)_{n=0}^N$.
700
701 Note
702 ----
703 This function is a wrapper around the C++ function
704 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::evolved_expectation_value_all()`.
705
706 See Also
707 --------
708 * :meth:`evolved_expectation_value()`
709 """
710 pass
[docs]
711 def switching_function(self,
712 ctrl_amp: np.ndarray[np.complex128],
713 state: np.ndarray[np.complex128],
714 dt: float,
715 cost: np.ndarray[np.complex128]
716 ) -> tuple[complex, np.ndarray[np.float64]]:
717 r"""
718 Calculates the switching function for a Mayer problem with an
719 expectation value as the cost function. More precisely if the cost
720 function is
721 $$
722 J\left[\vec a(t)\right]\coloneqq\langle\hat O\rangle
723 \equiv\psi^\dagger[\vec a(t);T]
724 \hat O\psi[\vec a(t);T],
725 $$
726 where $T=N\Delta t$, then the switching function is
727 $$
728 \phi_j(t)\coloneqq\frac{\delta J}{\delta a_j(t)}
729 =2\operatorname{Im}\left(\psi^\dagger[\vec a(t);T]
730 \hat OU(t\to T)H_j\psi[\vec a(t);t]\right).
731 $$
732 using the first-order Suzuki-Trotter expansion we can express the
733 switching function as
734 $$
735 \begin{align}
736 &\phi_j(n\Delta t)=\frac{1}{\Delta t}\pdv{J}{a_{nj}}\\
737 &=\!2\operatorname{Im}\!\left(\psi^\dagger(T)
738 \hat O\!\!\left[\prod_{i>n}^N\prod_{k=1}^{\textrm{length}}
739 e^{-ia_{ik}H_k\Delta t}\right]\!\!\!
740 \left[\prod_{k=j}^{\textrm{length}}
741 e^{-ia_{nk}H_k\Delta t}\right]\!H_j\!\!
742 \left[\prod_{k=0}^{j-1}
743 e^{-ia_{nk}H_k\Delta t}\right]
744 \!\psi(\left[n-1\right]\Delta t)\right),
745 \end{align}
746 $$
747 where for numerical efficiency we replace
748 $e^{-ia_{ik}H_k\Delta t}$
749 with
750 $U_ke^{-ia_{ik}D_k\Delta t}U_k^\dagger$
751 as in :meth:`propagate()`.
752
753 Parameters
754 ----------
755 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
756 $\left(a_{ij}\right)$ The control amplitudes at each time step
757 expressed as an $N\times\textrm{length}$ matrix where the element
758 $a_{ij}$ corresponds to the control amplitude of the $j$th control
759 Hamiltonian at the $i$th time step.
760 state : NDArray[Shape[runtime_dim], complex128]
761 $\left[\psi(0)\right]$ The initial state vector.
762 dt : float
763 ($\Delta t$) The time step.
764 cost : NDArray[Shape[runtime_dim, runtime_dim], complex128]
765 $(\hat O)$ The observable to calculate the expectation value of.
766
767 Returns
768 -------
769 tuple[complex, NDArray[Shape[time_steps, :attr:`length`], float64]]
770 The expectation value, $\psi^\dagger(T)\hat O\psi(T)$, and
771 the switching function,
772 $\phi_j(n\Delta t)$
773 for all
774 $j\in\left[1,\textrm{length}\right]$
775 and
776 $n\in\left[1,N\right]$.
777
778 Note
779 ----
780 This function is a wrapper around the C++ function
781 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::switching_function()`.
782 """
783 pass
784
[docs]
785class SparseUnitaryEvolver(UnitaryEvolver):
786 r"""
787 A class to store the diagonalised drift and control Hamiltonians with sparse
788 matrices and dynamic values of ``n_ctrl`` and ``dim``. On initialisation the
789 Hamiltonians are diagonalised and the eigenvectors and values stored as
790 sparse and dense matrices, respectively. This initial diagonalisation may be
791 slow and takes
792 $O(\textrm{dim}^3)$
793 time for a
794 $\textrm{dim}\times \textrm{dim}$
795 Hamiltonian. However, it allows each step of the Suzuki-Trotter expansion
796 to be implimented with sparse matrix multiplication and only scalar
797 exponentiation opposed to matrix exponentiation.
798
799 Note
800 ----
801 This class is a Python wrapper around the C++ struct:
802
803 .. code-block:: cpp
804
805 Suzuki_Trotter_Evolver::UnitaryEvolver<Dynamic, Dynamic, SMatrix<Dynamic, Dynamic>>
806
807 from
808 `Suzuki-Trotter-Evolver <https://github.com/Christopher-K-Long/Suzuki-Trotter-Evolver>`__.
809
810 Note
811 ----
812 Unlike :class:`SparseUnitaryEvolver_nctrl_dim`, :attr:`n_ctrl` and
813 :attr:`dim` dynamically determined at runtime.
814 :class:`SparseUnitaryEvolver_nctrl_dim` is typically more efficient as the
815 values for :attr:`n_ctrl` and :attr:`dim` are baked in at compile time.
816
817 See Also
818 --------
819 * :class:`SparseUnitaryEvolver_nctrl_dim`
820 * :class:`DenseUnitaryEvolver`
821
822
823 ---
824 """
825
826 n_ctrl: int = -1
827 r"""
828 The number of control Hamiltonians used to compile the C++ backend. A value
829 of ``-1`` implies the value is not precompiled in the C++. This was the value
830 at compile time while :attr:`length` is the value at runtime time.
831 """
832
833 dim: int = -1
834 r"""
835 The dimension of the vector space the Hamiltonians act upon used to compile
836 the C++ backend. A value of ``-1`` implies the value is not precompiled in
837 the C++.
838 """
839
840 dim_x_n_ctrl: int = -1
841 r"""
842 The dimension of rows in each control Hamiltonian multiplied by the
843 number of control Hamiltonians upon used to compile the C++ backend. This is
844 the number of rows for the ``control_hamiltonians`` argument for
845 :meth:`__init__()`. A value of ``-1`` implies the value is not precompiled
846 in the C++.
847
848 Note
849 ----
850 This is a wrapper around the C++ member
851 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::dim_x_n_ctrl`.
852 """
853
854 length: int
855 r"""
856 The number of control Hamiltonians. This is the value at runtime while
857 :attr:`n_ctrl` was the value at compile time. These two values will be equal
858 unless ``n_ctrl==-1``. If ``n_ctrl==-1`` then `length` is the actual number
859 of control Hamiltonians.
860
861 Note
862 ----
863 This is a wrapper around the C++ member
864 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::length`.
865 """
866
867 d0: np.ndarray
868 r"""
869 The eigenvalues,
870 $\operatorname{diag}(D_0)$,
871 of the drift Hamiltonian:
872 $H_0=U_0D_0U_0^\dagger$.
873
874 Note
875 ----
876 This is a wrapper around the C++ member
877 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::d0`.
878
879 See Also
880 --------
881 * :attr:`ds`
882 * :attr:`u0`
883 * :attr:`u0_inverse`
884 """
885
886 ds: list[np.ndarray]
887 r"""
888 The eigenvalues,
889 $\left(\operatorname{diag}(D_i)\right)_{i=1}^{\textrm{length}}$,
890 of the control Hamiltonians:
891 $H_i=U_iD_iU_i^\dagger$
892 for all
893 $i\in\left[\textrm{length}\right]$.
894
895 Note
896 ----
897 This is a wrapper around the C++ member
898 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::ds`.
899
900 See Also
901 --------
902 * :attr:`d0`
903 * :attr:`us_individual`
904 * :attr:`us_inverse_individual`
905 * :attr:`hs`
906 """
907
908 u0: np.ndarray
909 r"""
910 The unitary transformation,
911 $U_0$,
912 that diagonalises the drift Hamiltonian:
913 $H_0=U_0D_0U_0^\dagger$.
914
915 Note
916 ----
917 This is a wrapper around the C++ member
918 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::u0`.
919
920 See Also
921 --------
922 * :attr:`us_individual`
923 * :attr:`d0`
924 * :attr:`u0_inverse`
925 """
926
927 u0_inverse: np.ndarray
928 r"""
929 The inverse of the unitary transformation,
930 $U_0^\dagger$,
931 that diagonalises the drift Hamiltonian:
932 $H_0=U_0D_0U_0^\dagger$.
933
934 Note
935 ----
936 This is a wrapper around the C++ member
937 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::u0_inverse`.
938
939 See Also
940 --------
941 * :attr:`us_inverse_individual`
942 * :attr:`u0`
943 * :attr:`d0`
944 """
945
946 us: list[np.ndarray]
947 r"""
948 The unitary transformations,
949 $(U_i^\dagger U_{i-1})_{i=1}^{\textrm{length}}$,
950 from the eigen basis of
951 $H_{i-1}$
952 to the eigen basis of
953 $H_i$.
954
955 Note
956 ----
957 This is a wrapper around the C++ member
958 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::us`.
959
960 See Also
961 --------
962 * :attr:`us_individual`
963 * :attr:`us_inverse_individual`
964 """
965
966 us_individual: list[np.ndarray]
967 r"""
968 The unitary transformations,
969 $\left(U_i\right)_{i=1}^{\textrm{length}}$,
970 that diagonalise the control Hamiltonians:
971 $H_i=U_iD_iU_i^\dagger$
972 for all
973 $i\in\left[\textrm{length}\right]$.
974
975 Note
976 ----
977 This is a wrapper around the C++ member
978 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::us_individual`.
979
980 See Also
981 --------
982 * :attr:`us_inverse_individual`
983 * :attr:`us`
984 * :attr:`ds`
985 * :attr:`hs`
986 """
987
988 us_inverse_individual: list[np.ndarray]
989 r"""
990 The inverse of the unitary transformations,
991 $(U_i^\dagger)_{i=1}^{\textrm{length}}$,
992 that diagonalise the control Hamiltonians:
993 $H_i=U_iD_iU_i^\dagger$
994 for all
995 $i\in\left[\textrm{length}\right]$.
996
997 Note
998 ----
999 This is a wrapper around the C++ member
1000 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::us_inverse_individual`.
1001
1002 See Also
1003 --------
1004 * :attr:`us_individual`
1005 * :attr:`us`
1006 * :attr:`ds`
1007 * :attr:`hs`
1008 """
1009
1010 hs: list[np.ndarray]
1011 r"""
1012 The control Hamiltonians:
1013 $H_i$
1014 for all
1015 $i\in\left[\textrm{length}\right]$.
1016
1017 Note
1018 ----
1019 This is a wrapper around the C++ member
1020 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::hs`.
1021
1022 See Also
1023 --------
1024 * :attr:`us_individual`
1025 * :attr:`us_inverse_individual`
1026 * :attr:`ds`
1027 """
1028
1029 u0_inverse_u_last: list[np.ndarray]
1030 r"""
1031 The unitary transformation,
1032 $U_0^\dagger U_{\textrm{length}}$,
1033 from the eigen basis of
1034 $H_{\textrm{length}}$
1035 to the eigen basis of
1036 $H_0$.
1037
1038 Note
1039 ----
1040 This is a wrapper around the C++ member
1041 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::u0_inverse_u_last`.
1042
1043 See Also
1044 --------
1045 * :attr:`us_individual`
1046 * :attr:`us`
1047 * :attr:`u0_inverse`
1048 """
1049
[docs]
1050 def __init__(self,
1051 drift_hamiltonian: np.ndarray[np.complex128],
1052 control_hamiltonians: np.ndarray[np.complex128]):
1053 r"""
1054 Initialises a new unitary evolver with the Hamiltonian
1055 $$
1056 H(t)=H_0+\sum_{j=1}^{\textrm{length}}a_j(t)H_j,
1057 $$
1058 where $H_0$ is the drift Hamiltonian and $H_j$ are the control
1059 Hamiltonians modulated by control amplitudes $a_j(t)$ which need
1060 not be specified during initialisation.
1061
1062 Parameters
1063 ----------
1064 drift_hamiltonian : NDArray[Shape[runtime_dim, runtime_dim], complex128]
1065 The drift Hamiltonian.
1066 control_hamiltonians : NDArray[Shape[runtime_dim * :attr:`length`, runtime_dim], complex128]
1067 The control Hamiltonians.
1068 """
1069 pass
[docs]
1070 def propagate(self,
1071 ctrl_amp: np.ndarray[np.complex128],
1072 state: np.ndarray[np.complex128],
1073 dt: float
1074 ) -> np.ndarray[np.complex128]:
1075 r"""
1076 Propagates the state vector using the first-order Suzuki-Trotter
1077 expansion. More precisely, a state vector,
1078 $\psi(0)$,
1079 is evolved under the differential equation
1080 $$
1081 \dot\psi=-iH\psi
1082 $$
1083 using the first-order Suzuki-Trotter expansion:
1084 $$
1085 \begin{align}
1086 \psi(N\Delta t)&=\prod_{i=1}^N\prod_{j=0}^{\textrm{length}}
1087 e^{-ia_{ij}H_j\Delta t}\psi(0)+\mathcal E\\
1088 &=\prod_{i=1}^N\prod_{j=0}^{\textrm{length}}
1089 U_je^{-ia_{ij}D_j\Delta t}U_j^\dagger\psi(0)+\mathcal E.
1090 \end{align}
1091 $$
1092 where
1093 $a_{nj}\coloneqq a(n\Delta t)$,
1094 we set
1095 $a_{n0}=1$
1096 for notational ease, and the additive error
1097 $\mathcal E$
1098 is
1099 $$
1100 \begin{align}
1101 \mathcal E&=\mathcal O\left(
1102 \Delta t^2\left[\sum_{i=1}^N\sum_{j=1}^{\textrm{length}}\dot a_{ij}
1103 \norm{H_j}
1104 +\sum_{i=1}^N\sum_{j,k=0}^{\textrm{length}}a_{ij}a_{ik}
1105 \norm{[H_j,H_k]}\right]
1106 \right)\\
1107 &=\mathcal O\left(
1108 N\Delta t^2\textrm{length}\left[\omega E+\alpha^2+E^2\right]
1109 \right)
1110 \end{align}
1111 $$
1112 where $\dot a_{nj}\coloneqq\dot a_j(n\Delta t)$ and
1113 $$
1114 \begin{align}
1115 \omega&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
1116 j\in\left[1,\textrm{length}\right]}}\left|\dot a_{ij}\right|,\\
1117 \alpha&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
1118 j\in\left[0,\textrm{length}\right]}}\left|a_{ij}\right|,\\
1119 E&\coloneqq\max_{j\in\left[0,\textrm{length}\right]}\norm{H_j}.
1120 \end{align}
1121 $$
1122 Note the error is quadratic in $\Delta t$ but linear in $N$.
1123 We can also view this as being linear in $\Delta t$ and linear in
1124 total evolution time $N\Delta t$. Additionally, by Nyquist's theorem
1125 this asymptotic error scaling will not be achieved until the time step
1126 $\Delta t$ is smaller than $\frac{1}{2\Omega}$ where
1127 $\Omega$ is the largest energy or frequency in the system.
1128
1129 Parameters
1130 ----------
1131 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
1132 $\left(a_{ij}\right)$ The control amplitudes at each time step
1133 expressed as an $N\times\textrm{length}$ matrix where the element
1134 $a_{ij}$ corresponds to the control amplitude of the $j$th control
1135 Hamiltonian at the $i$th time step.
1136 state : NDArray[Shape[runtime_dim], complex128]
1137 $\left[\psi(0)\right]$ The state vector to propagate.
1138 dt : float
1139 ($\Delta t$) The time step to propagate by.
1140
1141 Returns
1142 -------
1143 NDArray[Shape[runtime_dim], complex128]
1144 The propagated state vector, $\psi(N\Delta t)$.
1145
1146 Note
1147 ----
1148 This function is a wrapper around the C++ function
1149 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::propagate()`.
1150
1151 See Also
1152 --------
1153 * :meth:`propagate_collection()`
1154 * :meth:`propagate_all()`
1155 """
1156 pass
[docs]
1157 def propagate_collection(self,
1158 ctrl_amp: np.ndarray[np.complex128],
1159 states: np.ndarray[np.complex128],
1160 dt: float
1161 ) -> np.ndarray[np.complex128]:
1162 r"""
1163 Propagates a collection of state vectors using the first-order
1164 Suzuki-Trotter expansion. More precisely, a collection of state vectors,
1165 $\left(\psi_k(0)\right)_{k}$,
1166 are evolved under the differential equation
1167 $$
1168 \dot\psi_k=-iH\psi_k
1169 $$
1170 using the first-order Suzuki-Trotter expansion:
1171 $$
1172 \begin{align}
1173 \psi_k(N\Delta t)&=\prod_{i=1}^N\prod_{j=0}^{\textrm{length}}
1174 e^{-ia_{ij}H_j\Delta t}\psi_k(0)+\mathcal E\\
1175 &=\prod_{i=1}^N\prod_{j=0}^{\textrm{length}}
1176 U_je^{-ia_{ij}D_j\Delta t}U_j^\dagger\psi_k(0)+\mathcal E.
1177 \end{align}
1178 $$
1179 where
1180 $a_{nj}\coloneqq a(n\Delta t)$,
1181 we set
1182 $a_{n0}=1$
1183 for notational ease, and the addative error
1184 $\mathcal E$
1185 is
1186 $$
1187 \begin{align}
1188 \mathcal E&=\mathcal O\left(
1189 \Delta t^2\left[\sum_{i=1}^N\sum_{j=1}^{\textrm{length}}\dot a_{ij}
1190 \norm{H_j}
1191 +\sum_{i=1}^N\sum_{j,k=0}^{\textrm{length}}a_{ij}a_{ik}
1192 \norm{[H_j,H_k]}\right]
1193 \right)\\
1194 &=\mathcal O\left(
1195 N\Delta t^2\textrm{length}\left[\omega E+\alpha^2+E^2\right]
1196 \right)
1197 \end{align}
1198 $$
1199 where $\dot a_{nj}\coloneqq\dot a_j(n\Delta t)$ and
1200 $$
1201 \begin{align}
1202 \omega&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
1203 j\in\left[1,\textrm{length}\right]}}\left|\dot a_{ij}\right|,\\
1204 \alpha&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
1205 j\in\left[0,\textrm{length}\right]}}\left|a_{ij}\right|,\\
1206 E&\coloneqq\max_{j\in\left[0,\textrm{length}\right]}\norm{H_j}.
1207 \end{align}
1208 $$
1209 Note the error is quadratic in $\Delta t$ but linear in $N$.
1210 We can also view this as being linear in $\Delta t$ and linear in
1211 total evolution time $N\Delta t$. Additionally, by Nyquist's theorem
1212 this asymptotic error scaling will not be achieved until the time step
1213 $\Delta t$ is smaller than $\frac{1}{2\Omega}$ where
1214 $\Omega$ is the largest energy or frequency in the system.
1215
1216 Parameters
1217 ----------
1218 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
1219 $\left(a_{ij}\right)$ The control amplitudes at each time step
1220 expressed as an $N\times\textrm{length}$ matrix where the element
1221 $a_{ij}$ corresponds to the control amplitude of the $j$th control
1222 Hamiltonian at the $i$th time step.
1223 states : NDArray[Shape[runtime_dim, number_of_states], complex128]
1224 $\left[\left(\psi(0)\right)_{k}\right]$ A collection of state
1225 vectors to propagate expressed as a matrix with each column
1226 corresponding to a state vector.
1227 dt : float
1228 ($\Delta t$) The time step to propagate by.
1229
1230 Returns
1231 -------
1232 NDArray[Shape[runtime_dim, number_of_states], complex128]
1233 The propagated state vectors, $\left(\psi_k(N\Delta t)\right)_k$.
1234
1235 Note
1236 ----
1237 This function is a wrapper around the C++ function
1238 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::propagate_collection()`.
1239
1240 See Also
1241 --------
1242 * :meth:`propagate()`
1243 * :meth:`propagate_all()`
1244 """
1245 pass
[docs]
1246 def propagate_all(self,
1247 ctrl_amp: np.ndarray[np.complex128],
1248 state: np.ndarray[np.complex128],
1249 dt: float
1250 ) -> np.ndarray[np.complex128]:
1251 r"""
1252 Propagates the state vector using the first-order Suzuki-Trotter
1253 expansion and returns the resulting state vector at every time step.
1254 More precisely, a state vector,
1255 $\psi(0)$,
1256 is evolved under the differential equation
1257 $$
1258 \dot\psi=-iH\psi
1259 $$
1260 using the first-order Suzuki-Trotter expansion:
1261 $$
1262 \begin{align}
1263 \psi(n\Delta t)&=\prod_{i=1}^n\prod_{j=0}^{\textrm{length}}
1264 e^{-ia_{ij}H_j\Delta t}\psi(0)+\mathcal E
1265 \quad\forall n\in\left[0, N\right]\\
1266 &=\prod_{i=1}^n\prod_{j=0}^{\textrm{length}}
1267 U_je^{-ia_{ij}D_j\Delta t}U_j^\dagger\psi(0)+\mathcal E
1268 \quad\forall n\in\left[0, N\right].
1269 \end{align}
1270 $$
1271 where
1272 $a_{nj}\coloneqq a(n\Delta t)$,
1273 we set
1274 $a_{n0}=1$
1275 for notational ease, and the additive error
1276 $\mathcal E$
1277 is
1278 $$
1279 \begin{align}
1280 \mathcal E&=\mathcal O\left(
1281 \Delta t^2\left[\sum_{i=1}^N\sum_{j=1}^{\textrm{length}}\dot a_{ij}
1282 \norm{H_j}
1283 +\sum_{i=1}^N\sum_{j,k=0}^{\textrm{length}}a_{ij}a_{ik}
1284 \norm{[H_j,H_k]}\right]
1285 \right)\\
1286 &=\mathcal O\left(
1287 N\Delta t^2\textrm{length}\left[\omega E+\alpha^2+E^2\right]
1288 \right)
1289 \end{align}
1290 $$
1291 where $\dot a_{nj}\coloneqq\dot a_j(n\Delta t)$ and
1292 $$
1293 \begin{align}
1294 \omega&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
1295 j\in\left[1,\textrm{length}\right]}}\left|\dot a_{ij}\right|,\\
1296 \alpha&\coloneqq\max_{\substack{i\in\left[1,N\right]\\
1297 j\in\left[0,\textrm{length}\right]}}\left|a_{ij}\right|,\\
1298 E&\coloneqq\max_{j\in\left[0,\textrm{length}\right]}\norm{H_j}.
1299 \end{align}
1300 $$
1301 Note the error is quadratic in $\Delta t$ but linear in $N$.
1302 We can also view this as being linear in $\Delta t$ and linear in
1303 total evolution time $N\Delta t$. Additionally, by Nyquist's theorem
1304 this asymptotic error scaling will not be achieved until the time step
1305 $\Delta t$ is smaller than $\frac{1}{2\Omega}$ where
1306 $\Omega$ is the largest energy or frequency in the system.
1307
1308 Parameters
1309 ----------
1310 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
1311 $\left(a_{ij}\right)$ The control amplitudes at each time step
1312 expressed as an $N\times\textrm{length}$ matrix where the element
1313 $a_{ij}$ corresponds to the control amplitude of the $j$th control
1314 Hamiltonian at the $i$th time step.
1315 state : NDArray[Shape[runtime_dim], complex128]
1316 $\left[\psi(0)\right]$ The state vector to propagate.
1317 dt : float
1318 ($\Delta t$) The time step to propagate by.
1319
1320 Returns
1321 -------
1322 NDArray[Shape[runtime_dim, time_steps + 1], complex128]
1323 The propagated state vector at each time step,
1324 $\left(\psi(n\Delta t)\right)_{n=0}^N$.
1325
1326 Note
1327 ----
1328 This function is a wrapper around the C++ function
1329 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::propagate_all()`.
1330
1331 See Also
1332 --------
1333 * :meth:`propagate()`
1334 * :meth:`propagate_collection()`
1335 """
1336 pass
[docs]
1337 def evolved_expectation_value(self,
1338 ctrl_amp: np.ndarray[np.complex128],
1339 state: np.ndarray[np.complex128],
1340 dt: float,
1341 observable: np.ndarray[np.complex128]
1342 ) -> complex:
1343 r"""
1344 Calculates the expectation value with respect to an observable of an
1345 evolved state vector evolved under a control Hamiltonian modulated by
1346 the control amplitudes. The integration is performed using
1347 :meth:`propagate()`.
1348
1349 Parameters
1350 ----------
1351 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
1352 $\left(a_{ij}\right)$ The control amplitudes at each time step
1353 expressed as an $N\times\textrm{length}$ matrix where the element
1354 $a_{ij}$ corresponds to the control amplitude of the $j$th control
1355 Hamiltonian at the $i$th time step.
1356 state : NDArray[Shape[runtime_dim], complex128]
1357 $\left[\psi(0)\right]$ The state vector to propagate.
1358 dt : float
1359 ($\Delta t$) The time step to propagate by.
1360 observable : NDArray[Shape[runtime_dim, runtime_dim], complex128]
1361 $(\hat O)$ The observable to calculate the expectation value of.
1362
1363 Returns
1364 -------
1365 complex
1366 The expectation value of the observable, $\langle\hat O\rangle
1367 \equiv\psi^\dagger(N\Delta t)\hat O\psi(N\Delta t)$.
1368
1369 Note
1370 ----
1371 This function is a wrapper around the C++ function
1372 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::evolved_expectation_value()`.
1373
1374 See Also
1375 --------
1376 * :meth:`evolved_expectation_value_all()`
1377 """
1378 pass
[docs]
1379 def evolved_expectation_value_all(self,
1380 ctrl_amp: np.ndarray[np.complex128],
1381 state: np.ndarray[np.complex128],
1382 dt: float,
1383 observable: np.ndarray[np.complex128]
1384 ) -> np.ndarray[np.complex128]:
1385 r"""
1386 Calculates the expectation values with respect to an observable of a
1387 time series of state vectors evolved under a control Hamiltonian
1388 modulated by the control amplitudes. The integration is performed using
1389 :meth:`propagate_all()`.
1390
1391 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
1392 $\left(a_{ij}\right)$ The control amplitudes at each time step
1393 expressed as an $N\times\textrm{length}$ matrix where the element
1394 $a_{ij}$ corresponds to the control amplitude of the $j$th control
1395 Hamiltonian at the $i$th time step.
1396 state : NDArray[Shape[runtime_dim], complex128]
1397 $\left[\psi(0)\right]$ The state vector to propagate.
1398 dt : float
1399 ($\Delta t$) The time step to propagate by.
1400 observable : NDArray[Shape[runtime_dim, runtime_dim], complex128]
1401 $(\hat O)$ The observable to calculate the expectation value of.
1402
1403 Returns
1404 -------
1405 NDArray[Shape[time_steps + 1], complex128]
1406 The expectation value of the observable,
1407 $\left(\psi^\dagger(n\Delta t)\hat O\psi(N\Delta t)\right)_{n=0}^N$.
1408
1409 Note
1410 ----
1411 This function is a wrapper around the C++ function
1412 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::evolved_expectation_value_all()`.
1413
1414 See Also
1415 --------
1416 * :meth:`evolved_expectation_value()`
1417 """
1418 pass
[docs]
1419 def switching_function(self,
1420 ctrl_amp: np.ndarray[np.complex128],
1421 state: np.ndarray[np.complex128],
1422 dt: float,
1423 cost: np.ndarray[np.complex128]
1424 ) -> tuple[complex, np.ndarray[np.float64]]:
1425 r"""
1426 Calculates the switching function for a Mayer problem with an
1427 expectation value as the cost function. More precisely if the cost
1428 function is
1429 $$
1430 J\left[\vec a(t)\right]\coloneqq\langle\hat O\rangle
1431 \equiv\psi^\dagger[\vec a(t);T]
1432 \hat O\psi[\vec a(t);T],
1433 $$
1434 where $T=N\Delta t$, then the switching function is
1435 $$
1436 \phi_j(t)\coloneqq\frac{\delta J}{\delta a_j(t)}
1437 =2\operatorname{Im}\left(\psi^\dagger[\vec a(t);T]
1438 \hat OU(t\to T)H_j\psi[\vec a(t);t]\right).
1439 $$
1440 using the first-order Suzuki-Trotter expansion we can express the
1441 switching function as
1442 $$
1443 \begin{align}
1444 &\phi_j(n\Delta t)=\frac{1}{\Delta t}\pdv{J}{a_{nj}}\\
1445 &=\!2\operatorname{Im}\!\left(\psi^\dagger(T)
1446 \hat O\!\!\left[\prod_{i>n}^N\prod_{k=1}^{\textrm{length}}
1447 e^{-ia_{ik}H_k\Delta t}\right]\!\!\!
1448 \left[\prod_{k=j}^{\textrm{length}}
1449 e^{-ia_{nk}H_k\Delta t}\right]\!H_j\!\!
1450 \left[\prod_{k=0}^{j-1}
1451 e^{-ia_{nk}H_k\Delta t}\right]
1452 \!\psi(\left[n-1\right]\Delta t)\right),
1453 \end{align}
1454 $$
1455 where for numerical efficiency we replace
1456 $e^{-ia_{ik}H_k\Delta t}$
1457 with
1458 $U_ke^{-ia_{ik}D_k\Delta t}U_k^\dagger$
1459 as in :meth:`propagate()`.
1460
1461 Parameters
1462 ----------
1463 ctrl_amp : NDArray[Shape[time_steps, :attr:`length`], complex128]
1464 $\left(a_{ij}\right)$ The control amplitudes at each time step
1465 expressed as an $N\times\textrm{length}$ matrix where the element
1466 $a_{ij}$ corresponds to the control amplitude of the $j$th control
1467 Hamiltonian at the $i$th time step.
1468 state : NDArray[Shape[runtime_dim], complex128]
1469 $\left[\psi(0)\right]$ The initial state vector.
1470 dt : float
1471 ($\Delta t$) The time step.
1472 cost : NDArray[Shape[runtime_dim, runtime_dim], complex128]
1473 $(\hat O)$ The observable to calculate the expectation value of.
1474
1475 Returns
1476 -------
1477 tuple[complex, NDArray[Shape[time_steps, :attr:`length`], float64]]
1478 The expectation value, $\psi^\dagger(T)\hat O\psi(T)$, and
1479 the switching function,
1480 $\phi_j(n\Delta t)$
1481 for all
1482 $j\in\left[1,\textrm{length}\right]$
1483 and
1484 $n\in\left[1,N\right]$.
1485
1486 Note
1487 ----
1488 This function is a wrapper around the C++ function
1489 :cpp:func:`Suzuki_Trotter_Evolver::UnitaryEvolver::switching_function()`.
1490 """
1491 pass
1492
[docs]
1493class DenseUnitaryEvolver_nctrl_dim(DenseUnitaryEvolver):
1494 r"""
1495 A class to store the diagonalised drift and control Hamiltonians with dense
1496 matrices and precompiled values of ``n_ctrl`` and ``dim``.
1497
1498 Important
1499 ---------
1500 This is not the actual class name ``nctrl`` and ``dim`` should be replaced
1501 with positive integers to specify their values. For example,
1502 ``DenseUnitaryEvolver_3_2`` is a valid class name with :attr:`n_ctrl`
1503 ``=3`` control Hamiltonians acting on a :attr:`dim` ``=2`` dimensional
1504 vector space. If only one of ``nctrl`` and ``dim`` is precompiled the other
1505 should be specified as `Dynamic`. For example,
1506 ``DenseUnitaryEvolver_Dynamic_2`` is a valid class name with a dynamic
1507 number of control Hamiltonians (:attr:`n_ctrl` ``=-1``) acting on a
1508 :attr:`dim` ``=2`` dimensional vector space.
1509
1510
1511 On initialisation the Hamiltonians are diagonalised and the eigenvectors and
1512 values stored as dense matrices. This initial diagonalisation may be slow
1513 and takes
1514 $O(\textrm{dim}^3)$
1515 time for a
1516 $\textrm{dim}\times \textrm{dim}$
1517 Hamiltonian. However, it allows each step of the Suzuki-Trotter expansion
1518 to be implimented in
1519 $O(\textrm{dim}^2)$
1520 time with matrix multiplication and only scalar exponentiation opposed to
1521 matrix exponentiation which takes
1522 $O(\textrm{dim}^3)$
1523 time.
1524
1525 Note
1526 ----
1527 This class is a Python wrapper around the C++ struct:
1528
1529 .. code-block:: cpp
1530
1531 Suzuki_Trotter_Evolver::UnitaryEvolver<n_ctrl, dim, DMatrix<dim, dim>>
1532
1533 from
1534 `Suzuki-Trotter-Evolver <https://github.com/Christopher-K-Long/Suzuki-Trotter-Evolver>`__.
1535
1536 Note
1537 ----
1538 Unlike :class:`DenseUnitaryEvolver`, :attr:`n_ctrl` and :attr:`dim` are
1539 baked into the class when the C++ code is compiled allowing for more
1540 efficient state propagation.
1541
1542 See Also
1543 --------
1544 * :class:`SparseUnitaryEvolver_nctrl_dim`
1545 * :class:`DenseUnitaryEvolver`
1546
1547
1548 ---
1549 """
1550
1551 n_ctrl: int = None
1552 r"""
1553 The number of control Hamiltonians used to compile the C++ backend. Equal to
1554 the value in the class name. A value of ``-1`` represents ``Dynamic`` in the
1555 class name and implies the value is not precompiled in the C++. This was the
1556 value at compile time while :attr:`length` is the value at runtime time.
1557 """
1558
1559 dim: int = None
1560 r"""
1561 The dimension of the vector space the Hamiltonians act upon used to compile
1562 the C++ backend. Equal to the value in the class name. A value of ``-1``
1563 represents ``Dynamic`` in the class name and implies the value is not
1564 precompiled in the C++.
1565 """
1566
1567 dim_x_n_ctrl: int = None
1568 r"""
1569 The dimension of rows in each control Hamiltonian multiplied by the
1570 number of control Hamiltonians upon used to compile the C++ backend. This is
1571 the number of rows for the ``control_hamiltonians`` argument for
1572 :meth:`__init__()`. A value of ``-1`` implies the value is not precompiled
1573 in the C++.
1574
1575 Note
1576 ----
1577 This is a wrapper around the C++ member
1578 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::dim_x_n_ctrl`.
1579 """
1580
[docs]
1581class SparseUnitaryEvolver_nctrl_dim(SparseUnitaryEvolver):
1582 r"""
1583 A class to store the diagonalised drift and control Hamiltonians with sparse
1584 matrices and precompiled values of ``n_ctrl`` and ``dim``.
1585
1586 Important
1587 ---------
1588 This is not the actual class name ``nctrl`` and ``dim`` should be replaced
1589 with positive integers to specify their values. For example,
1590 ``SparseUnitaryEvolver_3_2`` is a valid class name with :attr:`n_ctrl`
1591 ``=3`` control Hamiltonians acting on a :attr:`dim` ``=2`` dimensional
1592 vector space. If only one of ``nctrl`` and ``dim`` is precompiled the other
1593 should be specified as `Dynamic`. For example,
1594 ``SparseUnitaryEvolver_Dynamic_2`` is a valid class name with a dynamic
1595 number of control Hamiltonians (:attr:`n_ctrl` ``=-1``) acting on a
1596 :attr:`dim` ``=2`` dimensional vector space.
1597
1598
1599 On initialisation the Hamiltonians are diagonalised and the eigenvectors and
1600 values stored as sparse and dense matrices, respectively. This initial
1601 diagonalisation may be slow and takes
1602 $O(\textrm{dim}^3)$
1603 time for a
1604 $\textrm{dim}\times \textrm{dim}$
1605 Hamiltonian. However, it allows each step of the Suzuki-Trotter expansion
1606 to be implimented with sparse matrix multiplication and only scalar
1607 exponentiation opposed to matrix exponentiation.
1608
1609 Note
1610 ----
1611 This class is a Python wrapper around the C++ struct:
1612
1613 .. code-block:: cpp
1614
1615 Suzuki_Trotter_Evolver::UnitaryEvolver<n_ctrl, dim, SMatrix>
1616
1617 from
1618 `Suzuki-Trotter-Evolver <https://github.com/Christopher-K-Long/Suzuki-Trotter-Evolver>`__.
1619
1620 Note
1621 ----
1622 Unlike :class:`SparseUnitaryEvolver`, :attr:`n_ctrl` and :attr:`dim` are
1623 baked into the class when the C++ code is compiled allowing for more
1624 efficient state propagation.
1625
1626 See Also
1627 --------
1628 * :class:`DenseUnitaryEvolver_nctrl_dim`
1629 * :class:`SparseUnitaryEvolver`
1630
1631
1632 ---
1633 """
1634
1635 n_ctrl: int = None
1636 r"""
1637 The number of control Hamiltonians used to compile the C++ backend. Equal to
1638 the value in the class name. A value of ``-1`` represents ``Dynamic`` in the
1639 class name and implies the value is not precompiled in the C++. This was the
1640 value at compile time while :attr:`length` is the value at runtime time.
1641 """
1642
1643 dim: int = None
1644 r"""
1645 The dimension of the vector space the Hamiltonians act upon used to compile
1646 the C++ backend. Equal to the value in the class name. A value of ``-1``
1647 represents ``Dynamic`` in the class name and implies the value is not
1648 precompiled in the C++.
1649 """
1650
1651 dim_x_n_ctrl: int = None
1652 r"""
1653 The dimension of rows in each control Hamiltonian multiplied by the
1654 number of control Hamiltonians upon used to compile the C++ backend. This is
1655 the number of rows for the ``control_hamiltonians`` argument for :meth:`__init__()`. A value
1656 of ``-1`` implies the value is not precompiled in the C++.
1657
1658 Note
1659 ----
1660 This is a wrapper around the C++ member
1661 :cpp:member:`Suzuki_Trotter_Evolver::UnitaryEvolver::dim_x_n_ctrl`.
1662 """