Skip to content

Make time and pulse duration properties #61

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 4 commits into from
Mar 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion filter_functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
'gradient', 'pulse_sequence', 'remap', 'util', 'superoperator', 'infidelity_derivative']


__version__ = '1.0.2'
__version__ = '1.0.3'
__license__ = 'GNU GPLv3+'
__author__ = 'Quantum Technology Group, RWTH Aachen University'
66 changes: 47 additions & 19 deletions filter_functions/pulse_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,15 +243,12 @@ def __init__(self, *args, **kwargs) -> None:
self.c_coeffs = None
self.n_coeffs = None
self.dt = None
self.t = None
self.tau = None
self.d = None
self.basis = None

# Parse the input arguments and set attributes
attributes = ('c_opers', 'c_oper_identifiers', 'c_coeffs', 'n_opers',
'n_oper_identifiers', 'n_coeffs', 'dt', 't', 'tau', 'd',
'basis')
'n_oper_identifiers', 'n_coeffs', 'dt', 'd', 'basis')
if not args:
# Bypass args parsing and directly set necessary attributes
values = (kwargs[attr] for attr in attributes)
Expand All @@ -270,6 +267,8 @@ def __init__(self, *args, **kwargs) -> None:
setattr(self, attr, value)

# Initialize attributes that can be set by bound methods to None
self._t = None
self._tau = None
self._omega = None
self._eigvals = None
self._eigvecs = None
Expand Down Expand Up @@ -417,6 +416,39 @@ def is_cached(self, attr: str) -> bool:

return getattr(self, attr) is not None

@property
def t(self) -> ndarray:
"""The times of the pulse."""
if self._t is None:
self._t = np.concatenate(([0], self.dt.cumsum()))

return self._t

@t.setter
def t(self, val: ndarray):
"""Set the times of the pulse."""
self._t = val

@property
def tau(self) -> Union[float, int]:
"""The duration of the pulse."""
if self._t is not None:
self._tau = self.t[-1]
else:
self._tau = self.dt.sum()

return self._tau

@tau.setter
def tau(self, val: Union[float, int]):
"""Set the total duration of the pulse."""
self._tau = val

@property
def duration(self) -> float:
"""The duration of the pulse. Alias of tau."""
return self.tau

def diagonalize(self) -> None:
r"""Diagonalize the Hamiltonian defining the pulse sequence."""
# Only calculate if not done so before
Expand All @@ -426,7 +458,6 @@ def diagonalize(self) -> None:
self.eigvals, self.eigvecs, self.propagators = numeric.diagonalize(hamiltonian,
self.dt)

# Set the total propagator
self.total_propagator = self.propagators[-1]

def get_control_matrix(self, omega: Coefficients, show_progressbar: bool = False,
Expand Down Expand Up @@ -1089,8 +1120,6 @@ def _parse_args(H_c: Hamiltonian, H_n: Hamiltonian, dt: Coefficients, **kwargs)
# Check operator shapes
raise ValueError('Control and noise Hamiltonian not same dimension!')

t = np.concatenate(([0], dt.cumsum()))
tau = t[-1]
# Dimension of the system
d = control_args[0].shape[-1]

Expand All @@ -1109,7 +1138,7 @@ def _parse_args(H_c: Hamiltonian, H_n: Hamiltonian, dt: Coefficients, **kwargs)
raise ValueError("Expected basis elements to be of shape " +
f"({d}, {d}), not {basis.shape[1:]}!")

return (*control_args, *noise_args, dt, t, tau, d, basis)
return (*control_args, *noise_args, dt, d, basis)


def _parse_Hamiltonian(H: Hamiltonian, n_dt: int, H_str: str) -> Tuple[Sequence[Operator],
Expand Down Expand Up @@ -1493,14 +1522,16 @@ def concatenate_without_filter_function(pulses: Iterable[PulseSequence],
)

dt = np.concatenate(tuple(pulse.dt for pulse in pulses))
t = np.concatenate(([0], dt.cumsum()))
tau = t[-1]

attributes = {'dt': dt, 't': t, 'tau': tau, 'd': pulses[0].d, 'basis': basis}
attributes = {'dt': dt, 'd': pulses[0].d, 'basis': basis}
attributes.update(**{key: value for key, value in zip(control_keys, control_values)})
attributes.update(**{key: value for key, value in zip(noise_keys, noise_values)})

newpulse = PulseSequence(**attributes)
# Only cache total duration (whole array of times might be large
# in case of concatenation)
newpulse.tau = sum(pulse.tau for pulse in pulses)

if return_identifier_mappings:
return newpulse, control_values[-1], noise_values[-1]

Expand Down Expand Up @@ -1755,8 +1786,6 @@ def concatenate_periodic(pulse: PulseSequence, repeats: int) -> PulseSequence:
# Initialize a new PulseSequence instance with the Hamiltonians sequenced
# (this is much easier than in the general case, thus do it on the fly)
dt = np.tile(pulse.dt, repeats)
t = np.concatenate(([0], dt.cumsum()))
tau = t[-1]
newpulse = PulseSequence(
c_opers=pulse.c_opers,
n_opers=pulse.n_opers,
Expand All @@ -1765,11 +1794,10 @@ def concatenate_periodic(pulse: PulseSequence, repeats: int) -> PulseSequence:
c_coeffs=np.tile(pulse.c_coeffs, (1, repeats)),
n_coeffs=np.tile(pulse.n_coeffs, (1, repeats)),
dt=dt,
t=t,
tau=tau,
d=pulse.d,
basis=pulse.basis
)
newpulse.tau = repeats*pulse.tau

if not cached_ctrl_mat:
# No cached filter functions to reuse and pulse correlation FFs not
Expand Down Expand Up @@ -1876,11 +1904,11 @@ def remap(pulse: PulseSequence, order: Sequence[int], d_per_qubit: int = 2,
c_coeffs=pulse.c_coeffs[c_sort_idx],
n_coeffs=pulse.n_coeffs[n_sort_idx],
dt=pulse.dt,
t=pulse.t,
tau=pulse.tau,
d=pulse.d,
basis=pulse.basis
)
remapped_pulse.t = pulse._t
remapped_pulse.tau = pulse._tau

if pulse.is_cached('eigvals'):
remapped_pulse.eigvals = util.tensor_transpose(pulse.eigvals, order,
Expand Down Expand Up @@ -2298,11 +2326,11 @@ def extend(
c_coeffs=np.asarray(c_coeffs)[c_sort_idx],
n_coeffs=np.asarray(n_coeffs)[n_sort_idx],
dt=pulses[0].dt,
t=pulses[0].t,
tau=pulses[0].tau,
d=d,
basis=basis
)
newpulse.t = pulses[0]._t
newpulse.tau = pulses[0]._tau

if newpulse.basis.btype != 'Pauli':
# Cannot do any extensions
Expand Down
38 changes: 34 additions & 4 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,8 @@ def test_pulse_sequence_attributes(self):
assertion(A.is_cached(alias.replace(' ', '_')))

A.cleanup('all')
A._t = None
A._tau = None

# Test cleanup
C = ff.concatenate((A, A), calc_pulse_correlation_FF=True,
Expand Down Expand Up @@ -448,6 +450,14 @@ def test_pulse_sequence_attributes(self):
for attr in set(attrs).difference(freq_attrs):
self.assertIsNotNone(getattr(C, attr))

# Test t, tau, and duration properties
pulse = testutil.rand_pulse_sequence(2, 3, 1, 1)
self.assertIs(pulse._t, None)
self.assertIs(pulse._tau, None)
self.assertArrayEqual(pulse.t, [0, *pulse.dt.cumsum()])
self.assertEqual(pulse.tau, pulse.t[-1])
self.assertEqual(pulse.duration, pulse.tau)

def test_pulse_sequence_attributes_concat(self):
"""Test attributes of concatenated sequence."""
X, Y, Z = util.paulis[1:]
Expand Down Expand Up @@ -484,10 +494,6 @@ def test_pulse_sequence_attributes_concat(self):
newpulse = ff.concatenate(pulses, calc_filter_function=True)
self.assertTrue(newpulse.is_cached('filter function'))

pulse_12 = pulse_1 @ pulse_2
pulse_21 = pulse_2 @ pulse_1
pulse_45 = pulse_4 @ pulse_5

with self.assertRaises(TypeError):
_ = pulse_1 @ rng.standard_normal((2, 2))

Expand All @@ -498,9 +504,26 @@ def test_pulse_sequence_attributes_concat(self):
# Test nbytes property
_ = pulse_1.nbytes

pulse_12 = pulse_1 @ pulse_2
pulse_21 = pulse_2 @ pulse_1
pulse_45 = pulse_4 @ pulse_5

self.assertArrayEqual(pulse_12.dt, [*dt_1, *dt_2])
self.assertArrayEqual(pulse_21.dt, [*dt_2, *dt_1])

self.assertIs(pulse_12._t, None)
self.assertIs(pulse_21._t, None)

self.assertEqual(pulse_12._tau, pulse_1.tau + pulse_2.tau)
self.assertEqual(pulse_21._tau, pulse_1.tau + pulse_2.tau)

self.assertAlmostEqual(pulse_12.duration, pulse_1.duration + pulse_2.duration)
self.assertAlmostEqual(pulse_21.duration, pulse_2.duration + pulse_1.duration)
self.assertAlmostEqual(pulse_12.duration, pulse_21.duration)

self.assertArrayAlmostEqual(pulse_12.t, [*pulse_1.t, *(pulse_2.t[1:] + pulse_1.tau)])
self.assertArrayAlmostEqual(pulse_21.t, [*pulse_2.t, *(pulse_1.t[1:] + pulse_2.tau)])

self.assertArrayEqual(pulse_12.c_opers, [X, Y])
self.assertArrayEqual(pulse_21.c_opers, [Y, X])

Expand Down Expand Up @@ -563,6 +586,13 @@ def test_pulse_sequence_attributes_concat(self):
self.assertArrayEqual(pulse.c_oper_identifiers, sorted(ids))
self.assertArrayEqual(pulse.n_oper_identifiers, sorted(ids))

pulse = testutil.rand_pulse_sequence(2, 7, 1, 2)
periodic_pulse = ff.concatenate_periodic(pulse, 7)

self.assertIs(periodic_pulse._t, None)
self.assertEqual(periodic_pulse._tau, pulse.tau * 7)
self.assertArrayAlmostEqual(periodic_pulse.t, [0, *periodic_pulse.dt.cumsum()])

def test_cache_intermediates(self):
"""Test caching of intermediate elements"""
pulse = testutil.rand_pulse_sequence(3, 4, 2, 3)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_precision.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def test_calculation_with_unitaries(self):
pulse.n_opers = pulses[0].n_opers
pulse.n_oper_identifiers = pulses[0].n_oper_identifiers
if rng.integers(0, 2):
pulse.t = None
pulse._t = None

omega = rng.random(17)
B_atomic = np.array([numeric.calculate_noise_operators_from_scratch(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_sequencing.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ def test_caching(self):
pulse_2 = testutil.rand_pulse_sequence(2, 10, btype='Pauli')
pulse_3 = testutil.rand_pulse_sequence(2, 10, btype='GGM')
pulse_2.dt = pulse_1.dt
pulse_2.t = pulse_1.t
pulse_2._tau = pulse_1._tau
omega = util.get_sample_frequencies(pulse_1, 50)

# diagonalize one pulse
Expand Down