Skip to content

Second order magnus #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 63 commits into from
Mar 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
a603969
First draft to calculate second order Magnus
thangleiter May 12, 2020
6b9e40d
Test in docstring
thangleiter May 16, 2020
b31558b
Merge branch 'master' into feature/second_order_magnus
thangleiter Jul 17, 2020
7622091
Merge branch 'master' into feature/second_order_magnus
thangleiter Jul 20, 2020
aef8f04
Fix integral for edge cases and refactor calculation
thangleiter Jul 20, 2020
9d30b2c
Merge branch 'feature/second_order_magnus' of https://git.rwth-aachen…
thangleiter Jul 22, 2020
189a0ee
Improve performance, correct docstring
thangleiter Jul 26, 2020
823a63f
Add comments and better naming in numeric._second_order_integral
thangleiter Jul 27, 2020
417cf52
Make calculate_second_order_magnus more performant
thangleiter Jul 27, 2020
d6a6b31
Factor out calculation of first order integral in numeric.calculate_c…
thangleiter Jul 27, 2020
272cf5c
Drop case distinction when computing interaction picture noise operat…
thangleiter Jul 27, 2020
0c4a65e
Add docstring to _first_order_integral and calculate dE in _second_or…
thangleiter Jul 28, 2020
7879c33
Add type hints and buffers for energy differences
thangleiter Jul 29, 2020
cb8c953
Merge branch 'feature/follow_new_formulation' into feature/second_ord…
thangleiter Jul 29, 2020
d98e402
Mask expm1 in numeric._first_order_integral
thangleiter Jul 29, 2020
accb5f3
Add option to reuse intermediate results for second order FF and add …
thangleiter Jul 29, 2020
da61c3f
Add numeric.calculate_frequency_shifts (WIP)
thangleiter Jul 29, 2020
f3cb4c7
Fix calculation of second order
thangleiter Jul 31, 2020
b1c64c2
Use einsum since much faster for small d, not much slower for large
thangleiter Jul 31, 2020
77c99d1
Add second order to calculate_cumulant_function
thangleiter Jul 31, 2020
ef5de7f
Merge branch 'feature/follow_new_formulation' into feature/second_ord…
thangleiter Aug 9, 2020
826c667
Use util.parse_optional_parameters for order param
thangleiter Aug 9, 2020
1182baf
Add cache_intermediates arg to filter function get/set methods
thangleiter Aug 9, 2020
1b5917e
Add cache_intermediates arg to filter function get/set methods
thangleiter Aug 9, 2020
a023258
Merge branch 'feature/second_order_magnus' of https://git.rwth-aachen…
thangleiter Aug 9, 2020
daa27b9
Remove merge conflict artefact
thangleiter Aug 9, 2020
802e4ef
Remove unused arg
thangleiter Aug 10, 2020
458d317
Don't calculate cumulative for last step
thangleiter Aug 10, 2020
f9733b2
Remove surplus blank line
thangleiter Aug 10, 2020
e6c27cb
Add tests for second order
thangleiter Aug 12, 2020
6436371
Raise Exception earlier
thangleiter Aug 12, 2020
ae8c92b
Add docstring to calculate_second_order_filter_function
thangleiter Aug 12, 2020
4960a67
Merge branch 'feature/follow_new_formulation' into feature/second_ord…
thangleiter Aug 12, 2020
1105b5d
Bugfix
thangleiter Aug 12, 2020
0e3b5c6
Use only second order contribution for comparison
thangleiter Aug 12, 2020
1ca0dce
Move test_second_order_filter_function, test_cumulant_function to tes…
thangleiter Aug 12, 2020
03242bd
Fix cumulant function test
thangleiter Aug 12, 2020
7647895
Add more tests
thangleiter Aug 12, 2020
6addc39
Return frequency shifts as float since real
thangleiter Aug 12, 2020
cd4045a
Add second order to test_error_transfer_matrix and reduce boilerplate
thangleiter Aug 12, 2020
8434fac
Merge branch 'feature/follow_new_formulation' into feature/second_ord…
thangleiter Sep 11, 2020
c20ee0b
Use util.cexp - 1 instead of expm1 for speed
thangleiter Sep 20, 2020
d62a581
Merge branch 'feature/follow_new_formulation' into feature/second_ord…
thangleiter Sep 20, 2020
3efe569
Use expression for contraction of integral and n_opers_basis
thangleiter Sep 20, 2020
18f26d2
Merge branch 'master' into feature/second_order_magnus
thangleiter Dec 6, 2020
5dbbc3c
Merge branch 'master' into feature/second_order_magnus
thangleiter Feb 10, 2021
922829a
Add cache_intermediates functionality to calculate_decay_amplitudes
thangleiter Feb 10, 2021
c3200a2
Add some info regarding second order
thangleiter Feb 10, 2021
7c06219
Merge branch 'master' into feature/second_order_magnus
thangleiter Feb 15, 2021
af5d0c5
Merge branch 'feature/second_order_magnus' of github.com:qutech/filte…
thangleiter Feb 15, 2021
4f3ecd6
Merge branch 'master' into feature/second_order_magnus
thangleiter Feb 28, 2021
01059f4
Only raise wrong shape error if ndim correct
thangleiter Feb 28, 2021
a291073
Fix atols
thangleiter Feb 28, 2021
b88fb01
Merge branch 'hotfix/tests' into feature/second_order_magnus
thangleiter Feb 28, 2021
d687cdb
Remove leftover merge conflicts
thangleiter Feb 28, 2021
a622a50
Fix new numpy rng methods
thangleiter Feb 28, 2021
a2d2a86
Small improvements to doc
thangleiter Mar 1, 2021
41c10ca
Use Knuth's directive for line breaks before binary operators
thangleiter Mar 1, 2021
067309e
Relax test
thangleiter Mar 1, 2021
42dbb98
Allow for 2pi phase multiples in oper_equiv tests
thangleiter Mar 1, 2021
e067e60
Relax test reqs even more for 2nd order
thangleiter Mar 1, 2021
5ff308c
Less python, more numpy
thangleiter Mar 1, 2021
c68d14f
Remove memory_parsimonious option for frequency shifts
thangleiter Mar 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,450 changes: 1,431 additions & 19 deletions doc/source/examples/calculating_quantum_processes.ipynb

Large diffs are not rendered by default.

472 changes: 431 additions & 41 deletions filter_functions/numeric.py

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions filter_functions/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ def plot_cumulant_function(
omega: Optional[Coefficients] = None,
cumulant_function: Optional[ndarray] = None,
n_oper_identifiers: Optional[Sequence[int]] = None,
second_order: bool = False,
colorscale: str = 'linear',
linthresh: Optional[float] = None,
basis_labels: Optional[Sequence[str]] = None,
Expand Down Expand Up @@ -711,6 +712,10 @@ def plot_cumulant_function(
The identifiers of the noise operators for which the cumulant
function should be plotted. All identifiers can be accessed via
``pulse.n_oper_identifiers``. Defaults to all.
second_order: bool, optional
Also take into account the frequency shifts :math:`\Delta` that
correspond to second order Magnus expansion and constitute unitary
terms. Default ``False``.
colorscale: str, optional
The scale of the color code ('linear' or 'log' (default))
linthresh: float, optional
Expand Down Expand Up @@ -774,9 +779,8 @@ def plot_cumulant_function(

n_oper_inds = util.get_indices_from_identifiers(pulse, n_oper_identifiers, 'noise')
n_oper_identifiers = pulse.n_oper_identifiers[n_oper_inds]
K = numeric.calculate_cumulant_function(pulse, spectrum, omega,
n_oper_identifiers=n_oper_identifiers,
which='total')
K = numeric.calculate_cumulant_function(pulse, spectrum, omega, n_oper_identifiers,
'total', second_order)
if K.ndim == 4:
# Only autocorrelated noise supported
K = K[tuple(n_oper_inds), tuple(n_oper_inds)]
Expand Down
141 changes: 92 additions & 49 deletions filter_functions/pulse_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ def __init__(self, *args, **kwargs) -> None:
self._filter_function_gen = None
self._filter_function_pc = None
self._filter_function_pc_gen = None
self._filter_function_2 = None
self._intermediates = dict()

def __str__(self):
Expand Down Expand Up @@ -402,6 +403,7 @@ def is_cached(self, attr: str) -> bool:
'pulse correlation filter function': '_filter_function_pc',
'fidelity pulse correlation filter function': '_filter_function_pc',
'generalized pulse correlation filter function': '_filter_function_pc_gen',
'second order filter function': '_filter_function_2',
'control matrix': '_control_matrix',
'pulse correlation control matrix': '_control_matrix_pc'
}
Expand All @@ -420,8 +422,9 @@ def diagonalize(self) -> None:
# Only calculate if not done so before
if not all(self.is_cached(attr) for attr in ('eigvals', 'eigvecs', 'propagators')):
# Control Hamiltonian as a (n_dt, d, d) array
H = np.einsum('ijk,il->ljk', self.c_opers, self.c_coeffs)
self.eigvals, self.eigvecs, self.propagators = numeric.diagonalize(H, self.dt)
hamiltonian = np.einsum('ijk,il->ljk', self.c_opers, self.c_coeffs)
self.eigvals, self.eigvecs, self.propagators = numeric.diagonalize(hamiltonian,
self.dt)

# Set the total propagator
self.total_propagator = self.propagators[-1]
Expand Down Expand Up @@ -530,11 +533,15 @@ def get_pulse_correlation_control_matrix(self) -> ndarray:
"True."
)

@util.parse_which_FF_parameter
def get_filter_function(self, omega: Coefficients, which: str = 'fidelity',
show_progressbar: bool = False,
cache_intermediates: bool = False) -> ndarray:
r"""Get the first-order filter function.
@util.parse_optional_parameters({'which': ('fidelity', 'generalized'), 'order': (1, 2)})
def get_filter_function(
self, omega: Coefficients,
which: str = 'fidelity',
order: int = 1,
show_progressbar: bool = False,
cache_intermediates: bool = False
) -> ndarray:
r"""Get the first or second order filter function.

The filter function is cached so it doesn't need to be
calculated twice for the same frequencies.
Expand All @@ -545,7 +552,10 @@ def get_filter_function(self, omega: Coefficients, which: str = 'fidelity',
The frequencies at which to evaluate the filter function.
which: str, optional
Which filter function to return. Either 'fidelity' (default)
or 'generalized' (see :ref:`Notes <notes>`).
or 'generalized' (see :ref:`Notes <notes>`). Only if
``order == 1``.
order: int, optional
First or second order filter function.
show_progressbar: bool, optional
Show a progress bar for the calculation of the control
matrix.
Expand All @@ -563,7 +573,7 @@ def get_filter_function(self, omega: Coefficients, which: str = 'fidelity',

Notes
-----
The generalized filter function is given by
The first-order generalized filter function is given by

.. math::

Expand All @@ -586,33 +596,50 @@ def get_filter_function(self, omega: Coefficients, which: str = 'fidelity',
"""
# Only calculate if not calculated before for the same frequencies
if np.array_equal(self.omega, omega):
if which == 'fidelity':
if self.is_cached('filter_function'):
return self._filter_function
if order == 1:
if which == 'fidelity':
if self.is_cached('filter function'):
return self._filter_function
else:
# which == 'generalized'
if self.is_cached('filter_function_gen'):
return self._filter_function_gen
else:
# which == 'generalized'
if self.is_cached('filter_function_gen'):
return self._filter_function_gen
# order == 2
if self.is_cached('filter_function_2'):
return self._filter_function_2
else:
# Getting with different frequencies. Remove all cached attributes
# that are frequency-dependent
self.cleanup('frequency dependent')

control_matrix = self.get_control_matrix(omega, show_progressbar, cache_intermediates)
self.cache_filter_function(omega, control_matrix=control_matrix, which=which)
if order == 1:
control_matrix = self.get_control_matrix(omega, show_progressbar, cache_intermediates)
else:
# order == 2
control_matrix = None

self.cache_filter_function(omega, control_matrix=control_matrix, which=which,
order=order, show_progressbar=show_progressbar,
cache_intermediates=cache_intermediates)

if which == 'fidelity':
return self._filter_function
if order == 1:
if which == 'fidelity':
return self._filter_function
else:
# which == 'generalized'
return self._filter_function_gen
else:
# which == 'generalized'
return self._filter_function_gen
# order == 2
return self._filter_function_2

@util.parse_which_FF_parameter
@util.parse_optional_parameters({'which': ('fidelity', 'generalized'), 'order': (1, 2)})
def cache_filter_function(
self, omega: Coefficients,
control_matrix: Optional[ndarray] = None,
filter_function: Optional[ndarray] = None,
which: str = 'fidelity',
order: int = 1,
show_progressbar: bool = False,
cache_intermediates: bool = False
) -> None:
Expand All @@ -633,11 +660,14 @@ def cache_filter_function(
it is computed and the filter function derived from it.
filter_function: array_like, shape (n_nops, n_nops, [d**2, d**2,] n_omega), optional
The filter function for the frequencies *omega*. If
``None``, it is computed from control_matrix.
``None``, it is computed from R in the case ``order == 1``
and from scratch else.
which: str, optional
Which filter function to cache. Either 'fidelity' (default)
or 'generalized'.
show_progressbar: bool
order: int, optional
First or second order filter function.
show_progressbar: bool, optional
Show a progress bar for the calculation of the control
matrix.
cache_intermediates: bool, optional
Expand All @@ -649,31 +679,45 @@ def cache_filter_function(
PulseSequence.get_filter_function : Getter method
"""
if filter_function is None:
if control_matrix is None:
control_matrix = self.get_control_matrix(omega, show_progressbar, cache_intermediates)

self.cache_control_matrix(omega, control_matrix)
if control_matrix.ndim == 4:
# Calculate pulse correlation FF and derive canonical FF from it
F_pc = numeric.calculate_pulse_correlation_filter_function(control_matrix, which)

if which == 'fidelity':
self._filter_function_pc = F_pc
if order == 1:
if control_matrix is None:
control_matrix = self.get_control_matrix(omega, show_progressbar,
cache_intermediates)

self.cache_control_matrix(omega, control_matrix)
if control_matrix.ndim == 4:
# Calculate pulse correlation FF and derive canonical FF
# from it
F_pc = numeric.calculate_pulse_correlation_filter_function(control_matrix,
which)

if which == 'fidelity':
self._filter_function_pc = F_pc
else:
# which == 'generalized'
self._filter_function_pc_gen = F_pc

filter_function = F_pc.sum(axis=(0, 1))
else:
# which == 'generalized'
self._filter_function_pc_gen = F_pc

filter_function = F_pc.sum(axis=(0, 1))
# Regular case
filter_function = numeric.calculate_filter_function(control_matrix, which)
else:
# Regular case
filter_function = numeric.calculate_filter_function(control_matrix, which)
# order == 2
filter_function = numeric.calculate_second_order_filter_function(
self.eigvals, self.eigvecs, self.propagators, omega, self.basis,
self.n_opers, self.n_coeffs, self.dt, self._intermediates, show_progressbar
)

self.omega = omega
if which == 'fidelity':
self._filter_function = filter_function
if order == 1:
if which == 'fidelity':
self._filter_function = filter_function
else:
# which == 'generalized'
self._filter_function_gen = filter_function
else:
# which == 'generalized'
self._filter_function_gen = filter_function
# order == 2
self._filter_function_2 = filter_function

@util.parse_which_FF_parameter
def get_pulse_correlation_filter_function(self, which: str = 'fidelity') -> ndarray:
Expand Down Expand Up @@ -947,7 +991,6 @@ def cleanup(self, method: str = 'conservative') -> None:
- _total_phases
- _control_matrix
- _control_matrix_pc
- _intermediates

If set to 'all', all of the above as well as the following
attributes are deleted:
Expand All @@ -957,6 +1000,7 @@ def cleanup(self, method: str = 'conservative') -> None:
- _filter_function_gen
- _filter_function_pc
- _filter_function_pc_gen
- _filter_function_2
- _intermediates['control_matrix_step']

If set to 'frequency dependent' only attributes that are
Expand All @@ -970,16 +1014,15 @@ def cleanup(self, method: str = 'conservative') -> None:
default_attrs = {'_eigvals', '_eigvecs', '_propagators'}
concatenation_attrs = {'_total_propagator', '_total_phases', '_total_propagator_liouville',
'_control_matrix', '_control_matrix_pc', '_intermediates'}
filter_function_attrs = {'omega', '_filter_function', '_filter_function_gen',
'_filter_function_pc', '_filter_function_pc_gen'}
filter_function_attrs = {'_filter_function', '_filter_function_2', '_filter_function_gen',
'_filter_function_pc', '_filter_function_pc_gen', 'omega'}

if method == 'conservative':
attrs = default_attrs
elif method == 'greedy':
attrs = default_attrs.union(concatenation_attrs)
elif method == 'frequency dependent':
attrs = filter_function_attrs.union({'_control_matrix',
'_control_matrix_pc',
attrs = filter_function_attrs.union({'_control_matrix', '_control_matrix_pc',
'_total_phases'})
# Remove frequency dependent control_matrix_step from intermediates
self._intermediates.pop('control_matrix_step', None)
Expand Down
9 changes: 7 additions & 2 deletions filter_functions/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ def cexp(x: ndarray, out=None, where=True) -> ndarray:
----------
x: ndarray
Argument of the complex exponential :math:`\exp(i x)`.
out: ndarray, None, or tuple of ndarray and None, optional
A location into which the result is stored. See
:func:`numpy.ufunc`.
where: array_like, optional
This condition is broadcast over the input. See
:func:`numpy.ufunc`.

Returns
-------
Expand Down Expand Up @@ -787,8 +793,7 @@ def integrate(f: ndarray, x: Optional[ndarray] = None, dx: float = 1.0) -> Union

"""
dx = np.diff(x) if x is not None else dx
ret = f[..., 1:].copy()
ret += f[..., :-1]
ret = f[..., 1:] + f[..., :-1]
ret *= dx
return ret.sum(axis=-1)/2

Expand Down
Loading