Skip to content

Commit 55d3b44

Browse files
authored
Merge pull request #111 from qutech/refactor/init
Refactor PulseSequence constructor
2 parents afb5d44 + ba0de9f commit 55d3b44

File tree

2 files changed

+247
-121
lines changed

2 files changed

+247
-121
lines changed

filter_functions/pulse_sequence.py

Lines changed: 136 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -249,61 +249,124 @@ class PulseSequence:
249249
:meth:`__getitem__`), NumPy would otherwise try to create an
250250
ndarray of single-segment :class:`PulseSequence` s.
251251
"""
252+
c_opers: ndarray
253+
n_opers: ndarray
254+
c_oper_identifiers: ndarray
255+
n_oper_identifiers: ndarray
256+
c_coeffs: ndarray
257+
n_coeffs: ndarray
258+
dt: ndarray
259+
d: int
260+
basis: Basis
261+
262+
# Initialize attributes that can be set by bound methods to None
263+
_t = None
264+
_tau = None
265+
_omega = None
266+
_eigvals = None
267+
_eigvecs = None
268+
_propagators = None
269+
_total_phases = None
270+
_total_propagator = None
271+
_total_propagator_liouville = None
272+
_control_matrix = None
273+
_control_matrix_pc = None
274+
_filter_function = None
275+
_filter_function_gen = None
276+
_filter_function_pc = None
277+
_filter_function_pc_gen = None
278+
_filter_function_2 = None
279+
280+
def __new__(cls, *args, **kwargs):
281+
new = super().__new__(cls)
282+
new._intermediates = dict()
283+
return new
284+
285+
def __init__(self, H_c: Hamiltonian, H_n: Hamiltonian, dt: Coefficients,
286+
basis: Optional[Basis] = None):
287+
if not util.is_sequence_like(dt):
288+
raise TypeError(f'Expected a sequence of time steps, not {type(dt)}')
289+
290+
self.dt = np.asarray(dt)
291+
if not np.isreal(self.dt).all():
292+
raise ValueError('Times dt are not (all) real!')
293+
if (self.dt < 0).any():
294+
raise ValueError('Time steps are not (all) positive!')
295+
296+
self.c_opers, self.c_oper_identifiers, self.c_coeffs = _parse_hamiltonian(
297+
H_c, len(self.dt), 'H_c'
298+
)
299+
self.n_opers, self.n_oper_identifiers, self.n_coeffs = _parse_hamiltonian(
300+
H_n, len(self.dt), 'H_n'
301+
)
302+
303+
if self.c_opers.shape[-2:] != self.n_opers.shape[-2:]:
304+
raise ValueError('Control and noise Hamiltonian not same dimension!')
305+
306+
# Dimension of the system
307+
self.d = self.c_opers.shape[-1]
252308

253-
def __init__(self, *args, **kwargs) -> None:
254-
"""Initialize a PulseSequence instance."""
255-
# Initialize attributes set by _parse_args to None to satisfy static
256-
# code checker
257-
self.c_opers = None
258-
self.n_opers = None
259-
self.c_oper_identifiers = None
260-
self.n_oper_identifiers = None
261-
self.c_coeffs = None
262-
self.n_coeffs = None
263-
self.dt = None
264-
self.d = None
265-
self.basis = None
266-
267-
# Parse the input arguments and set attributes
268-
# TODO: Jesus, this is in need of refactoring.
269-
attributes = ('c_opers', 'c_oper_identifiers', 'c_coeffs',
270-
'n_opers', 'n_oper_identifiers', 'n_coeffs',
271-
'dt', 'd', 'basis')
272-
if not args:
273-
# Bypass args parsing and directly set necessary attributes
274-
values = (kwargs[attr] for attr in attributes)
309+
if basis is None:
310+
# Use generalized Gell-Mann basis by default since we have a nice
311+
# expression for a basis expansion
312+
self.basis = Basis.ggm(self.d)
275313
else:
276-
if len(args) == 4:
277-
# basis given as arg, not kwarg
278-
kwargs['basis'] = args[-1]
279-
elif len(args) < 3:
280-
posargs = ['H_c', 'H_n', 'dt']
281-
raise TypeError(f'Missing {3 - len(args)} required positional argument(s): '
282-
+ f'{posargs[len(args):]}')
283-
284-
values = _parse_args(*args[:3], **kwargs)
285-
286-
for attr, value in zip(attributes, values):
287-
setattr(self, attr, value)
288-
289-
# Initialize attributes that can be set by bound methods to None
290-
self._t = None
291-
self._tau = None
292-
self._omega = None
293-
self._eigvals = None
294-
self._eigvecs = None
295-
self._propagators = None
296-
self._total_phases = None
297-
self._total_propagator = None
298-
self._total_propagator_liouville = None
299-
self._control_matrix = None
300-
self._control_matrix_pc = None
301-
self._filter_function = None
302-
self._filter_function_gen = None
303-
self._filter_function_pc = None
304-
self._filter_function_pc_gen = None
305-
self._filter_function_2 = None
306-
self._intermediates = dict()
314+
if not isinstance(basis, Basis):
315+
raise ValueError("Expected basis to be an instance of the "
316+
+ f"'filter_functions.basis.Basis' class, not {type(basis)}!")
317+
if basis.shape[1:] != (self.d, self.d):
318+
# Make sure the basis has the correct dimension (we allow an
319+
# incomplete set)
320+
raise ValueError("Expected basis elements to be of shape "
321+
+ f"({self.d}, {self.d}), not {basis.shape[1:]}!")
322+
self.basis = basis
323+
324+
@classmethod
325+
def from_arrays(cls,
326+
c_opers: ndarray, c_oper_identifiers: ndarray, c_coeffs: ndarray,
327+
n_opers: ndarray, n_oper_identifiers: ndarray, n_coeffs: ndarray,
328+
dt: ndarray, basis: Optional[Basis] = None):
329+
"""Instantiate a :class:`PulseSequence` from NumPy arrays.
330+
331+
This is an alternative constructor to the nested list of lists
332+
structure that is used for initialization. See the class
333+
docstring for more information on the parameters.
334+
335+
Parameters
336+
----------
337+
c_opers : ndarray, shape (n_cops, d, d)
338+
c_oper_identifiers : ndarray, shape (n_cops,)
339+
c_coeffs : ndarray, shape (n_cops, n_dt)
340+
n_opers : ndarray, shape (n_nops, d, d)
341+
n_oper_identifiers : ndarray, shape (n_nops,)
342+
n_coeffs : ndarray, shape (n_nops, n_dt)
343+
dt : ndarray, shape (n_dt,)
344+
basis : Basis, optional
345+
346+
"""
347+
new = cls.__new__(cls)
348+
new.c_opers = np.asanyarray(c_opers)
349+
new.c_oper_identifiers = np.asanyarray(c_oper_identifiers)
350+
new.c_coeffs = np.asanyarray(c_coeffs)
351+
new.n_opers = np.asanyarray(n_opers)
352+
new.n_oper_identifiers = np.asanyarray(n_oper_identifiers)
353+
new.n_coeffs = np.asanyarray(n_coeffs)
354+
new.dt = np.asanyarray(dt)
355+
new.d = new.c_opers.shape[-1]
356+
new.basis = np.asanyarray(basis).view(Basis) if basis is not None else Basis.ggm(new.d)
357+
358+
if not len(new.c_opers) == len(new.c_oper_identifiers) == len(new.c_coeffs):
359+
raise ValueError('Control Hamiltonian not same length!')
360+
if not len(new.n_opers) == len(new.n_oper_identifiers) == len(new.n_coeffs):
361+
raise ValueError('Noise Hamiltonian not same length!')
362+
if not len(set(new.c_opers.shape[1:] + new.n_opers.shape[1:])) == 1:
363+
raise ValueError('Control and/or noise Hamiltonian not same, square dimension!')
364+
if not new.dt.size == new.n_coeffs.shape[1] == new.c_coeffs.shape[1]:
365+
raise ValueError('Time steps not same length!')
366+
if not new.basis.d == new.d:
367+
raise ValueError('Basis dimension not same as Hamiltonian dimension!')
368+
369+
return new
307370

308371
def __str__(self):
309372
"""String method."""
@@ -397,15 +460,14 @@ def __getitem__(self, key) -> 'PulseSequence':
397460
if not new_dt.size:
398461
raise IndexError('Cannot create empty PulseSequence')
399462

400-
new = self.__class__(
463+
new = self.__class__.from_arrays(
401464
c_opers=self.c_opers,
402465
n_opers=self.n_opers,
403466
c_oper_identifiers=self.c_oper_identifiers,
404467
n_oper_identifiers=self.n_oper_identifiers,
405468
c_coeffs=np.atleast_2d(self.c_coeffs.T[key]).T,
406469
n_coeffs=np.atleast_2d(self.n_coeffs.T[key]).T,
407470
dt=new_dt,
408-
d=self.d,
409471
basis=self.basis
410472
)
411473

@@ -1241,48 +1303,6 @@ def _join_equal_segments(pulse: PulseSequence) -> Sequence[Coefficients]:
12411303
return c_coeffs, n_coeffs, dt
12421304

12431305

1244-
def _parse_args(H_c: Hamiltonian, H_n: Hamiltonian, dt: Coefficients, **kwargs) -> Any:
1245-
"""
1246-
Function to parse the arguments given at instantiation of the
1247-
PulseSequence object.
1248-
"""
1249-
1250-
if not util.is_sequence_like(dt):
1251-
raise TypeError(f'Expected a sequence of time steps, not {type(dt)}')
1252-
1253-
dt = np.asarray(dt)
1254-
if not np.isreal(dt).all():
1255-
raise ValueError('Times dt are not (all) real!')
1256-
if (dt < 0).any():
1257-
raise ValueError('Time steps are not (all) positive!')
1258-
1259-
control_args = _parse_hamiltonian(H_c, len(dt), 'H_c')
1260-
noise_args = _parse_hamiltonian(H_n, len(dt), 'H_n')
1261-
1262-
if control_args[0].shape[-2:] != noise_args[0].shape[-2:]:
1263-
raise ValueError('Control and noise Hamiltonian not same dimension!')
1264-
1265-
# Dimension of the system
1266-
d = control_args[0].shape[-1]
1267-
1268-
basis = kwargs.pop('basis', None)
1269-
if basis is None:
1270-
# Use generalized Gell-Mann basis by default since we have a nice
1271-
# expression for a basis expansion
1272-
basis = Basis.ggm(d)
1273-
else:
1274-
if not isinstance(basis, Basis):
1275-
raise ValueError("Expected basis to be an instance of the "
1276-
+ f"'filter_functions.basis.Basis' class, not {type(basis)}!")
1277-
if basis.shape[1:] != (d, d):
1278-
# Make sure the basis has the correct dimension (we allow an
1279-
# incomplete set)
1280-
raise ValueError("Expected basis elements to be of shape "
1281-
+ f"({d}, {d}), not {basis.shape[1:]}!")
1282-
1283-
return *control_args, *noise_args, dt, d, basis
1284-
1285-
12861306
def _parse_hamiltonian(H: Hamiltonian, n_dt: int, H_str: str) -> Tuple[Sequence[Operator],
12871307
Sequence[str],
12881308
Sequence[Coefficients]]:
@@ -1639,28 +1659,26 @@ def concatenate_without_filter_function(pulses: Iterable[PulseSequence],
16391659
if not util.all_array_equal((pulse.basis for pulse in pulses)):
16401660
raise ValueError('Trying to concatenate PulseSequence instances with different bases!')
16411661

1642-
control_keys = ('c_opers', 'c_oper_identifiers', 'c_coeffs')
1643-
noise_keys = ('n_opers', 'n_oper_identifiers', 'n_coeffs')
1644-
1645-
control_values = _concatenate_hamiltonian(*[[getattr(pulse, key) for pulse in pulses]
1646-
for key in control_keys],
1647-
kind='control')
1648-
noise_values = _concatenate_hamiltonian(*[[getattr(pulse, key) for pulse in pulses]
1649-
for key in noise_keys],
1650-
kind='noise')
1651-
1652-
attributes = {'dt': np.concatenate(tuple(pulse.dt for pulse in pulses)),
1653-
'd': pulses[0].d, 'basis': pulses[0].basis}
1654-
attributes.update(zip(control_keys, control_values))
1655-
attributes.update(zip(noise_keys, noise_values))
1662+
*control_hamiltonian, control_mapping = _concatenate_hamiltonian(
1663+
*[[getattr(pulse, key) for pulse in pulses]
1664+
for key in ['c_opers', 'c_oper_identifiers', 'c_coeffs']],
1665+
kind='control'
1666+
)
1667+
*noise_hamiltonian, noise_mapping = _concatenate_hamiltonian(
1668+
*[[getattr(pulse, key) for pulse in pulses]
1669+
for key in ['n_opers', 'n_oper_identifiers', 'n_coeffs']],
1670+
kind='noise'
1671+
)
1672+
dt = np.concatenate(tuple(pulse.dt for pulse in pulses))
1673+
basis = pulses[0].basis
16561674

1657-
newpulse = PulseSequence(**attributes)
1675+
newpulse = PulseSequence.from_arrays(*control_hamiltonian, *noise_hamiltonian, dt, basis)
16581676
# Only cache total duration (whole array of times might be large
16591677
# in case of concatenation)
16601678
newpulse.tau = sum(pulse.tau for pulse in pulses)
16611679

16621680
if return_identifier_mappings:
1663-
return newpulse, control_values[-1], noise_values[-1]
1681+
return newpulse, control_mapping, noise_mapping
16641682

16651683
return newpulse
16661684

@@ -1905,15 +1923,14 @@ def concatenate_periodic(pulse: PulseSequence, repeats: int,
19051923
# Initialize a new PulseSequence instance with the Hamiltonians sequenced
19061924
# (this is much easier than in the general case, thus do it on the fly)
19071925
dt = np.tile(pulse.dt, repeats)
1908-
newpulse = PulseSequence(
1926+
newpulse = PulseSequence.from_arrays(
19091927
c_opers=pulse.c_opers,
1910-
n_opers=pulse.n_opers,
19111928
c_oper_identifiers=pulse.c_oper_identifiers,
1912-
n_oper_identifiers=pulse.n_oper_identifiers,
19131929
c_coeffs=np.tile(pulse.c_coeffs, (1, repeats)),
1930+
n_opers=pulse.n_opers,
1931+
n_oper_identifiers=pulse.n_oper_identifiers,
19141932
n_coeffs=np.tile(pulse.n_coeffs, (1, repeats)),
19151933
dt=dt,
1916-
d=pulse.d,
19171934
basis=pulse.basis
19181935
)
19191936
newpulse.tau = repeats*pulse.tau
@@ -2011,15 +2028,14 @@ def remap(pulse: PulseSequence, order: Sequence[int], d_per_qubit: int = 2,
20112028
n_oper_identifiers, n_sort_idx = _map_identifiers(pulse.n_oper_identifiers,
20122029
oper_identifier_mapping)
20132030

2014-
remapped_pulse = PulseSequence(
2031+
remapped_pulse = PulseSequence.from_arrays(
20152032
c_opers=c_opers[c_sort_idx],
20162033
n_opers=n_opers[n_sort_idx],
20172034
c_oper_identifiers=c_oper_identifiers[c_sort_idx],
20182035
n_oper_identifiers=n_oper_identifiers[n_sort_idx],
20192036
c_coeffs=pulse.c_coeffs[c_sort_idx],
20202037
n_coeffs=pulse.n_coeffs[n_sort_idx],
20212038
dt=pulse.dt,
2022-
d=pulse.d,
20232039
basis=pulse.basis
20242040
)
20252041
remapped_pulse.t = pulse._t
@@ -2429,15 +2445,14 @@ def extend(
24292445
c_sort_idx = np.argsort(c_oper_identifiers)
24302446
n_sort_idx = np.argsort(n_oper_identifiers)
24312447

2432-
newpulse = PulseSequence(
2448+
newpulse = PulseSequence.from_arrays(
24332449
c_opers=np.asarray(c_opers)[c_sort_idx],
24342450
n_opers=np.asarray(n_opers)[n_sort_idx],
24352451
c_oper_identifiers=np.asarray(c_oper_identifiers)[c_sort_idx],
24362452
n_oper_identifiers=np.asarray(n_oper_identifiers)[n_sort_idx],
24372453
c_coeffs=np.asarray(c_coeffs)[c_sort_idx],
24382454
n_coeffs=np.asarray(n_coeffs)[n_sort_idx],
24392455
dt=pulses[0].dt,
2440-
d=d,
24412456
basis=basis
24422457
)
24432458
newpulse.t = pulses[0]._t

0 commit comments

Comments
 (0)