From 2f4bc126a125723aecffb35544f6c5adeadc2575 Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Mon, 23 Mar 2020 11:38:56 +1100 Subject: [PATCH 01/12] Reversing direction of final pi/2 pulse when number of pulses is even --- .../driven_controls.py | 4 ++-- .../dynamic_decoupling_sequences/predefined.py | 18 ++++++++++++++---- tests/test_predefined_dynamical_decoupling.py | 14 ++++++++------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py index 83c13964..0ceeefbb 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py @@ -231,10 +231,10 @@ def convert_dds_to_driven_control( pulse_start_ends[op_idx, 1] = pulse_mid_points[op_idx] + half_pulse_duration else: pulse_start_ends[op_idx, 0] = pulse_mid_points[op_idx] - \ - 0.5 * operations[3, op_idx] / maximum_detuning_rate + 0.5 * np.abs(operations[3, op_idx]) / maximum_detuning_rate pulse_start_ends[op_idx, 1] = pulse_mid_points[op_idx] + \ - 0.5 * operations[3, op_idx] / maximum_detuning_rate + 0.5 * np.abs(operations[3, op_idx]) / maximum_detuning_rate # check if any of the pulses have gone outside the time limit [0, sequence_duration] # if yes, adjust the segment timing diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index 0cd55415..201a75c0 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -35,9 +35,13 @@ def _add_pre_post_rotations( rabi_rotations, azimuthal_angles, detuning_rotations): - """Adds a pre-post pi.2 rotation at the + """ + Adds a pre-post pi.2 rotation at the start and end of the sequence. + The sign of the final pi/2-pulse is inverted if the number of pulses (offsets) + in the original sequence is even, in order to make sure that the output is an + identity gate. Parameters ---------- @@ -59,6 +63,12 @@ def _add_pre_post_rotations( resulting after the addition of pi/2 pulses at the start and end of the sequence. """ + # The azimuthal angle of the final pulse is 0 if the number of offsets is odd, + # and pi if the number of offsets is even. + final_azimuthal = 0. + if len(offsets)%2 == 0: + final_azimuthal = np.pi + offsets = np.insert(offsets, [0, offsets.shape[0]], # pylint: disable=unsubscriptable-object [0, duration]) @@ -69,7 +79,7 @@ def _add_pre_post_rotations( azimuthal_angles = np.insert( azimuthal_angles, [0, azimuthal_angles.shape[0]], # pylint: disable=unsubscriptable-object - [0, 0]) + [0, final_azimuthal]) detuning_rotations = np.insert( detuning_rotations, [0, detuning_rotations.shape[0]], # pylint: disable=unsubscriptable-object @@ -79,7 +89,7 @@ def _add_pre_post_rotations( def new_predefined_dds(scheme=SPIN_ECHO, **kwargs): - """Create a new instance of ne of the predefined + """Create a new instance of one of the predefined dynamic decoupling sequence Parameters @@ -209,7 +219,7 @@ def _new_ramsey_sequence(duration=None, if pre_post_rotation: offsets = duration * np.array([0.0, 1.]) rabi_rotations = np.array([np.pi/2, np.pi/2]) - azimuthal_angles = np.zeros(offsets.shape) + azimuthal_angles = np.array([0., np.pi]) detuning_rotations = np.zeros(offsets.shape) return DynamicDecouplingSequence( diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index 6a25c170..a3d8eeea 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -59,7 +59,7 @@ def test_ramsey(): pre_post_rotation=True) _rabi_rotations = np.array([np.pi/2, np.pi/2]) - _azimuthal_angles = np.array([0., 0.]) + _azimuthal_angles = np.array([0., np.pi]) _detuning_rotations = np.array([0., 0.]) assert np.allclose(_rabi_rotations, sequence.rabi_rotations) @@ -140,7 +140,7 @@ def test_curr_purcell(): _spacing * 0.5 + 2 * _spacing, _spacing * 0.5 + 3 * _spacing, duration]) _rabi_rotations = np.array([np.pi/2, np.pi, np.pi, np.pi, np.pi, np.pi/2]) - _azimuthal_angles = np.array([0, 0, 0, 0, 0, 0]) + _azimuthal_angles = np.array([0, 0, 0, 0, 0, np.pi]) _detuning_rotations = np.array([0, 0, 0, 0, 0, 0]) assert np.allclose(_offsets, sequence.offsets) @@ -183,7 +183,7 @@ def test_curr_purcell_meiboom_sequence(): # pylint: disable=invalid-name _offsets = np.array([0, _spacing * 0.5, _spacing * 0.5 + _spacing, _spacing * 0.5 + 2 * _spacing, _spacing * 0.5 + 3 * _spacing, duration]) _rabi_rotations = np.array([np.pi/2, np.pi, np.pi, np.pi, np.pi, np.pi/2]) - _azimuthal_angles = np.array([0, np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2, 0]) + _azimuthal_angles = np.array([0, np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2, np.pi]) _detuning_rotations = np.array([0, 0, 0, 0, 0, 0]) assert np.allclose(_offsets, sequence.offsets) @@ -231,7 +231,7 @@ def test_uhrig_single_axis_sequence(): [0, duration]) _rabi_rotations = np.array([np.pi/2, np.pi, np.pi, np.pi, np.pi, np.pi/2]) - _azimuthal_angles = np.array([0., np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2, 0.]) + _azimuthal_angles = np.array([0., np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2, np.pi]) _detuning_rotations = np.array([0., 0, 0, 0, 0, 0.]) assert np.allclose(_offsets, sequence.offsets) @@ -278,7 +278,7 @@ def test_periodic_single_axis_sequence(): # pylint: disable=invalid-name [0, duration]) _rabi_rotations = np.array([np.pi/2, np.pi, np.pi, np.pi, np.pi, np.pi/2]) - _azimuthal_angles = np.array([0, 0, 0, 0, 0, 0]) + _azimuthal_angles = np.array([0, 0, 0, 0, 0, np.pi]) _detuning_rotations = np.array([0, 0, 0, 0, 0, 0]) assert np.allclose(_offsets, sequence.offsets) @@ -339,6 +339,7 @@ def test_walsh_single_axis_sequence(): _rabi_rotations = np.insert(_rabi_rotations, [0, _rabi_rotations.shape[0]], [np.pi/2, np.pi/2]) _azimuthal_angles = np.zeros(_offsets.shape) + _azimuthal_angles[-1] = np.pi _detuning_rotations = np.zeros(_offsets.shape) assert np.allclose(_offsets, sequence.offsets) @@ -419,6 +420,7 @@ def test_quadratic_sequence(): [0, 0]) _azimuthal_angles = np.zeros(_offsets.shape) + _azimuthal_angles[-1] = np.pi assert np.allclose(_offsets, sequence.offsets) assert np.allclose(_rabi_rotations, sequence.rabi_rotations) @@ -523,7 +525,7 @@ def test_xyconcatenated_sequence(): _azimuthal_angles = np.insert( _azimuthal_angles, [0, _azimuthal_angles.shape[0]], # pylint: disable=unsubscriptable-object - [0, 0]) + [0, np.pi]) _detuning_rotations = np.insert( _detuning_rotations, [0, _detuning_rotations.shape[0]], # pylint: disable=unsubscriptable-object From addbbc49ec018d4dfd1015170c811b3f4e1c85d9 Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Mon, 23 Mar 2020 11:49:07 +1100 Subject: [PATCH 02/12] Reversing unnecessary changes --- .../dynamic_decoupling_sequences/driven_controls.py | 4 ++-- qctrlopencontrols/dynamic_decoupling_sequences/predefined.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py index 0ceeefbb..83c13964 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/driven_controls.py @@ -231,10 +231,10 @@ def convert_dds_to_driven_control( pulse_start_ends[op_idx, 1] = pulse_mid_points[op_idx] + half_pulse_duration else: pulse_start_ends[op_idx, 0] = pulse_mid_points[op_idx] - \ - 0.5 * np.abs(operations[3, op_idx]) / maximum_detuning_rate + 0.5 * operations[3, op_idx] / maximum_detuning_rate pulse_start_ends[op_idx, 1] = pulse_mid_points[op_idx] + \ - 0.5 * np.abs(operations[3, op_idx]) / maximum_detuning_rate + 0.5 * operations[3, op_idx] / maximum_detuning_rate # check if any of the pulses have gone outside the time limit [0, sequence_duration] # if yes, adjust the segment timing diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index 201a75c0..3c43e060 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -35,8 +35,7 @@ def _add_pre_post_rotations( rabi_rotations, azimuthal_angles, detuning_rotations): - """ - Adds a pre-post pi.2 rotation at the + """Adds a pre-post pi.2 rotation at the start and end of the sequence. The sign of the final pi/2-pulse is inverted if the number of pulses (offsets) @@ -89,7 +88,7 @@ def _add_pre_post_rotations( def new_predefined_dds(scheme=SPIN_ECHO, **kwargs): - """Create a new instance of one of the predefined + """Create a new instance of ne of the predefined dynamic decoupling sequence Parameters From 82130c81f05cbe37ee2610c3259f633ed7f0514f Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Mon, 23 Mar 2020 17:25:30 +1100 Subject: [PATCH 03/12] Enhancing method of inverting pi/2-pulses, adding unit tests to all dynamical decoupling sequences --- .../predefined.py | 27 +- tests/test_predefined_dynamical_decoupling.py | 288 ++++++++++++++++++ 2 files changed, 309 insertions(+), 6 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index 3c43e060..33cb4713 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -38,9 +38,10 @@ def _add_pre_post_rotations( """Adds a pre-post pi.2 rotation at the start and end of the sequence. - The sign of the final pi/2-pulse is inverted if the number of pulses (offsets) - in the original sequence is even, in order to make sure that the output is an - identity gate. + The sign of the final pi/2-pulse is inverted if the number of X or Y pi-pulses + in the original sequence is even (odd), as long as the number of Y or Z pi-pulses + is also even (odd). This is necessary to make sure that all the contributions of + X gates cancel out. Parameters ---------- @@ -62,10 +63,24 @@ def _add_pre_post_rotations( resulting after the addition of pi/2 pulses at the start and end of the sequence. """ - # The azimuthal angle of the final pulse is 0 if the number of offsets is odd, - # and pi if the number of offsets is even. + # The azimuthal angle of the final pulse is 0 if the number of X/Y pi-pulses is odd, + # and pi if the number of offsets is even. However, these two values invert again if + # there is an odd number of Y/Z pi-pulses in the sequence. These conditions mean that + # the sign of the final pi/2-pulse should be inverted is the numbers X/Y pulses and + # Y/Z pulses are either both odd or both even. final_azimuthal = 0. - if len(offsets)%2 == 0: + + is_x_pi_pulse = np.logical_and(np.isclose(rabi_rotations, np.pi), + np.isclose(azimuthal_angles, 0.)) + is_y_pi_pulse = np.logical_and(np.isclose(rabi_rotations, np.pi), + np.isclose(azimuthal_angles, np.pi)) + is_z_pi_pulse = np.logical_and(np.isclose(rabi_rotations, 0.), + np.isclose(detuning_rotations, np.pi)) + + is_xy_pi_pulse = np.where(np.logical_or(is_x_pi_pulse, is_y_pi_pulse), 1, 0) + is_yz_pi_pulse = np.where(np.logical_or(is_y_pi_pulse, is_z_pi_pulse), 1, 0) + + if (sum(is_xy_pi_pulse)%2 == sum(is_yz_pi_pulse)%2): final_azimuthal = np.pi offsets = np.insert(offsets, diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index a3d8eeea..ea1bef9e 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -582,3 +582,291 @@ def test_attribute_values(): _ = new_predefined_dds( scheme=XY_CONCATENATED, duration=-2, concatenation_order=-1) + +def _pulses_produce_identity(sequence, accept_sigma_z=False): + """ + Tests if the pulses of a DDS sequence produce an identity in absence of noise. + We check this by creating the unitary of each pulse and then multiplying them + together to check their evolution + """ + sigma_x = np.array([[0., 1.], [1., 0.]]) + sigma_y = np.array([[0., -1.j], [1.j, 0.]]) + sigma_z = np.array([[1., 0.], [0., -1.]]) + + # The unitary evolution due to an instantaneous pulse can be written as + # U = cos(|n|) I -i sin(|n|) *(n_x sigma_x + n_y sigma_y + n_z sigma_z)/|n| + # where n is a vector with components + # n_x = rabi * cos(azimuthal)/2 + # n_y = rabi * sin(azimuthal)/2 + # n_z = detuning/2 + + matrix_product = np.identity(2) + for rabi, azimuth, detuning in zip(sequence.rabi_rotations, + sequence.azimuthal_angles, + sequence.detuning_rotations): + n_x = rabi * np.cos(azimuth)/2. + n_y = rabi * np.sin(azimuth)/2. + n_z = detuning/2. + mod_n = np.sqrt(n_x**2 + n_y**2 + n_z**2) + unitary = ( + np.cos(mod_n) * np.identity(2) + -1.j * (np.sin(mod_n)*n_x/mod_n) * sigma_x + -1.j * (np.sin(mod_n)*n_y/mod_n) * sigma_y + -1.j * (np.sin(mod_n)*n_z/mod_n) * sigma_z + ) + matrix_product = np.matmul(unitary, matrix_product) + + # Removes global phase + matrix_product *= np.exp(-1.j* np.angle(matrix_product[0][0])) + + if accept_sigma_z: + print (matrix_product) + matrix_product[1][1] *= -1. + + return np.allclose(matrix_product, np.identity(2)) + +def test_if_ramsey_sequence_is_identity(): + """ + Tests if the product of the pulses in the Ramsey sequence with pre/post + pi/2-pulses is an identity. + """ + ramsey_sequence = new_predefined_dds( + scheme='Ramsey', + duration=10., + pre_post_rotation=True) + + assert _pulses_produce_identity(ramsey_sequence) + +def test_if_spin_echo_sequence_is_identity(): + """ + Tests if the product of the pulses in a Spin Echo sequence with pre/post + pi/2-pulses is an identity. + """ + spin_echo_sequence = new_predefined_dds( + scheme=SPIN_ECHO, + duration=10., + pre_post_rotation=True) + + assert _pulses_produce_identity(spin_echo_sequence) + +def test_if_carr_purcell_sequence_with_odd_pulses_is_identity(): + """ + Tests if the product of the pulses in a Carr-Purcell sequence with pre/post + pi/2-pulses is an identity, when the number of pulses is odd. + """ + odd_carr_purcell_sequence = new_predefined_dds( + scheme=CARR_PURCELL, + duration=10., + number_of_offsets=7, + pre_post_rotation=True) + + assert _pulses_produce_identity(odd_carr_purcell_sequence) + +def test_if_carr_purcell_sequence_with_even_pulses_is_identity(): + """ + Tests if the product of the pulses in a Carr-Purcell sequence with pre/post + pi/2-pulses is an identity, when the number of pulses is even. + """ + even_carr_purcell_sequence = new_predefined_dds( + scheme=CARR_PURCELL, + duration=10., + number_of_offsets=8, + pre_post_rotation=True) + + assert _pulses_produce_identity(even_carr_purcell_sequence) + +def test_if_cpmg_sequence_with_odd_pulses_is_identity(): + """ + Tests if the product of the pulses in a CPMG sequence with pre/post + pi/2-pulses is an identity, when the number of pulses is odd. + """ + odd_cpmg_sequence = new_predefined_dds( + scheme=CARR_PURCELL_MEIBOOM_GILL, + duration=10., + number_of_offsets=7, + pre_post_rotation=True) + + assert _pulses_produce_identity(odd_cpmg_sequence, + accept_sigma_z=True) + +def test_if_cpmg_sequence_with_even_pulses_is_identity(): + """ + Tests if the product of the pulses in a CPMG sequence with pre/post + pi/2-pulses is an identity, when the number of pulses is even. + """ + even_cpmg_sequence = new_predefined_dds( + scheme=CARR_PURCELL_MEIBOOM_GILL, + duration=10., + number_of_offsets=8, + pre_post_rotation=True) + + assert _pulses_produce_identity(even_cpmg_sequence) + +def test_if_uhrig_sequence_with_odd_pulses_is_identity(): + """ + Tests if the product of the pulses in a Uhrig sequence with pre/post + pi/2-pulses is an identity, when the number of pulses is odd. + """ + even_uhrig_sequence = new_predefined_dds( + scheme=UHRIG_SINGLE_AXIS, + duration=10., + number_of_offsets=7, + pre_post_rotation=True) + + assert _pulses_produce_identity(even_uhrig_sequence, + accept_sigma_z=True) + +def test_if_uhrig_sequence_with_even_pulses_is_identity(): + """ + Tests if the product of the pulses in a Uhrig sequence with pre/post + pi/2-pulses is an identity, when the number of pulses is even. + """ + even_uhrig_sequence = new_predefined_dds( + scheme=UHRIG_SINGLE_AXIS, + duration=10., + number_of_offsets=8, + pre_post_rotation=True) + + assert _pulses_produce_identity(even_uhrig_sequence) + +def test_if_periodic_sequence_with_odd_pulses_is_identity(): + """ + Tests if the product of the pulses in a periodic DDS with pre/post + pi/2-pulses is an identity, when the number of pulses is odd. + """ + odd_periodic_sequence = new_predefined_dds( + scheme=PERIODIC_SINGLE_AXIS, + duration=10., + number_of_offsets=7, + pre_post_rotation=True) + + assert _pulses_produce_identity(odd_periodic_sequence) + +def test_if_periodic_sequence_with_even_pulses_is_identity(): + """ + Tests if the product of the pulses in a periodic DDS with pre/post + pi/2-pulses is an identity, when the number of pulses is even. + """ + even_periodic_sequence = new_predefined_dds( + scheme=PERIODIC_SINGLE_AXIS, + duration=10., + number_of_offsets=8, + pre_post_rotation=True) + + assert _pulses_produce_identity(even_periodic_sequence) + +def test_if_walsh_sequence_with_odd_pulses_is_identity(): + """ + Tests if the product of the pulses in a Walsh sequence with pre/post + pi/2-pulses is an identity, when the number of pulses is odd. + """ + odd_walsh_sequence = new_predefined_dds( + scheme=WALSH_SINGLE_AXIS, + duration=10., + paley_order=7, + pre_post_rotation=True) + + # A Walsh sequence with paley_order 7 has 5 pi-pulses + 2 pi/2-pulses, + # see https://arxiv.org/pdf/1109.6002.pdf + assert len(odd_walsh_sequence.offsets) == 5 + 2 + + assert _pulses_produce_identity(odd_walsh_sequence) + +def test_if_walsh_sequence_with_even_pulses_is_identity(): + """ + Tests if the product of the pulses in a quadratic sequence with pre/post + pi/2-pulses is an identity, when the number of pulses is even. + """ + even_walsh_sequence = new_predefined_dds( + scheme=WALSH_SINGLE_AXIS, + duration=10., + paley_order=6, + pre_post_rotation=True) + + # A Walsh sequence with paley_order 7 has 4 pi-pulses + 2 pi/2-pulses, + # see https://arxiv.org/pdf/1109.6002.pdf + assert len(even_walsh_sequence.offsets) == 4 + 2 + + assert _pulses_produce_identity(even_walsh_sequence) + +def test_if_quadratic_sequence_with_odd_pulses_is_identity(): + """ + Tests if the product of the pulses in a quadratic sequence with pre/post + pi/2-pulses is an identity, when the total number of pulses is odd. + """ + odd_quadratic_sequence = new_predefined_dds( + scheme=QUADRATIC, + duration=10., + number_inner_offsets=7, + number_outer_offsets=7, + pre_post_rotation=True) + + # n_outer + n_inner*(n_outer+1) pi-pulses + 2 pi/2-pulses + # total number here is odd + assert len(odd_quadratic_sequence.offsets) == 7 + 7 * (7+1) + 2 + + assert _pulses_produce_identity(odd_quadratic_sequence) + + +def test_if_quadratic_sequence_with_even_pulses_is_identity(): + """ + Tests if the product of the pulses in a quadratic sequence with pre/post + pi/2-pulses is an identity, when the total number of pulses is even. + """ + even_quadratic_sequence = new_predefined_dds( + scheme=QUADRATIC, + duration=10., + number_inner_offsets=8, + number_outer_offsets=8, + pre_post_rotation=True) + + # n_outer + n_inner*(n_outer+1) pi-pulses + 2 pi/2-pulses + # total number here is even + assert len(even_quadratic_sequence.offsets) == 8 + 8 * (8+1) + 2 + + assert _pulses_produce_identity(even_quadratic_sequence) + +def test_if_quadratic_sequence_with_odd_inner_pulses_is_identity(): + """ + Tests if the product of the pulses in a quadratic sequence with pre/post + pi/2-pulses is an identity, when the total number of inner pulses is odd. + """ + inner_odd_quadratic_sequence = new_predefined_dds( + scheme=QUADRATIC, + duration=10., + number_inner_offsets=7, + number_outer_offsets=8, + pre_post_rotation=True) + + # n_outer + n_inner*(n_outer+1) pi-pulses + 2 pi/2-pulses + # total number here is odd + assert len(inner_odd_quadratic_sequence.offsets) == 8 + 7 * (8+1) + 2 + + assert _pulses_produce_identity(inner_odd_quadratic_sequence, + accept_sigma_z=True) + +def test_if_x_concatenated_sequence_is_identity(): + """ + Tests if the product of the pulses in an X concatenated sequence with pre/post + pi/2-pulses is an identity. + """ + x_concat_sequence = new_predefined_dds( + scheme=X_CONCATENATED, + duration=10., + concatenation_order=4, + pre_post_rotation=True) + + assert _pulses_produce_identity(x_concat_sequence) + +def test_if_xy_concatenated_sequence_is_identity(): + """ + Tests if the product of the pulses in an XY concatenated sequence with pre/post + pi/2-pulses is an identity. + """ + xy_concat_sequence = new_predefined_dds( + scheme=XY_CONCATENATED, + duration=10., + concatenation_order=4, + pre_post_rotation=True) + + assert _pulses_produce_identity(xy_concat_sequence) From 725e62194ed884d8c9d43babf3ba07afb957e1b1 Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Mon, 23 Mar 2020 18:00:13 +1100 Subject: [PATCH 04/12] Improvements in the text, one additional test --- .../predefined.py | 15 ++++--- tests/test_predefined_dynamical_decoupling.py | 45 ++++++++++++++----- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index 33cb4713..db5313b8 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -41,7 +41,7 @@ def _add_pre_post_rotations( The sign of the final pi/2-pulse is inverted if the number of X or Y pi-pulses in the original sequence is even (odd), as long as the number of Y or Z pi-pulses is also even (odd). This is necessary to make sure that all the contributions of - X gates cancel out. + the X gates cancel out. Parameters ---------- @@ -63,13 +63,14 @@ def _add_pre_post_rotations( resulting after the addition of pi/2 pulses at the start and end of the sequence. """ - # The azimuthal angle of the final pulse is 0 if the number of X/Y pi-pulses is odd, - # and pi if the number of offsets is even. However, these two values invert again if - # there is an odd number of Y/Z pi-pulses in the sequence. These conditions mean that - # the sign of the final pi/2-pulse should be inverted is the numbers X/Y pulses and - # Y/Z pulses are either both odd or both even. + # If there is an even number of Y and Z pi-pulses in the sequence, the azimuthal angle + # of the final pulse is 0 if the number of X and Y pi-pulses is odd, and pi if the number + # of X/Y pulses is even. These two values are swapped if there is an odd number of Y/Z + # pi-pulses in the sequence. Together, these conditions mean that the azimuthal angle of + # the final pi/2-pulse is pi if the numbers of X/Y pulses and Y/Z pulses have the same parity. final_azimuthal = 0. + # These lists have True if the pulse is of the specificed type, False if it is not is_x_pi_pulse = np.logical_and(np.isclose(rabi_rotations, np.pi), np.isclose(azimuthal_angles, 0.)) is_y_pi_pulse = np.logical_and(np.isclose(rabi_rotations, np.pi), @@ -77,9 +78,11 @@ def _add_pre_post_rotations( is_z_pi_pulse = np.logical_and(np.isclose(rabi_rotations, 0.), np.isclose(detuning_rotations, np.pi)) + # These lists have '1' if the pulse is X/Y (Y/Z), and '0' if it is not is_xy_pi_pulse = np.where(np.logical_or(is_x_pi_pulse, is_y_pi_pulse), 1, 0) is_yz_pi_pulse = np.where(np.logical_or(is_y_pi_pulse, is_z_pi_pulse), 1, 0) + # Azimuthal angles is pi if the parity is the same if (sum(is_xy_pi_pulse)%2 == sum(is_yz_pi_pulse)%2): final_azimuthal = np.pi diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index ea1bef9e..edd76f83 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -583,11 +583,11 @@ def test_attribute_values(): scheme=XY_CONCATENATED, duration=-2, concatenation_order=-1) -def _pulses_produce_identity(sequence, accept_sigma_z=False): +def _pulses_produce_identity(sequence, expect_sigma_z=False): """ Tests if the pulses of a DDS sequence produce an identity in absence of noise. We check this by creating the unitary of each pulse and then multiplying them - together to check their evolution + by each other to check the complete evolution. """ sigma_x = np.array([[0., 1.], [1., 0.]]) sigma_y = np.array([[0., -1.j], [1.j, 0.]]) @@ -616,14 +616,16 @@ def _pulses_produce_identity(sequence, accept_sigma_z=False): ) matrix_product = np.matmul(unitary, matrix_product) - # Removes global phase + # Remove global phase matrix_product *= np.exp(-1.j* np.angle(matrix_product[0][0])) - if accept_sigma_z: - print (matrix_product) - matrix_product[1][1] *= -1. + expected_matrix_product = np.identity(2) - return np.allclose(matrix_product, np.identity(2)) + # In case the expected output is sigma_z rather than identity + if expect_sigma_z: + expected_matrix_product = sigma_z + + return np.allclose(matrix_product, expected_matrix_product) def test_if_ramsey_sequence_is_identity(): """ @@ -687,7 +689,7 @@ def test_if_cpmg_sequence_with_odd_pulses_is_identity(): pre_post_rotation=True) assert _pulses_produce_identity(odd_cpmg_sequence, - accept_sigma_z=True) + expect_sigma_z=True) def test_if_cpmg_sequence_with_even_pulses_is_identity(): """ @@ -704,7 +706,7 @@ def test_if_cpmg_sequence_with_even_pulses_is_identity(): def test_if_uhrig_sequence_with_odd_pulses_is_identity(): """ - Tests if the product of the pulses in a Uhrig sequence with pre/post + Tests if the product of the pulses in an Uhrig sequence with pre/post pi/2-pulses is an identity, when the number of pulses is odd. """ even_uhrig_sequence = new_predefined_dds( @@ -714,11 +716,11 @@ def test_if_uhrig_sequence_with_odd_pulses_is_identity(): pre_post_rotation=True) assert _pulses_produce_identity(even_uhrig_sequence, - accept_sigma_z=True) + expect_sigma_z=True) def test_if_uhrig_sequence_with_even_pulses_is_identity(): """ - Tests if the product of the pulses in a Uhrig sequence with pre/post + Tests if the product of the pulses in an Uhrig sequence with pre/post pi/2-pulses is an identity, when the number of pulses is even. """ even_uhrig_sequence = new_predefined_dds( @@ -843,7 +845,26 @@ def test_if_quadratic_sequence_with_odd_inner_pulses_is_identity(): assert len(inner_odd_quadratic_sequence.offsets) == 8 + 7 * (8+1) + 2 assert _pulses_produce_identity(inner_odd_quadratic_sequence, - accept_sigma_z=True) + expect_sigma_z=True) + + +def test_if_quadratic_sequence_with_even_inner_pulses_is_identity(): + """ + Tests if the product of the pulses in a quadratic sequence with pre/post + pi/2-pulses is an identity, when the total number of inner pulses is even. + """ + inner_odd_quadratic_sequence = new_predefined_dds( + scheme=QUADRATIC, + duration=10., + number_inner_offsets=8, + number_outer_offsets=7, + pre_post_rotation=True) + + # n_outer + n_inner*(n_outer+1) pi-pulses + 2 pi/2-pulses + # total number here is even + assert len(inner_odd_quadratic_sequence.offsets) == 7 + 8 * (7+1) + 2 + + assert _pulses_produce_identity(inner_odd_quadratic_sequence) def test_if_x_concatenated_sequence_is_identity(): """ From 6e4c259fbac25cf79fd8849e94f46815de2cc3ce Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Mon, 23 Mar 2020 18:05:28 +1100 Subject: [PATCH 05/12] Fixing typo --- tests/test_predefined_dynamical_decoupling.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index edd76f83..88b2b250 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -853,7 +853,7 @@ def test_if_quadratic_sequence_with_even_inner_pulses_is_identity(): Tests if the product of the pulses in a quadratic sequence with pre/post pi/2-pulses is an identity, when the total number of inner pulses is even. """ - inner_odd_quadratic_sequence = new_predefined_dds( + inner_even_quadratic_sequence = new_predefined_dds( scheme=QUADRATIC, duration=10., number_inner_offsets=8, @@ -862,9 +862,9 @@ def test_if_quadratic_sequence_with_even_inner_pulses_is_identity(): # n_outer + n_inner*(n_outer+1) pi-pulses + 2 pi/2-pulses # total number here is even - assert len(inner_odd_quadratic_sequence.offsets) == 7 + 8 * (7+1) + 2 + assert len(inner_even_quadratic_sequence.offsets) == 7 + 8 * (7+1) + 2 - assert _pulses_produce_identity(inner_odd_quadratic_sequence) + assert _pulses_produce_identity(inner_even_quadratic_sequence) def test_if_x_concatenated_sequence_is_identity(): """ From bb382bc6482715be1154141337636ec4ab687edc Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Mon, 23 Mar 2020 18:07:05 +1100 Subject: [PATCH 06/12] Fixing other typo --- tests/test_predefined_dynamical_decoupling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index 88b2b250..c380610c 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -709,13 +709,13 @@ def test_if_uhrig_sequence_with_odd_pulses_is_identity(): Tests if the product of the pulses in an Uhrig sequence with pre/post pi/2-pulses is an identity, when the number of pulses is odd. """ - even_uhrig_sequence = new_predefined_dds( + odd_uhrig_sequence = new_predefined_dds( scheme=UHRIG_SINGLE_AXIS, duration=10., number_of_offsets=7, pre_post_rotation=True) - assert _pulses_produce_identity(even_uhrig_sequence, + assert _pulses_produce_identity(odd_uhrig_sequence, expect_sigma_z=True) def test_if_uhrig_sequence_with_even_pulses_is_identity(): From 0271a4dc23a27c9a46cea68d6d8ce34f785b9039 Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Tue, 24 Mar 2020 10:51:45 +1100 Subject: [PATCH 07/12] Improving algorithm by simplifying conditions --- .../predefined.py | 48 ++++++++++--------- tests/test_predefined_dynamical_decoupling.py | 2 +- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index db5313b8..efdae7b6 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -38,10 +38,16 @@ def _add_pre_post_rotations( """Adds a pre-post pi.2 rotation at the start and end of the sequence. - The sign of the final pi/2-pulse is inverted if the number of X or Y pi-pulses - in the original sequence is even (odd), as long as the number of Y or Z pi-pulses - is also even (odd). This is necessary to make sure that all the contributions of - the X gates cancel out. + The direction of rotation of the final pi/2-pulse is inverted if there + is an even number of X pi-pulses in the sequence. This is necessary to make + sure that the sequences produce an identity in absence of noise. In practice, + this inversion is equivalent to setting up an azimuthal angle of pi, as opposed + to the angle 0 for the initial pulse. If there is an odd number of Z pi-pulses + in the sequence, the direction of this rotation has to be inverted again. + Together, these conditions add up to inverting the final pi/2-pulse whenever + the sum of X and Z pi-pulses in the sequence is an even number. (Note that Y + pi-pulses do not interfere in this count, as they are equivalent to adding + both an X and a Z pi-pulse to the sequence.) Parameters ---------- @@ -63,27 +69,23 @@ def _add_pre_post_rotations( resulting after the addition of pi/2 pulses at the start and end of the sequence. """ - # If there is an even number of Y and Z pi-pulses in the sequence, the azimuthal angle - # of the final pulse is 0 if the number of X and Y pi-pulses is odd, and pi if the number - # of X/Y pulses is even. These two values are swapped if there is an odd number of Y/Z - # pi-pulses in the sequence. Together, these conditions mean that the azimuthal angle of - # the final pi/2-pulse is pi if the numbers of X/Y pulses and Y/Z pulses have the same parity. + # Setting the azimuthal angle of the final pi/2-pulse as 0 by default, + # which is the same value as the initial pi/2-pulse final_azimuthal = 0. - # These lists have True if the pulse is of the specificed type, False if it is not - is_x_pi_pulse = np.logical_and(np.isclose(rabi_rotations, np.pi), - np.isclose(azimuthal_angles, 0.)) - is_y_pi_pulse = np.logical_and(np.isclose(rabi_rotations, np.pi), - np.isclose(azimuthal_angles, np.pi)) - is_z_pi_pulse = np.logical_and(np.isclose(rabi_rotations, 0.), - np.isclose(detuning_rotations, np.pi)) - - # These lists have '1' if the pulse is X/Y (Y/Z), and '0' if it is not - is_xy_pi_pulse = np.where(np.logical_or(is_x_pi_pulse, is_y_pi_pulse), 1, 0) - is_yz_pi_pulse = np.where(np.logical_or(is_y_pi_pulse, is_z_pi_pulse), 1, 0) - - # Azimuthal angles is pi if the parity is the same - if (sum(is_xy_pi_pulse)%2 == sum(is_yz_pi_pulse)%2): + # These lists have '1' if the pulse is of the specificed type, '0' if it is not + is_x_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, np.pi), + np.isclose(azimuthal_angles, 0.)), + 1, + 0) + is_z_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, 0.), + np.isclose(detuning_rotations, np.pi)), + 1, + 0) + + # Setting the azimuthal angle of the pi/2-pulse to pi if the sum of the number + # X pi-pulses and Z pi-pulses is an even number + if (sum(is_x_pi_pulse) + sum(is_z_pi_pulse))%2 == 0: final_azimuthal = np.pi offsets = np.insert(offsets, diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index c380610c..1a32a1c6 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -805,7 +805,7 @@ def test_if_quadratic_sequence_with_odd_pulses_is_identity(): # n_outer + n_inner*(n_outer+1) pi-pulses + 2 pi/2-pulses # total number here is odd - assert len(odd_quadratic_sequence.offsets) == 7 + 7 * (7+1) + 2 + assert len(odd_quadratic_sequence.offsets) == 7 + 7 * (7+1) + 2 assert _pulses_produce_identity(odd_quadratic_sequence) From 5331565772830474b8348cdf27f86f94540f6a14 Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Tue, 24 Mar 2020 11:04:49 +1100 Subject: [PATCH 08/12] Minor textual changes --- .../dynamic_decoupling_sequences/predefined.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index efdae7b6..9240cc46 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -73,7 +73,7 @@ def _add_pre_post_rotations( # which is the same value as the initial pi/2-pulse final_azimuthal = 0. - # These lists have '1' if the pulse is of the specificed type, '0' if it is not + # These lists have 1 if the pulse is of the specificed type, 0 if it is not is_x_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, np.pi), np.isclose(azimuthal_angles, 0.)), 1, @@ -83,8 +83,8 @@ def _add_pre_post_rotations( 1, 0) - # Setting the azimuthal angle of the pi/2-pulse to pi if the sum of the number - # X pi-pulses and Z pi-pulses is an even number + # Setting the azimuthal angle of the final pi/2-pulse to pi if the sum of + # the number of X pi-pulses and Z pi-pulses is even if (sum(is_x_pi_pulse) + sum(is_z_pi_pulse))%2 == 0: final_azimuthal = np.pi From 19b0f6a6a889ccfc78750a845c2c95fc167f5295 Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Tue, 24 Mar 2020 13:51:42 +1100 Subject: [PATCH 09/12] Changin algorithm in preparation to eliminate spurious sigma_z --- .../predefined.py | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index 9240cc46..7a7289a8 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -49,6 +49,10 @@ def _add_pre_post_rotations( pi-pulses do not interfere in this count, as they are equivalent to adding both an X and a Z pi-pulse to the sequence.) + Moreover, if the number of Y and Z pi-pulses is odd, we have to replace both + pi/2-pulses with pulses in a direction that cancels out the remaining sigma_z + operation. If the pulses for the initial and final + Parameters ---------- duration: float @@ -69,24 +73,44 @@ def _add_pre_post_rotations( resulting after the addition of pi/2 pulses at the start and end of the sequence. """ - # Setting the azimuthal angle of the final pi/2-pulse as 0 by default, - # which is the same value as the initial pi/2-pulse - final_azimuthal = 0. + # These settings correspond to the case where the pulses of the sequence + # already yield an identity, so that we want the the pi/2-pulses to cancel + # each other out + rabi_value = np.pi / 2 + initial_azimuthal = 0 + final_azimuthal = np.pi + initial_detuning = 0 + final_detuning = 0 # These lists have 1 if the pulse is of the specificed type, 0 if it is not is_x_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, np.pi), np.isclose(azimuthal_angles, 0.)), 1, 0) + is_y_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, np.pi), + np.isclose(azimuthal_angles, np.pi/2.)), + 1, + 0) is_z_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, 0.), np.isclose(detuning_rotations, np.pi)), 1, 0) - # Setting the azimuthal angle of the final pi/2-pulse to pi if the sum of - # the number of X pi-pulses and Z pi-pulses is even - if (sum(is_x_pi_pulse) + sum(is_z_pi_pulse))%2 == 0: - final_azimuthal = np.pi + # The sequence results in an X gate, rather than the identity, if the number + # of X and Y gates is odd + remainder_x = ((sum(is_x_pi_pulse) + sum(is_y_pi_pulse))%2 == 1) + + # The sequence results in a Z gate, rather than the identity, if the number + # of Y and Z gates is odd + remainder_z = ((sum(is_y_pi_pulse) + sum(is_z_pi_pulse))%2 == 1) + + # If there is an X gate left over, but no Z gate, we just invert the direction + # of the last pi/2-pulse to cancel it out + if remainder_x and not remainder_z: + final_azimuthal = 0 + + if not remainder_x and remainder_z: + final_azimuthal = 0 offsets = np.insert(offsets, [0, offsets.shape[0]], # pylint: disable=unsubscriptable-object @@ -94,15 +118,15 @@ def _add_pre_post_rotations( rabi_rotations = np.insert( rabi_rotations, [0, rabi_rotations.shape[0]], # pylint: disable=unsubscriptable-object - [np.pi / 2, np.pi / 2]) + [rabi_value, rabi_value]) azimuthal_angles = np.insert( azimuthal_angles, [0, azimuthal_angles.shape[0]], # pylint: disable=unsubscriptable-object - [0, final_azimuthal]) + [initial_azimuthal, final_azimuthal]) detuning_rotations = np.insert( detuning_rotations, [0, detuning_rotations.shape[0]], # pylint: disable=unsubscriptable-object - [0, 0]) + [initial_detuning, final_detuning]) return offsets, rabi_rotations, azimuthal_angles, detuning_rotations From 4a301f5e1e554a67f4655bef1a95e07516fb5757 Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Tue, 24 Mar 2020 14:13:00 +1100 Subject: [PATCH 10/12] eliminating spurious sigma_z --- .../predefined.py | 40 +++++++++++-------- tests/test_predefined_dynamical_decoupling.py | 15 ++----- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index 7a7289a8..dd028915 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -38,20 +38,17 @@ def _add_pre_post_rotations( """Adds a pre-post pi.2 rotation at the start and end of the sequence. - The direction of rotation of the final pi/2-pulse is inverted if there - is an even number of X pi-pulses in the sequence. This is necessary to make - sure that the sequences produce an identity in absence of noise. In practice, - this inversion is equivalent to setting up an azimuthal angle of pi, as opposed - to the angle 0 for the initial pulse. If there is an odd number of Z pi-pulses - in the sequence, the direction of this rotation has to be inverted again. - Together, these conditions add up to inverting the final pi/2-pulse whenever - the sum of X and Z pi-pulses in the sequence is an even number. (Note that Y - pi-pulses do not interfere in this count, as they are equivalent to adding - both an X and a Z pi-pulse to the sequence.) - - Moreover, if the number of Y and Z pi-pulses is odd, we have to replace both - pi/2-pulses with pulses in a direction that cancels out the remaining sigma_z - operation. If the pulses for the initial and final + The parameters of the pi/2-pulses are chosen in order to cancel out the + product of the pulses in the DSS, so that its total effect in the + absence of noise is an identity. + + For a DSS that already produces an identity, this function adds X pi/2-pulses + in opposite directions, so that they cancel out. If the DDS produces an X + gate, the X pi/2-pulses will be in the same direction. If the DDS produces + a Y (Z) gate, the pi/2-pulses are around the Y (Z) axis. + + This function assumes that the sequences only have X, Y, and Z pi-pulses. + Usage of other pulses will result in a DDS that does not result in an identity. Parameters ---------- @@ -79,8 +76,7 @@ def _add_pre_post_rotations( rabi_value = np.pi / 2 initial_azimuthal = 0 final_azimuthal = np.pi - initial_detuning = 0 - final_detuning = 0 + detuning_value = 0 # These lists have 1 if the pulse is of the specificed type, 0 if it is not is_x_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, np.pi), @@ -109,8 +105,18 @@ def _add_pre_post_rotations( if remainder_x and not remainder_z: final_azimuthal = 0 + # If there is a Y gate left over, we want the pi/2-pulses to be in the Y + # direction, so that the remaining gate is cancelled out + if remainder_x and remainder_z: + initial_azimuthal = np.pi / 2 + final_azimuthal = np.pi / 2 + + # If there is a Z gate left over, we want both pi/2-pulses to be in the Z + # direction, so that the remaining gate is cancelled out if not remainder_x and remainder_z: + rabi_value = 0 final_azimuthal = 0 + detuning_value = np.pi / 2 offsets = np.insert(offsets, [0, offsets.shape[0]], # pylint: disable=unsubscriptable-object @@ -126,7 +132,7 @@ def _add_pre_post_rotations( detuning_rotations = np.insert( detuning_rotations, [0, detuning_rotations.shape[0]], # pylint: disable=unsubscriptable-object - [initial_detuning, final_detuning]) + [detuning_value, detuning_value]) return offsets, rabi_rotations, azimuthal_angles, detuning_rotations diff --git a/tests/test_predefined_dynamical_decoupling.py b/tests/test_predefined_dynamical_decoupling.py index 1a32a1c6..8ea850d4 100644 --- a/tests/test_predefined_dynamical_decoupling.py +++ b/tests/test_predefined_dynamical_decoupling.py @@ -583,7 +583,7 @@ def test_attribute_values(): scheme=XY_CONCATENATED, duration=-2, concatenation_order=-1) -def _pulses_produce_identity(sequence, expect_sigma_z=False): +def _pulses_produce_identity(sequence): """ Tests if the pulses of a DDS sequence produce an identity in absence of noise. We check this by creating the unitary of each pulse and then multiplying them @@ -621,10 +621,6 @@ def _pulses_produce_identity(sequence, expect_sigma_z=False): expected_matrix_product = np.identity(2) - # In case the expected output is sigma_z rather than identity - if expect_sigma_z: - expected_matrix_product = sigma_z - return np.allclose(matrix_product, expected_matrix_product) def test_if_ramsey_sequence_is_identity(): @@ -688,8 +684,7 @@ def test_if_cpmg_sequence_with_odd_pulses_is_identity(): number_of_offsets=7, pre_post_rotation=True) - assert _pulses_produce_identity(odd_cpmg_sequence, - expect_sigma_z=True) + assert _pulses_produce_identity(odd_cpmg_sequence) def test_if_cpmg_sequence_with_even_pulses_is_identity(): """ @@ -715,8 +710,7 @@ def test_if_uhrig_sequence_with_odd_pulses_is_identity(): number_of_offsets=7, pre_post_rotation=True) - assert _pulses_produce_identity(odd_uhrig_sequence, - expect_sigma_z=True) + assert _pulses_produce_identity(odd_uhrig_sequence) def test_if_uhrig_sequence_with_even_pulses_is_identity(): """ @@ -844,8 +838,7 @@ def test_if_quadratic_sequence_with_odd_inner_pulses_is_identity(): # total number here is odd assert len(inner_odd_quadratic_sequence.offsets) == 8 + 7 * (8+1) + 2 - assert _pulses_produce_identity(inner_odd_quadratic_sequence, - expect_sigma_z=True) + assert _pulses_produce_identity(inner_odd_quadratic_sequence) def test_if_quadratic_sequence_with_even_inner_pulses_is_identity(): From 7bd7e54d3f06cf16c7243e9deb894b2897623325 Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Tue, 24 Mar 2020 14:19:42 +1100 Subject: [PATCH 11/12] using count_nonzero --- .../predefined.py | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index dd028915..e61ea470 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -78,27 +78,21 @@ def _add_pre_post_rotations( final_azimuthal = np.pi detuning_value = 0 - # These lists have 1 if the pulse is of the specificed type, 0 if it is not - is_x_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, np.pi), - np.isclose(azimuthal_angles, 0.)), - 1, - 0) - is_y_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, np.pi), - np.isclose(azimuthal_angles, np.pi/2.)), - 1, - 0) - is_z_pi_pulse = np.where(np.logical_and(np.isclose(rabi_rotations, 0.), - np.isclose(detuning_rotations, np.pi)), - 1, - 0) + # Count the number of X, Y, and Z pi-pulses + x_pi_pulses = np.count_nonzero(np.logical_and(np.isclose(rabi_rotations, np.pi), + np.isclose(azimuthal_angles, 0.))) + y_pi_pulses = np.count_nonzero(np.logical_and(np.isclose(rabi_rotations, np.pi), + np.isclose(azimuthal_angles, np.pi/2.))) + z_pi_pulses = np.count_nonzero(np.logical_and(np.isclose(rabi_rotations, 0.), + np.isclose(detuning_rotations, np.pi))) # The sequence results in an X gate, rather than the identity, if the number # of X and Y gates is odd - remainder_x = ((sum(is_x_pi_pulse) + sum(is_y_pi_pulse))%2 == 1) + remainder_x = ((x_pi_pulses + y_pi_pulses)%2 == 1) # The sequence results in a Z gate, rather than the identity, if the number # of Y and Z gates is odd - remainder_z = ((sum(is_y_pi_pulse) + sum(is_z_pi_pulse))%2 == 1) + remainder_z = ((y_pi_pulses + z_pi_pulses)%2 == 1) # If there is an X gate left over, but no Z gate, we just invert the direction # of the last pi/2-pulse to cancel it out From f6e422b98722c2a5265e436e528bd35b4ba728cf Mon Sep 17 00:00:00 2001 From: Leonardo Andreta de Castro Date: Tue, 24 Mar 2020 16:51:11 +1100 Subject: [PATCH 12/12] Adding checks to input, making logic more explicit --- .../predefined.py | 99 ++++++++++++------- 1 file changed, 62 insertions(+), 37 deletions(-) diff --git a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py index e61ea470..fbb05509 100644 --- a/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py +++ b/qctrlopencontrols/dynamic_decoupling_sequences/predefined.py @@ -48,7 +48,7 @@ def _add_pre_post_rotations( a Y (Z) gate, the pi/2-pulses are around the Y (Z) axis. This function assumes that the sequences only have X, Y, and Z pi-pulses. - Usage of other pulses will result in a DDS that does not result in an identity. + An exception is thrown if that is not the case. Parameters ---------- @@ -68,49 +68,74 @@ def _add_pre_post_rotations( tuple Containing the (offsets, rabi_rotations, azimuthal_angles, detuning_rotations) resulting after the addition of pi/2 pulses at the start and end of the sequence. - """ - - # These settings correspond to the case where the pulses of the sequence - # already yield an identity, so that we want the the pi/2-pulses to cancel - # each other out - rabi_value = np.pi / 2 - initial_azimuthal = 0 - final_azimuthal = np.pi - detuning_value = 0 + Raises + ----- + ArgumentsValueError + Raised when sequence does not consist solely of X, Y, and Z pi-pulses. + """ # Count the number of X, Y, and Z pi-pulses - x_pi_pulses = np.count_nonzero(np.logical_and(np.isclose(rabi_rotations, np.pi), - np.isclose(azimuthal_angles, 0.))) - y_pi_pulses = np.count_nonzero(np.logical_and(np.isclose(rabi_rotations, np.pi), - np.isclose(azimuthal_angles, np.pi/2.))) - z_pi_pulses = np.count_nonzero(np.logical_and(np.isclose(rabi_rotations, 0.), - np.isclose(detuning_rotations, np.pi))) - - # The sequence results in an X gate, rather than the identity, if the number - # of X and Y gates is odd - remainder_x = ((x_pi_pulses + y_pi_pulses)%2 == 1) - - # The sequence results in a Z gate, rather than the identity, if the number - # of Y and Z gates is odd - remainder_z = ((y_pi_pulses + z_pi_pulses)%2 == 1) - - # If there is an X gate left over, but no Z gate, we just invert the direction - # of the last pi/2-pulse to cancel it out - if remainder_x and not remainder_z: + x_pi_pulses = np.count_nonzero(np.logical_and.reduce((np.isclose(rabi_rotations, np.pi), + np.isclose(azimuthal_angles, 0.), + np.isclose(detuning_rotations, 0.)))) + y_pi_pulses = np.count_nonzero(np.logical_and.reduce((np.isclose(rabi_rotations, np.pi), + np.isclose(azimuthal_angles, np.pi/2.), + np.isclose(detuning_rotations, 0.)))) + z_pi_pulses = np.count_nonzero(np.logical_and.reduce((np.isclose(rabi_rotations, 0.), + np.isclose(azimuthal_angles, 0.), + np.isclose(detuning_rotations, np.pi)))) + + # Check if the sequence consists solely of X, Y, and Z pi-pulses + if len(offsets) != x_pi_pulses + y_pi_pulses + z_pi_pulses: + raise ArgumentsValueError( + 'Sequence contains pulses that are not X, Y, or Z pi-pulses.', + {'rabi_rotations': rabi_rotations, + 'azimuthal_angles': azimuthal_angles, + 'detuning_rotations': detuning_rotations}) + + # The sequence will preserve the state |0> is it has an even number + # of X and Y pi-pulses + preserves_10 = ((x_pi_pulses + y_pi_pulses)%2 == 0) + + # The sequence will preserve the state |0>+|1> is it has an even number + # of Y and Z pi-pulses + preserves_11 = ((y_pi_pulses + z_pi_pulses)%2 == 0) + + # When states |0> and |0>+|1> are preserved, the sequence already produces + # an identity, so that we want the the pi/2-pulses to cancel each other out + if preserves_10 and preserves_11: + rabi_value = np.pi / 2 + initial_azimuthal = 0 + final_azimuthal = np.pi + detuning_value = 0 + + # When only state |0>+|1> is not preserved, the sequence results in a Z rotation. + # In this case, we want both pi/2-pulses to be in the Z direction, + # so that the remaining rotation is cancelled out + if preserves_10 and not preserves_11: + rabi_value = 0 + initial_azimuthal = 0 + final_azimuthal = 0 + detuning_value = np.pi / 2 + + # When only state |0> is not preserved, the sequence results in an X rotation. + # In this case, we want both pi/2-pulses to be in the X direction, + # so that the remaining rotation is cancelled out + if not preserves_10 and preserves_11: + rabi_value = np.pi / 2 + initial_azimuthal = 0 final_azimuthal = 0 + detuning_value = 0 - # If there is a Y gate left over, we want the pi/2-pulses to be in the Y - # direction, so that the remaining gate is cancelled out - if remainder_x and remainder_z: + # When neither state is preserved, the sequence results in a Y rotation. + # In this case, we want both pi/2-pulses to be in the Y direction, + # so that the remaining rotation is cancelled out + if not preserves_10 and not preserves_11: + rabi_value = np.pi / 2 initial_azimuthal = np.pi / 2 final_azimuthal = np.pi / 2 + detuning_value = 0 - # If there is a Z gate left over, we want both pi/2-pulses to be in the Z - # direction, so that the remaining gate is cancelled out - if not remainder_x and remainder_z: - rabi_value = 0 - final_azimuthal = 0 - detuning_value = np.pi / 2 offsets = np.insert(offsets, [0, offsets.shape[0]], # pylint: disable=unsubscriptable-object