Skip to content

Commit aba0d05

Browse files
authored
n_coeffs_deriv sorting (#83)
Sort n_coeffs_deriv by identifiers
1 parent e60bf5f commit aba0d05

File tree

4 files changed

+184
-70
lines changed

4 files changed

+184
-70
lines changed

filter_functions/gradient.py

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def _control_matrix_at_timestep_derivative(
262262
The individual control matrices of all time steps
263263
ctrlmat_g_deriv: ndarray, shape (n_dt, n_nops, d**2, n_ctrl, n_omega)
264264
The corresponding derivative with respect to the control
265-
strength :math:`\frac{\partial\mathcal{B}_{\alpha j}^{(g)}(\omega)}
265+
strength :math:`\frac{\partial\mathcal{B}_{\alpha j}^{(g)}(\omega)}`
266266
267267
Notes
268268
-----
@@ -384,8 +384,6 @@ def calculate_derivative_of_control_matrix_from_scratch(
384384
n_opers: Sequence[Operator],
385385
n_coeffs: Sequence[Coefficients],
386386
c_opers: Sequence[Operator],
387-
all_identifiers: Sequence[str],
388-
control_identifiers: Optional[Sequence[str]] = None,
389387
n_coeffs_deriv: Optional[Sequence[Coefficients]] = None,
390388
intermediates: Optional[Dict[str, ndarray]] = None
391389
) -> ndarray:
@@ -421,28 +419,16 @@ def calculate_derivative_of_control_matrix_from_scratch(
421419
n_coeffs: array_like, shape (n_nops, n_dt)
422420
The sensitivities of the system to the noise operators given by
423421
*n_opers* at the given time step.
424-
c_opers: array_like, shape (n_cops, d, d)
425-
Control operators :math:`H_k`.
426-
all_identifiers: array_like, shape (n_cops)
427-
Identifiers of all control operators.
428-
control_identifiers: Sequence[str], shape (n_ctrl), Optional
429-
Sequence of strings with the control identifiers to distinguish
430-
between accessible control and drift Hamiltonian. The default is
431-
None.
422+
c_opers: array_like, shape (n_ctrl, d, d)
423+
Control operators :math:`H_k` with respect to which the
424+
derivative is computed.
432425
n_coeffs_deriv: array_like, shape (n_nops, n_ctrl, n_dt)
433426
The derivatives of the noise susceptibilities by the control
434427
amplitudes. Defaults to None.
435428
intermediates: Dict[str, ndarray], optional
436429
Optional dictionary containing intermediate results of the
437430
calculation of the control matrix.
438431
439-
Raises
440-
------
441-
ValueError
442-
If the given identifiers *control_identifier* are not subset of
443-
the total identifiers *all_identifiers* of all control
444-
operators.
445-
446432
Returns
447433
-------
448434
ctrlmat_deriv: ndarray, shape (n_ctrl, n_omega, n_dt, n_nops, d**2)
@@ -470,24 +456,16 @@ def calculate_derivative_of_control_matrix_from_scratch(
470456
_liouville_derivative
471457
_control_matrix_at_timestep_derivative
472458
"""
473-
# Distinction between control and drift operators and only
474-
# calculate the derivatives in control direction
475-
try:
476-
idx = util.get_indices_from_identifiers(all_identifiers, control_identifiers)
477-
except ValueError as err:
478-
raise ValueError('Given control identifiers have to be a subset of (drift+control) ' +
479-
'Hamiltonian!') from err
480-
481459
d = eigvecs.shape[-1]
482460
n_dt = len(dt)
483-
n_ctrl = len(idx)
461+
n_ctrl = len(c_opers)
484462
n_nops = len(n_opers)
485463
n_omega = len(omega)
486464

487465
# Precompute some transformations or grab from cache if possible
488466
basis_transformed = numeric._transform_by_unitary(eigvecs[:, None], basis[None],
489467
out=np.empty((n_dt, d**2, d, d), complex))
490-
c_opers_transformed = numeric._transform_hamiltonian(eigvecs, c_opers[idx]).swapaxes(0, 1)
468+
c_opers_transformed = numeric._transform_hamiltonian(eigvecs, c_opers).swapaxes(0, 1)
491469
if not intermediates:
492470
# None or empty
493471
n_opers_transformed = numeric._transform_hamiltonian(eigvecs, n_opers,
@@ -575,6 +553,7 @@ def infidelity_derivative(
575553
spectrum: Coefficients,
576554
omega: Coefficients,
577555
control_identifiers: Optional[Sequence[str]] = None,
556+
n_oper_identifiers: Optional[Sequence[str]] = None,
578557
n_coeffs_deriv: Optional[Sequence[Coefficients]] = None
579558
) -> ndarray:
580559
r"""Calculate the entanglement infidelity derivative of the
@@ -599,11 +578,29 @@ def infidelity_derivative(
599578
omega: array_like, shape (n_omega,)
600579
The frequencies at which the integration is to be carried out.
601580
control_identifiers: Sequence[str], shape (n_ctrl,)
602-
Sequence of strings with the control identifiern to distinguish
603-
between accessible control and drift Hamiltonian.
581+
Sequence of strings with the control identifiers to
582+
distinguish between control and drift Hamiltonian. The
583+
default is None, in which case the derivative is computed
584+
for all known non-noise operators.
585+
n_oper_identifiers: Sequence[str], shape (n_nops,)
586+
Sequence of strings with the noise identifiers for which to
587+
compute the derivative contribution. The default is None, in
588+
which case it is computed for all known noise operators.
604589
n_coeffs_deriv: array_like, shape (n_nops, n_ctrl, n_dt)
605590
The derivatives of the noise susceptibilities by the control
606-
amplitudes. Defaults to None.
591+
amplitudes. The rows and columns should be in the same order
592+
as the corresponding identifiers above. Defaults to None, in
593+
which case the coefficients are assumed to be constant and
594+
hence their derivative vanishing.
595+
596+
.. warning::
597+
598+
Internally, control and noise terms of the Hamiltonian
599+
are stored alphanumerically sorted by their identifiers.
600+
If the noise and/or control identifiers above are not
601+
explicitly given, the rows and/or columns of this
602+
parameter need to be sorted in the same fashion.
603+
607604
608605
Raises
609606
------
@@ -616,7 +613,9 @@ def infidelity_derivative(
616613
infid_deriv: ndarray, shape (n_nops, n_dt, n_ctrl)
617614
Array with the derivative of the infidelity for each noise
618615
source taken for each control direction at each time step
619-
:math:`\frac{\partial I_e}{\partial u_h(t_{g'})}`.
616+
:math:`\frac{\partial I_e}{\partial u_h(t_{g'})}`. Sorted in
617+
the same fashion as `n_coeffs_deriv` or, if not given,
618+
alphanumerically by the identifiers.
620619
621620
Notes
622621
-----
@@ -658,7 +657,9 @@ def infidelity_derivative(
658657
https://doi.org/10.1016/S0375-9601(02)01272-0
659658
"""
660659
spectrum = numeric._parse_spectrum(spectrum, omega, range(len(pulse.n_opers)))
661-
filter_function_deriv = pulse.get_filter_function_derivative(omega, control_identifiers,
660+
filter_function_deriv = pulse.get_filter_function_derivative(omega,
661+
control_identifiers,
662+
n_oper_identifiers,
662663
n_coeffs_deriv)
663664

664665
integrand = np.einsum('ao,atho->atho', spectrum, filter_function_deriv)

filter_functions/pulse_sequence.py

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -160,17 +160,23 @@ class PulseSequence:
160160
Attributes
161161
----------
162162
c_opers: ndarray, shape (n_cops, d, d)
163-
Control operators
163+
Control operators. Note that they are stored sorted by their
164+
corresponding identifiers.
164165
n_opers: ndarray, shape (n_nops, d, d)
165-
Noise operators
166+
Noise operators. Note that they are stored sorted by their
167+
corresponding identifiers.
166168
c_oper_identifers: sequence of str
167-
Identifiers for the control operators of the system
169+
Identifiers for the control operators of the system. Stored
170+
sorted.
168171
n_oper_identifers: sequence of str
169-
Identifiers for the noise operators of the system
172+
Identifiers for the noise operators of the system. Stored
173+
sorted.
170174
c_coeffs: ndarray, shape (n_cops, n_dt)
171-
Control parameters in units of :math:`\hbar`
175+
Control parameters in units of :math:`\hbar`. Note that they
176+
are stored sorted by their corresponding identifiers.
172177
n_coeffs: ndarray, shape (n_nops, n_dt)
173-
Noise sensitivities in units of :math:`\hbar`
178+
Noise sensitivities in units of :math:`\hbar`. Note that they
179+
are stored sorted by their corresponding identifiers.
174180
dt: ndarray, shape (n_dt,)
175181
Time steps
176182
t: ndarray, shape (n_dt + 1,)
@@ -247,8 +253,10 @@ def __init__(self, *args, **kwargs) -> None:
247253
self.basis = None
248254

249255
# Parse the input arguments and set attributes
250-
attributes = ('c_opers', 'c_oper_identifiers', 'c_coeffs', 'n_opers',
251-
'n_oper_identifiers', 'n_coeffs', 'dt', 'd', 'basis')
256+
# TODO: Jesus, this is in need of refactoring.
257+
attributes = ('c_opers', 'c_oper_identifiers', 'c_coeffs',
258+
'n_opers', 'n_oper_identifiers', 'n_coeffs',
259+
'dt', 'd', 'basis')
252260
if not args:
253261
# Bypass args parsing and directly set necessary attributes
254262
values = (kwargs[attr] for attr in attributes)
@@ -855,6 +863,7 @@ def get_filter_function_derivative(
855863
self,
856864
omega: Coefficients,
857865
control_identifiers: Optional[Sequence[str]] = None,
866+
n_oper_identifiers: Optional[Sequence[str]] = None,
858867
n_coeffs_deriv: Optional[Sequence[Coefficients]] = None
859868
) -> ndarray:
860869
r"""Calculate the pulse sequence's filter function derivative.
@@ -864,27 +873,70 @@ def get_filter_function_derivative(
864873
omega: array_like, shape (n_omega,)
865874
Frequencies at which the pulse control matrix is to be
866875
evaluated.
867-
control_identifiers: Sequence[str]
868-
Sequence of strings with the control identifiern to
876+
control_identifiers: Sequence[str], shape (n_ctrl,)
877+
Sequence of strings with the control identifiers to
869878
distinguish between control and drift Hamiltonian. The
870-
default is None.
879+
default is None, in which case the derivative is computed
880+
for all known non-noise operators.
881+
n_oper_identifiers: Sequence[str], shape (n_nops,)
882+
Sequence of strings with the noise identifiers for which to
883+
compute the derivative contribution. The default is None, in
884+
which case it is computed for all known noise operators.
871885
n_coeffs_deriv: array_like, shape (n_nops, n_ctrl, n_dt)
872886
The derivatives of the noise susceptibilities by the control
873-
amplitudes. Defaults to None.
887+
amplitudes. The rows and columns should be in the same order
888+
as the corresponding identifiers above. Defaults to None, in
889+
which case the coefficients are assumed to be constant and
890+
hence their derivative vanishing.
891+
892+
.. warning::
893+
894+
Internally, control and noise terms of the Hamiltonian
895+
are stored alphanumerically sorted by their identifiers.
896+
If the noise and/or control identifiers above are not
897+
explicitly given, the rows and/or columns of this
898+
parameter need to be sorted in the same fashion.
874899
875900
Returns
876901
-------
877902
filter_function_deriv: ndarray, shape (n_nops, n_t, n_ctrl, n_omega)
878903
The regular filter functions' derivatives for variation in
879-
each control contribution.
904+
each control contribution. Sorted in the same fashion as
905+
`n_coeffs_deriv` or, if not given, alphanumerically by the
906+
identifiers.
880907
881908
"""
882-
control_matrix = self.get_control_matrix(omega, cache_intermediates=True)
909+
c_idx = util.get_indices_from_identifiers(self.c_oper_identifiers, control_identifiers)
910+
n_idx = util.get_indices_from_identifiers(self.n_oper_identifiers, n_oper_identifiers)
911+
912+
if n_coeffs_deriv is not None:
913+
# TODO 05/22: walrus once support for 3.7 is dropped.
914+
actual_shape = np.shape(n_coeffs_deriv)
915+
required_shape = (len(n_idx), len(c_idx), len(self))
916+
if actual_shape != required_shape:
917+
raise ValueError(f'Expected n_coeffs_deriv to be of shape {required_shape}, '
918+
f'not {actual_shape}. Did you forget to specify identifiers?')
919+
else:
920+
# Do nothing; n_coeffs_deriv specifies the sorting order. If identifiers
921+
# are given, we sort everything else by them. If not, n_coeffs_deriv is
922+
# expected to be sorted in accordance with the opers and coeffs.
923+
pass
883924

925+
# Check if we can pass on intermediates.
926+
intermediates = dict()
927+
# TODO 05/22: walrus once support for 3.7 is dropped.
928+
n_opers_transformed = self._intermediates.get('n_opers_transformed')
929+
first_order_integral = self._intermediates.get('first_order_integral')
930+
if n_opers_transformed is not None:
931+
intermediates['n_opers_transformed'] = n_opers_transformed[n_idx]
932+
if first_order_integral is not None:
933+
intermediates['first_order_integral'] = first_order_integral
934+
935+
control_matrix = self.get_control_matrix(omega, cache_intermediates=True)[n_idx]
884936
control_matrix_deriv = gradient.calculate_derivative_of_control_matrix_from_scratch(
885937
omega, self.propagators, self.eigvals, self.eigvecs, self.basis, self.t, self.dt,
886-
self.n_opers, self.n_coeffs, self.c_opers, self.c_oper_identifiers,
887-
control_identifiers, n_coeffs_deriv, self._intermediates
938+
self.n_opers[n_idx], self.n_coeffs[n_idx], self.c_opers[c_idx], n_coeffs_deriv,
939+
intermediates
888940
)
889941
return gradient.calculate_filter_function_derivative(control_matrix, control_matrix_deriv)
890942

tests/gradient_testutil.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ def deriv_exchange_interaction(eps):
2121
return j_0 / eps_0 * np.exp(eps / eps_0)
2222

2323

24-
def deriv_2_exchange_interaction(eps):
25-
return j_0 / eps_0 ** 2 * np.exp(eps / eps_0)
26-
27-
2824
def one_over_f_noise(f):
2925
spectrum = np.divide(S_0, f, where=(f != 0))
3026
spectrum[f == 0] = spectrum[np.abs(f).argmin()]
@@ -40,8 +36,6 @@ def create_sing_trip_pulse_seq(eps, dbz, *args):
4036

4137
H_n = [
4238
[sigma_z, deriv_exchange_interaction(eps[0])]
43-
# [sigma_z, np.ones_like(eps[0])]
44-
4539
]
4640

4741
dt = time_step * np.ones(n_time_steps)
@@ -84,18 +78,17 @@ def finite_diff_infid(u_ctrl_central, u_drift, d, pulse_sequence_builder,
8478
8579
"""
8680
pulse = pulse_sequence_builder(u_ctrl_central, u_drift, d)
87-
all_id = pulse.c_oper_identifiers
8881
if c_id is None:
89-
c_id = all_id[:len(u_ctrl_central)]
82+
c_id = pulse.c_oper_identifiers[:len(u_ctrl_central)]
9083

9184
# Make sure we test for zero frequency case (possible convergence issues)
9285
omega = ff.util.get_sample_frequencies(pulse=pulse, n_samples=n_freq_samples, spacing='log',
9386
include_quasistatic=True)
9487
spectrum = spectral_noise_density(omega)
9588

96-
gradient = np.empty((len(pulse.n_coeffs), len(pulse.dt), len(c_id)))
89+
gradient = np.empty(pulse.n_coeffs.shape + (len(c_id),))
9790

98-
for g in range(len(pulse.dt)):
91+
for g in range(len(pulse)):
9992
for k in range(len(c_id)):
10093
u_plus = u_ctrl_central.copy()
10194
u_plus[k, g] += delta_u

0 commit comments

Comments
 (0)