diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py index 0eb996b5..9820d4c6 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py @@ -109,7 +109,7 @@ def convert_dds_to_driven_control( Parameters ---------- dynamic_decoupling_sequence : qctrlopencontrols.DynamicDecouplingSequence - The base DDS + The base DDS. Its offsets should be sorted in ascending order in time. maximum_rabi_rate : float, optional Maximum Rabi Rate; Defaults to 2*pi. 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( minimum_segment_duration : float, optional If set, further restricts the duration of every segment of the Driven Controls. Defaults to 0, in which case it does not affect the duration of the pulses. + Must be greater or equal to 0, if set. kwargs : dict, optional Options to make the corresponding filter type. I.e. the options for primitive are described in doc for the PrimitivePulse class. @@ -165,6 +166,10 @@ def convert_dds_to_driven_control( {'type(dynamic_decoupling_sequence': type(dynamic_decoupling_sequence)}) + if minimum_segment_duration < 0.: + raise ArgumentsValueError('Minimum segment duration must be greater or equal to 0.', + {'minimum_segment_duration': minimum_segment_duration}) + _check_maximum_rotation_rate(maximum_rabi_rate, maximum_detuning_rate) sequence_duration = dynamic_decoupling_sequence.duration @@ -206,6 +211,14 @@ def convert_dds_to_driven_control( azimuthal_angles = np.append(azimuthal_angles, [0]) detuning_rotations = np.append(detuning_rotations, [0]) + # check that the offsets are correctly sorted in time + if any(np.diff(offsets) <= 0.): + raise ArgumentsValueError("Pulse timing could not be properly deduced from " + "the sequence offsets. Make sure all offsets are " + "in increasing order.", + {'dynamic_decoupling_sequence': dynamic_decoupling_sequence}, + extras={'offsets': offsets}) + offsets = offsets[np.newaxis, :] rabi_rotations = rabi_rotations[np.newaxis, :] azimuthal_angles = azimuthal_angles[np.newaxis, :] @@ -244,29 +257,14 @@ def convert_dds_to_driven_control( translation = pulse_start_ends[-1, 1] - sequence_duration pulse_start_ends[-1, :] = pulse_start_ends[-1, :] - translation - # three conditions to check - # 1. Control segment start times should be monotonically increasing - # 2. Control segment end times should be monotonically increasing - # 3. Adjacent segments should not be overlapping - if (np.any(pulse_start_ends[0:-1, 0] - pulse_start_ends[1:, 0] > 0.) or - np.any(pulse_start_ends[0:-1, 1] - pulse_start_ends[1:, 1] > 0.) or - np.any(pulse_start_ends[1:, 0]-pulse_start_ends[0:-1, 1] < 0.)): - - raise ArgumentsValueError('Pulse timing could not be properly deduced from ' - 'the sequence operation offsets. Try increasing the ' - 'maximum rabi rate or maximum detuning rate.', - {'dynamic_decoupling_sequence': dynamic_decoupling_sequence, - 'maximum_rabi_rate': maximum_rabi_rate, - 'maximum_detuning_rate': maximum_detuning_rate}, - extras={'deduced_pulse_start_timing': pulse_start_ends[:, 0], - 'deduced_pulse_end_timing': pulse_start_ends[:, 1]}) - # check if the minimum_segment_duration is respected in the gaps between the pulses + # as minimum_segment_duration >= 0, this also excludes overlaps gap_durations = pulse_start_ends[1:, 0] - pulse_start_ends[:-1, 1] if not np.all(np.logical_or(np.greater(gap_durations, minimum_segment_duration), np.isclose(gap_durations, minimum_segment_duration))): raise ArgumentsValueError("Distance between pulses does not respect minimum_segment_duration. " - "Try decreasing the minimum_segment_duration.", + "Try decreasing the minimum_segment_duration or increasing " + "the maximum_rabi_rate or the maximum_detuning_rate.", {'dynamic_decoupling_sequence': dynamic_decoupling_sequence, 'maximum_rabi_rate': maximum_rabi_rate, 'maximum_detuning_rate': maximum_detuning_rate, diff --git a/tests/test_dynamical_decoupling.py b/tests/test_dynamical_decoupling.py index f2a04f8d..52ab85ff 100644 --- a/tests/test_dynamical_decoupling.py +++ b/tests/test_dynamical_decoupling.py @@ -575,6 +575,32 @@ def test_conversion_of_pulses_with_arbitrary_detuning_rotations(): assert _all_greater_or_close(driven_control.duration, minimum_segment_duration) +def test_conversion_of_tightly_packed_sequence(): + """ + Tests if the method to convert a DDS to driven controls handles properly + a sequence tightly packed with pulses, where there is no time for a gap between + the pi/2-pulses and the adjacent pi-pulses. + """ + # create a sequence containing 2 pi-pulses and 2 pi/2-pulses at the extremities + dynamic_decoupling_sequence = DynamicDecouplingSequence( + duration=0.2, + offsets=np.array([0., 0.05, 0.15, 0.2]), + rabi_rotations=np.array([1.57079633, 3.14159265, 3.14159265, 1.57079633]), + azimuthal_angles=np.array([0., 0., 0., 0.]), + detuning_rotations=np.array([0., 0., 0., 0.]), + name=None) + + driven_control = convert_dds_to_driven_control(dynamic_decoupling_sequence, + maximum_rabi_rate= 20. * np.pi, + minimum_segment_duration=0., + name=None) + + # There is no space for a gap between the pi/2-pulses and the adjacent pi-pulses, + # so the resulting sequence should have 4 pulses + 1 gaps = 5 segments with non-zero duration + assert sum(np.greater(driven_control.durations, 0.)) == 5 + + # ... of which 4 are X pulses (i.e. rabi_rotation > 0) + assert sum(np.greater(driven_control.rabi_rates, 0.)) == 4 def test_free_evolution_conversion():