Skip to content

Commit 3659145

Browse files
authored
Merge pull request #46 from qutech/feature/second_order_magnus
Second order magnus
2 parents 148f2f1 + c68d14f commit 3659145

File tree

8 files changed

+2191
-220
lines changed

8 files changed

+2191
-220
lines changed

doc/source/examples/calculating_quantum_processes.ipynb

Lines changed: 1431 additions & 19 deletions
Large diffs are not rendered by default.

filter_functions/numeric.py

Lines changed: 430 additions & 40 deletions
Large diffs are not rendered by default.

filter_functions/plotting.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ def plot_cumulant_function(
669669
omega: Optional[Coefficients] = None,
670670
cumulant_function: Optional[ndarray] = None,
671671
n_oper_identifiers: Optional[Sequence[int]] = None,
672+
second_order: bool = False,
672673
colorscale: str = 'linear',
673674
linthresh: Optional[float] = None,
674675
basis_labels: Optional[Sequence[str]] = None,
@@ -711,6 +712,10 @@ def plot_cumulant_function(
711712
The identifiers of the noise operators for which the cumulant
712713
function should be plotted. All identifiers can be accessed via
713714
``pulse.n_oper_identifiers``. Defaults to all.
715+
second_order: bool, optional
716+
Also take into account the frequency shifts :math:`\Delta` that
717+
correspond to second order Magnus expansion and constitute unitary
718+
terms. Default ``False``.
714719
colorscale: str, optional
715720
The scale of the color code ('linear' or 'log' (default))
716721
linthresh: float, optional
@@ -774,9 +779,8 @@ def plot_cumulant_function(
774779

775780
n_oper_inds = util.get_indices_from_identifiers(pulse, n_oper_identifiers, 'noise')
776781
n_oper_identifiers = pulse.n_oper_identifiers[n_oper_inds]
777-
K = numeric.calculate_cumulant_function(pulse, spectrum, omega,
778-
n_oper_identifiers=n_oper_identifiers,
779-
which='total')
782+
K = numeric.calculate_cumulant_function(pulse, spectrum, omega, n_oper_identifiers,
783+
'total', second_order)
780784
if K.ndim == 4:
781785
# Only autocorrelated noise supported
782786
K = K[tuple(n_oper_inds), tuple(n_oper_inds)]

filter_functions/pulse_sequence.py

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ def __init__(self, *args, **kwargs) -> None:
283283
self._filter_function_gen = None
284284
self._filter_function_pc = None
285285
self._filter_function_pc_gen = None
286+
self._filter_function_2 = None
286287
self._intermediates = dict()
287288

288289
def __str__(self):
@@ -402,6 +403,7 @@ def is_cached(self, attr: str) -> bool:
402403
'pulse correlation filter function': '_filter_function_pc',
403404
'fidelity pulse correlation filter function': '_filter_function_pc',
404405
'generalized pulse correlation filter function': '_filter_function_pc_gen',
406+
'second order filter function': '_filter_function_2',
405407
'control matrix': '_control_matrix',
406408
'pulse correlation control matrix': '_control_matrix_pc'
407409
}
@@ -420,8 +422,9 @@ def diagonalize(self) -> None:
420422
# Only calculate if not done so before
421423
if not all(self.is_cached(attr) for attr in ('eigvals', 'eigvecs', 'propagators')):
422424
# Control Hamiltonian as a (n_dt, d, d) array
423-
H = np.einsum('ijk,il->ljk', self.c_opers, self.c_coeffs)
424-
self.eigvals, self.eigvecs, self.propagators = numeric.diagonalize(H, self.dt)
425+
hamiltonian = np.einsum('ijk,il->ljk', self.c_opers, self.c_coeffs)
426+
self.eigvals, self.eigvecs, self.propagators = numeric.diagonalize(hamiltonian,
427+
self.dt)
425428

426429
# Set the total propagator
427430
self.total_propagator = self.propagators[-1]
@@ -530,11 +533,15 @@ def get_pulse_correlation_control_matrix(self) -> ndarray:
530533
"True."
531534
)
532535

533-
@util.parse_which_FF_parameter
534-
def get_filter_function(self, omega: Coefficients, which: str = 'fidelity',
535-
show_progressbar: bool = False,
536-
cache_intermediates: bool = False) -> ndarray:
537-
r"""Get the first-order filter function.
536+
@util.parse_optional_parameters({'which': ('fidelity', 'generalized'), 'order': (1, 2)})
537+
def get_filter_function(
538+
self, omega: Coefficients,
539+
which: str = 'fidelity',
540+
order: int = 1,
541+
show_progressbar: bool = False,
542+
cache_intermediates: bool = False
543+
) -> ndarray:
544+
r"""Get the first or second order filter function.
538545
539546
The filter function is cached so it doesn't need to be
540547
calculated twice for the same frequencies.
@@ -545,7 +552,10 @@ def get_filter_function(self, omega: Coefficients, which: str = 'fidelity',
545552
The frequencies at which to evaluate the filter function.
546553
which: str, optional
547554
Which filter function to return. Either 'fidelity' (default)
548-
or 'generalized' (see :ref:`Notes <notes>`).
555+
or 'generalized' (see :ref:`Notes <notes>`). Only if
556+
``order == 1``.
557+
order: int, optional
558+
First or second order filter function.
549559
show_progressbar: bool, optional
550560
Show a progress bar for the calculation of the control
551561
matrix.
@@ -563,7 +573,7 @@ def get_filter_function(self, omega: Coefficients, which: str = 'fidelity',
563573
564574
Notes
565575
-----
566-
The generalized filter function is given by
576+
The first-order generalized filter function is given by
567577
568578
.. math::
569579
@@ -586,33 +596,50 @@ def get_filter_function(self, omega: Coefficients, which: str = 'fidelity',
586596
"""
587597
# Only calculate if not calculated before for the same frequencies
588598
if np.array_equal(self.omega, omega):
589-
if which == 'fidelity':
590-
if self.is_cached('filter_function'):
591-
return self._filter_function
599+
if order == 1:
600+
if which == 'fidelity':
601+
if self.is_cached('filter function'):
602+
return self._filter_function
603+
else:
604+
# which == 'generalized'
605+
if self.is_cached('filter_function_gen'):
606+
return self._filter_function_gen
592607
else:
593-
# which == 'generalized'
594-
if self.is_cached('filter_function_gen'):
595-
return self._filter_function_gen
608+
# order == 2
609+
if self.is_cached('filter_function_2'):
610+
return self._filter_function_2
596611
else:
597612
# Getting with different frequencies. Remove all cached attributes
598613
# that are frequency-dependent
599614
self.cleanup('frequency dependent')
600615

601-
control_matrix = self.get_control_matrix(omega, show_progressbar, cache_intermediates)
602-
self.cache_filter_function(omega, control_matrix=control_matrix, which=which)
616+
if order == 1:
617+
control_matrix = self.get_control_matrix(omega, show_progressbar, cache_intermediates)
618+
else:
619+
# order == 2
620+
control_matrix = None
621+
622+
self.cache_filter_function(omega, control_matrix=control_matrix, which=which,
623+
order=order, show_progressbar=show_progressbar,
624+
cache_intermediates=cache_intermediates)
603625

604-
if which == 'fidelity':
605-
return self._filter_function
626+
if order == 1:
627+
if which == 'fidelity':
628+
return self._filter_function
629+
else:
630+
# which == 'generalized'
631+
return self._filter_function_gen
606632
else:
607-
# which == 'generalized'
608-
return self._filter_function_gen
633+
# order == 2
634+
return self._filter_function_2
609635

610-
@util.parse_which_FF_parameter
636+
@util.parse_optional_parameters({'which': ('fidelity', 'generalized'), 'order': (1, 2)})
611637
def cache_filter_function(
612638
self, omega: Coefficients,
613639
control_matrix: Optional[ndarray] = None,
614640
filter_function: Optional[ndarray] = None,
615641
which: str = 'fidelity',
642+
order: int = 1,
616643
show_progressbar: bool = False,
617644
cache_intermediates: bool = False
618645
) -> None:
@@ -633,11 +660,14 @@ def cache_filter_function(
633660
it is computed and the filter function derived from it.
634661
filter_function: array_like, shape (n_nops, n_nops, [d**2, d**2,] n_omega), optional
635662
The filter function for the frequencies *omega*. If
636-
``None``, it is computed from control_matrix.
663+
``None``, it is computed from R in the case ``order == 1``
664+
and from scratch else.
637665
which: str, optional
638666
Which filter function to cache. Either 'fidelity' (default)
639667
or 'generalized'.
640-
show_progressbar: bool
668+
order: int, optional
669+
First or second order filter function.
670+
show_progressbar: bool, optional
641671
Show a progress bar for the calculation of the control
642672
matrix.
643673
cache_intermediates: bool, optional
@@ -649,31 +679,45 @@ def cache_filter_function(
649679
PulseSequence.get_filter_function : Getter method
650680
"""
651681
if filter_function is None:
652-
if control_matrix is None:
653-
control_matrix = self.get_control_matrix(omega, show_progressbar, cache_intermediates)
654-
655-
self.cache_control_matrix(omega, control_matrix)
656-
if control_matrix.ndim == 4:
657-
# Calculate pulse correlation FF and derive canonical FF from it
658-
F_pc = numeric.calculate_pulse_correlation_filter_function(control_matrix, which)
659-
660-
if which == 'fidelity':
661-
self._filter_function_pc = F_pc
682+
if order == 1:
683+
if control_matrix is None:
684+
control_matrix = self.get_control_matrix(omega, show_progressbar,
685+
cache_intermediates)
686+
687+
self.cache_control_matrix(omega, control_matrix)
688+
if control_matrix.ndim == 4:
689+
# Calculate pulse correlation FF and derive canonical FF
690+
# from it
691+
F_pc = numeric.calculate_pulse_correlation_filter_function(control_matrix,
692+
which)
693+
694+
if which == 'fidelity':
695+
self._filter_function_pc = F_pc
696+
else:
697+
# which == 'generalized'
698+
self._filter_function_pc_gen = F_pc
699+
700+
filter_function = F_pc.sum(axis=(0, 1))
662701
else:
663-
# which == 'generalized'
664-
self._filter_function_pc_gen = F_pc
665-
666-
filter_function = F_pc.sum(axis=(0, 1))
702+
# Regular case
703+
filter_function = numeric.calculate_filter_function(control_matrix, which)
667704
else:
668-
# Regular case
669-
filter_function = numeric.calculate_filter_function(control_matrix, which)
705+
# order == 2
706+
filter_function = numeric.calculate_second_order_filter_function(
707+
self.eigvals, self.eigvecs, self.propagators, omega, self.basis,
708+
self.n_opers, self.n_coeffs, self.dt, self._intermediates, show_progressbar
709+
)
670710

671711
self.omega = omega
672-
if which == 'fidelity':
673-
self._filter_function = filter_function
712+
if order == 1:
713+
if which == 'fidelity':
714+
self._filter_function = filter_function
715+
else:
716+
# which == 'generalized'
717+
self._filter_function_gen = filter_function
674718
else:
675-
# which == 'generalized'
676-
self._filter_function_gen = filter_function
719+
# order == 2
720+
self._filter_function_2 = filter_function
677721

678722
@util.parse_which_FF_parameter
679723
def get_pulse_correlation_filter_function(self, which: str = 'fidelity') -> ndarray:
@@ -947,7 +991,6 @@ def cleanup(self, method: str = 'conservative') -> None:
947991
- _total_phases
948992
- _control_matrix
949993
- _control_matrix_pc
950-
- _intermediates
951994
952995
If set to 'all', all of the above as well as the following
953996
attributes are deleted:
@@ -957,6 +1000,7 @@ def cleanup(self, method: str = 'conservative') -> None:
9571000
- _filter_function_gen
9581001
- _filter_function_pc
9591002
- _filter_function_pc_gen
1003+
- _filter_function_2
9601004
- _intermediates['control_matrix_step']
9611005
9621006
If set to 'frequency dependent' only attributes that are
@@ -970,16 +1014,15 @@ def cleanup(self, method: str = 'conservative') -> None:
9701014
default_attrs = {'_eigvals', '_eigvecs', '_propagators'}
9711015
concatenation_attrs = {'_total_propagator', '_total_phases', '_total_propagator_liouville',
9721016
'_control_matrix', '_control_matrix_pc', '_intermediates'}
973-
filter_function_attrs = {'omega', '_filter_function', '_filter_function_gen',
974-
'_filter_function_pc', '_filter_function_pc_gen'}
1017+
filter_function_attrs = {'_filter_function', '_filter_function_2', '_filter_function_gen',
1018+
'_filter_function_pc', '_filter_function_pc_gen', 'omega'}
9751019

9761020
if method == 'conservative':
9771021
attrs = default_attrs
9781022
elif method == 'greedy':
9791023
attrs = default_attrs.union(concatenation_attrs)
9801024
elif method == 'frequency dependent':
981-
attrs = filter_function_attrs.union({'_control_matrix',
982-
'_control_matrix_pc',
1025+
attrs = filter_function_attrs.union({'_control_matrix', '_control_matrix_pc',
9831026
'_total_phases'})
9841027
# Remove frequency dependent control_matrix_step from intermediates
9851028
self._intermediates.pop('control_matrix_step', None)

filter_functions/util.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ def cexp(x: ndarray, out=None, where=True) -> ndarray:
131131
----------
132132
x: ndarray
133133
Argument of the complex exponential :math:`\exp(i x)`.
134+
out: ndarray, None, or tuple of ndarray and None, optional
135+
A location into which the result is stored. See
136+
:func:`numpy.ufunc`.
137+
where: array_like, optional
138+
This condition is broadcast over the input. See
139+
:func:`numpy.ufunc`.
134140
135141
Returns
136142
-------
@@ -787,8 +793,7 @@ def integrate(f: ndarray, x: Optional[ndarray] = None, dx: float = 1.0) -> Union
787793
788794
"""
789795
dx = np.diff(x) if x is not None else dx
790-
ret = f[..., 1:].copy()
791-
ret += f[..., :-1]
796+
ret = f[..., 1:] + f[..., :-1]
792797
ret *= dx
793798
return ret.sum(axis=-1)/2
794799

0 commit comments

Comments
 (0)