Skip to content

Commit a0f82f7

Browse files
authored
Making the errors in DDS to Controls conversion more floating-point-friendly (#69)
* Splitting remaining errors in the timing of pulses into two more meaningful kinds of errors (offsets in the wrong order and pulse overlap) * Adding requirement and check for minimum_segment_duration >= 0. * Adding unit tests for tightly packed sequences of pulses
1 parent a291675 commit a0f82f7

File tree

2 files changed

+43
-19
lines changed

2 files changed

+43
-19
lines changed

qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def convert_dds_to_driven_control(
109109
Parameters
110110
----------
111111
dynamic_decoupling_sequence : qctrlopencontrols.DynamicDecouplingSequence
112-
The base DDS
112+
The base DDS. Its offsets should be sorted in ascending order in time.
113113
maximum_rabi_rate : float, optional
114114
Maximum Rabi Rate; Defaults to 2*pi.
115115
Must be greater than 0 and less or equal to UPPER_BOUND_RABI_RATE, if set.
@@ -119,6 +119,7 @@ def convert_dds_to_driven_control(
119119
minimum_segment_duration : float, optional
120120
If set, further restricts the duration of every segment of the Driven Controls.
121121
Defaults to 0, in which case it does not affect the duration of the pulses.
122+
Must be greater or equal to 0, if set.
122123
kwargs : dict, optional
123124
Options to make the corresponding filter type.
124125
I.e. the options for primitive are described in doc for the PrimitivePulse class.
@@ -165,6 +166,10 @@ def convert_dds_to_driven_control(
165166
{'type(dynamic_decoupling_sequence':
166167
type(dynamic_decoupling_sequence)})
167168

169+
if minimum_segment_duration < 0.:
170+
raise ArgumentsValueError('Minimum segment duration must be greater or equal to 0.',
171+
{'minimum_segment_duration': minimum_segment_duration})
172+
168173
_check_maximum_rotation_rate(maximum_rabi_rate, maximum_detuning_rate)
169174

170175
sequence_duration = dynamic_decoupling_sequence.duration
@@ -206,6 +211,14 @@ def convert_dds_to_driven_control(
206211
azimuthal_angles = np.append(azimuthal_angles, [0])
207212
detuning_rotations = np.append(detuning_rotations, [0])
208213

214+
# check that the offsets are correctly sorted in time
215+
if any(np.diff(offsets) <= 0.):
216+
raise ArgumentsValueError("Pulse timing could not be properly deduced from "
217+
"the sequence offsets. Make sure all offsets are "
218+
"in increasing order.",
219+
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence},
220+
extras={'offsets': offsets})
221+
209222
offsets = offsets[np.newaxis, :]
210223
rabi_rotations = rabi_rotations[np.newaxis, :]
211224
azimuthal_angles = azimuthal_angles[np.newaxis, :]
@@ -244,29 +257,14 @@ def convert_dds_to_driven_control(
244257
translation = pulse_start_ends[-1, 1] - sequence_duration
245258
pulse_start_ends[-1, :] = pulse_start_ends[-1, :] - translation
246259

247-
# three conditions to check
248-
# 1. Control segment start times should be monotonically increasing
249-
# 2. Control segment end times should be monotonically increasing
250-
# 3. Adjacent segments should not be overlapping
251-
if (np.any(pulse_start_ends[0:-1, 0] - pulse_start_ends[1:, 0] > 0.) or
252-
np.any(pulse_start_ends[0:-1, 1] - pulse_start_ends[1:, 1] > 0.) or
253-
np.any(pulse_start_ends[1:, 0]-pulse_start_ends[0:-1, 1] < 0.)):
254-
255-
raise ArgumentsValueError('Pulse timing could not be properly deduced from '
256-
'the sequence operation offsets. Try increasing the '
257-
'maximum rabi rate or maximum detuning rate.',
258-
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence,
259-
'maximum_rabi_rate': maximum_rabi_rate,
260-
'maximum_detuning_rate': maximum_detuning_rate},
261-
extras={'deduced_pulse_start_timing': pulse_start_ends[:, 0],
262-
'deduced_pulse_end_timing': pulse_start_ends[:, 1]})
263-
264260
# check if the minimum_segment_duration is respected in the gaps between the pulses
261+
# as minimum_segment_duration >= 0, this also excludes overlaps
265262
gap_durations = pulse_start_ends[1:, 0] - pulse_start_ends[:-1, 1]
266263
if not np.all(np.logical_or(np.greater(gap_durations, minimum_segment_duration),
267264
np.isclose(gap_durations, minimum_segment_duration))):
268265
raise ArgumentsValueError("Distance between pulses does not respect minimum_segment_duration. "
269-
"Try decreasing the minimum_segment_duration.",
266+
"Try decreasing the minimum_segment_duration or increasing "
267+
"the maximum_rabi_rate or the maximum_detuning_rate.",
270268
{'dynamic_decoupling_sequence': dynamic_decoupling_sequence,
271269
'maximum_rabi_rate': maximum_rabi_rate,
272270
'maximum_detuning_rate': maximum_detuning_rate,

tests/test_dynamical_decoupling.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,32 @@ def test_conversion_of_pulses_with_arbitrary_detuning_rotations():
575575
assert _all_greater_or_close(driven_control.duration, minimum_segment_duration)
576576

577577

578+
def test_conversion_of_tightly_packed_sequence():
579+
"""
580+
Tests if the method to convert a DDS to driven controls handles properly
581+
a sequence tightly packed with pulses, where there is no time for a gap between
582+
the pi/2-pulses and the adjacent pi-pulses.
583+
"""
584+
# create a sequence containing 2 pi-pulses and 2 pi/2-pulses at the extremities
585+
dynamic_decoupling_sequence = DynamicDecouplingSequence(
586+
duration=0.2,
587+
offsets=np.array([0., 0.05, 0.15, 0.2]),
588+
rabi_rotations=np.array([1.57079633, 3.14159265, 3.14159265, 1.57079633]),
589+
azimuthal_angles=np.array([0., 0., 0., 0.]),
590+
detuning_rotations=np.array([0., 0., 0., 0.]),
591+
name=None)
592+
593+
driven_control = convert_dds_to_driven_control(dynamic_decoupling_sequence,
594+
maximum_rabi_rate= 20. * np.pi,
595+
minimum_segment_duration=0.,
596+
name=None)
597+
598+
# There is no space for a gap between the pi/2-pulses and the adjacent pi-pulses,
599+
# so the resulting sequence should have 4 pulses + 1 gaps = 5 segments with non-zero duration
600+
assert sum(np.greater(driven_control.durations, 0.)) == 5
601+
602+
# ... of which 4 are X pulses (i.e. rabi_rotation > 0)
603+
assert sum(np.greater(driven_control.rabi_rates, 0.)) == 4
578604

579605
def test_free_evolution_conversion():
580606

0 commit comments

Comments
 (0)