Skip to content

Commit 376ea42

Browse files
committed
Add function to more quickly compute exp(1j*x)
This is a major time sink in the calculation of the control matrix using calculate_control_matrix_from_scratch(). Computing real and imaginary part separately and writing to a preallocated array should make the whole calculation faster by a factor of two.
1 parent b563a5b commit 376ea42

File tree

4 files changed

+51
-10
lines changed

4 files changed

+51
-10
lines changed

filter_functions/numeric.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
from .basis import Basis, ggm_expand
5757
from .plotting import plot_infidelity_convergence
5858
from .types import Coefficients, Operator
59-
from .util import (abs2, get_indices_from_identifiers, progressbar,
59+
from .util import (abs2, cexp, get_indices_from_identifiers, progressbar,
6060
symmetrize_spectrum)
6161

6262
__all__ = ['diagonalize', 'liouville_representation',
@@ -108,8 +108,7 @@ def diagonalize(H: ndarray, dt: Coefficients) -> Sequence[ndarray]:
108108
# P = np.empty((500, 4, 4), dtype=complex)
109109
# for l, (V, D) in enumerate(zip(HV, np.exp(-1j*dt*HD.T).T)):
110110
# P[l] = (V * D) @ V.conj().T
111-
P = np.einsum('lij,jl,lkj->lik', HV, np.exp(-1j*np.asarray(dt)*HD.T),
112-
HV.conj())
111+
P = np.einsum('lij,jl,lkj->lik', HV, cexp(-np.asarray(dt)*HD.T), HV.conj())
113112
# The cumulative propagator Q with the identity operator as first
114113
# element (Q_0 = P_0 = I), i.e.
115114
# Q = [Q_0, Q_1, ..., Q_n] = [P_0, P_1 @ P_0, ..., P_n @ ... @ P_0]
@@ -250,7 +249,7 @@ def progressbar_range(*args):
250249
# Mask the integral to avoid convergence problems
251250
mask_small = np.abs(EdE*dt[l]) <= 1e-7
252251
integral[mask_small] = dt[l]
253-
integral[~mask_small] = (np.exp(1j*EdE[~mask_small]*dt[l]) - 1) /\
252+
integral[~mask_small] = (cexp(EdE[~mask_small]*dt[l]) - 1) /\
254253
(1j*(EdE[~mask_small]))
255254
"""
256255
# Test convergence of integral as argument of exponential -> 0
@@ -263,7 +262,7 @@ def progressbar_range(*args):
263262
# Faster for d = 2 to also contract over the time dimension instead of
264263
# loop, but for readability we don't distinguish.
265264
R += np.einsum('o,j,jmn,omn,knm->jko',
266-
np.exp(1j*E*t[l]), n_coeffs[:, l], B[:, l], integral,
265+
cexp(E*t[l]), n_coeffs[:, l], B[:, l], integral,
267266
QdagV[l].conj().T @ basis @ QdagV[l],
268267
optimize=R_path)
269268

filter_functions/pulse_sequence.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@
5757
calculate_control_matrix_from_scratch,
5858
calculate_control_matrix_periodic,
5959
calculate_filter_function,
60-
calculate_pulse_correlation_filter_function, diagonalize,
60+
calculate_pulse_correlation_filter_function,
61+
diagonalize,
6162
liouville_representation)
6263
from .types import Coefficients, Hamiltonian, Operator, PulseMapping
63-
from .util import (CalculationError, all_array_equal,
64+
from .util import (CalculationError, all_array_equal, cexp,
6465
get_indices_from_identifiers, hash_array_along_axis, mdot,
6566
tensor, tensor_insert, tensor_merge, tensor_transpose)
6667

@@ -659,7 +660,7 @@ def cache_total_phases(self, omega: Coefficients,
659660
they are computed.
660661
"""
661662
if total_phases is None:
662-
total_phases = np.exp(1j*np.asarray(omega)*self.t[-1])
663+
total_phases = cexp(np.asarray(omega)*self.t[-1])
663664

664665
self.omega = omega
665666
self._total_phases = total_phases
@@ -814,7 +815,7 @@ def propagator_at_arb_t(self, t: Coefficients) -> ndarray:
814815
idx[idx < 0] = 0
815816
Q_prev = self.Q[idx]
816817
U_curr = np.einsum('lij,jl,lkj->lik', self.HV[idx],
817-
np.exp(-1j*(t - self.t[idx])*self.HD[idx].T),
818+
cexp((self.t[idx] - t)*self.HD[idx].T),
818819
self.HV[idx].conj())
819820

820821
return U_curr @ Q_prev

filter_functions/util.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def _get_notebook_name() -> str:
176176
P_np = [P.full() for P in P_qt]
177177

178178

179-
def abs2(x):
179+
def abs2(x: ndarray) -> ndarray:
180180
r"""
181181
Fast function to calculate the absolute value squared,
182182
@@ -191,6 +191,31 @@ def abs2(x):
191191
return x.real**2 + x.imag**2
192192

193193

194+
def cexp(x: ndarray) -> ndarray:
195+
r"""Fast complex exponential.
196+
197+
Parameters
198+
----------
199+
x : ndarray
200+
Argument of the complex exponential :math:`\exp(i x)`.
201+
202+
Returns
203+
-------
204+
y : ndarray
205+
Complex exponential :math:`y = \exp(i x)`.
206+
207+
References
208+
----------
209+
https://software.intel.com/en-us/forums/intel-distribution-for-python/topic/758148 # noqa
210+
"""
211+
df_exp = np.empty(x.shape, dtype=np.complex128)
212+
trig_buf = np.cos(x)
213+
df_exp.real[:] = trig_buf
214+
np.sin(x, out=trig_buf)
215+
df_exp.imag[:] = trig_buf
216+
return df_exp
217+
218+
194219
def _tensor_product_shape(shape_A: Sequence[int], shape_B: Sequence[int],
195220
rank: int):
196221
"""Get shape of the tensor product between A and B of rank rank"""

tests/test_util.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ def test_abs2(self):
3535
x = randn(20, 100) + 1j*randn(20, 100)
3636
self.assertArrayAlmostEqual(np.abs(x)**2, util.abs2(x))
3737

38+
def test_cexp(self):
39+
"""Fast complex exponential."""
40+
x = randn(50, 100)
41+
a = util.cexp(x)
42+
b = np.exp(1j*x)
43+
self.assertArrayAlmostEqual(a, b)
44+
45+
a = util.cexp(-x)
46+
b = np.exp(-1j*x)
47+
self.assertArrayAlmostEqual(a, b)
48+
49+
x = randn(50, 100)*(1 + 1j)
50+
a = util.cexp(x)
51+
b = np.exp(1j*x)
52+
self.assertArrayAlmostEqual(a, b)
53+
3854
def test_get_indices_from_identifiers(self):
3955
pulse = PulseSequence(
4056
[[util.P_np[3], [2], 'Z'],

0 commit comments

Comments
 (0)