Skip to content

Commit e6a39af

Browse files
authored
Merge pull request #94 from qctrl/dd
Fix pre-post rotations
2 parents ed86035 + b8ab67c commit e6a39af

File tree

2 files changed

+32
-55
lines changed

2 files changed

+32
-55
lines changed

qctrlopencontrols/dynamic_decoupling_sequences/predefined.py

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,12 @@
3939
def _add_pre_post_rotations(
4040
duration, offsets, rabi_rotations, azimuthal_angles, detuning_rotations
4141
):
42-
"""Adds a pre-post pi.2 rotation at the
43-
start and end of the sequence.
42+
"""Adds a pre and post X rotation at the start and end of the sequence.
4443
45-
The parameters of the pi/2-pulses are chosen in order to cancel out the
46-
product of the pulses in the DSS, so that its total effect in the
47-
absence of noise is an identity.
48-
49-
For a DSS that already produces an identity, this function adds X pi/2-pulses
50-
in opposite directions, so that they cancel out. If the DDS produces an X
51-
gate, the X pi/2-pulses will be in the same direction. If the DDS produces
52-
a Y (Z) gate, the pi/2-pulses are around the Y (Z) axis.
44+
Note that with these two pre and post X rotations, the net effect of the DDS does not
45+
necessarily have to be an identity, but it will always be either an identity or Z pi rotation.
46+
For example, given a CPMG sequence of odd number Y pi rotations in the middle with the pre
47+
(pi/2) and post(-pi/2) X rotations, the net effect will be a Z gate.
5348
5449
This function assumes that the sequences only have X, Y, and Z pi-pulses.
5550
An exception is thrown if that is not the case.
@@ -118,6 +113,12 @@ def _add_pre_post_rotations(
118113
},
119114
)
120115

116+
# parameters for pre-post pulses
117+
rabi_value = np.pi / 2
118+
detuning_value = 0
119+
initial_azimuthal = 0 # for pre-pulse
120+
final_azimuthal = 0 # for post-pulse
121+
121122
# The sequence will preserve the state |0> is it has an even number
122123
# of X and Y pi-pulses
123124
preserves_10 = (x_pi_pulses + y_pi_pulses) % 2 == 0
@@ -126,40 +127,12 @@ def _add_pre_post_rotations(
126127
# of Y and Z pi-pulses
127128
preserves_11 = (y_pi_pulses + z_pi_pulses) % 2 == 0
128129

129-
# When states |0> and |0>+|1> are preserved, the sequence already produces
130-
# an identity, so that we want the the pi/2-pulses to cancel each other out
131-
if preserves_10 and preserves_11:
132-
rabi_value = np.pi / 2
133-
initial_azimuthal = 0
130+
# the direction of the post rotation depends on the property of DDS.
131+
# if the net effect of the sequences is an identity gate or Y rotation, the post rotation
132+
# is chosen to be -pi/2 X pulse, otherwise use pi/2 X pulse, to ensure the net effect is an
133+
# identity or Z rotation.
134+
if (preserves_10 and preserves_11) or (not preserves_10 and not preserves_11):
134135
final_azimuthal = np.pi
135-
detuning_value = 0
136-
137-
# When only state |0>+|1> is not preserved, the sequence results in a Z rotation.
138-
# In this case, we want both pi/2-pulses to be in the Z direction,
139-
# so that the remaining rotation is cancelled out
140-
if preserves_10 and not preserves_11:
141-
rabi_value = 0
142-
initial_azimuthal = 0
143-
final_azimuthal = 0
144-
detuning_value = np.pi / 2
145-
146-
# When only state |0> is not preserved, the sequence results in an X rotation.
147-
# In this case, we want both pi/2-pulses to be in the X direction,
148-
# so that the remaining rotation is cancelled out
149-
if not preserves_10 and preserves_11:
150-
rabi_value = np.pi / 2
151-
initial_azimuthal = 0
152-
final_azimuthal = 0
153-
detuning_value = 0
154-
155-
# When neither state is preserved, the sequence results in a Y rotation.
156-
# In this case, we want both pi/2-pulses to be in the Y direction,
157-
# so that the remaining rotation is cancelled out
158-
if not preserves_10 and not preserves_11:
159-
rabi_value = np.pi / 2
160-
initial_azimuthal = np.pi / 2
161-
final_azimuthal = np.pi / 2
162-
detuning_value = 0
163136

164137
offsets = np.insert(offsets, [0, offsets.shape[0]], [0, duration],)
165138
rabi_rotations = np.insert(

tests/test_predefined_dynamical_decoupling.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
)
3737
from qctrlopencontrols.exceptions import ArgumentsValueError
3838

39+
SIGMA_X = np.array([[0.0, 1.0], [1.0, 0.0]])
40+
SIGMA_Y = np.array([[0.0, -1.0j], [1.0j, 0.0]])
41+
SIGMA_Z = np.array([[1.0, 0.0], [0.0, -1.0]])
42+
3943

4044
def test_ramsey():
4145

@@ -672,16 +676,13 @@ def test_attribute_values():
672676

673677
def _pulses_produce_identity(sequence):
674678
"""
675-
Tests if the pulses of a DDS sequence produce an identity in absence of noise.
679+
Tests if the pulses of a DDS sequence produce an identity or Z rotation in absence of noise.
676680
We check this by creating the unitary of each pulse and then multiplying them
677681
by each other to check the complete evolution.
678682
"""
679-
sigma_x = np.array([[0.0, 1.0], [1.0, 0.0]])
680-
sigma_y = np.array([[0.0, -1.0j], [1.0j, 0.0]])
681-
sigma_z = np.array([[1.0, 0.0], [0.0, -1.0]])
682683

683684
# The unitary evolution due to an instantaneous pulse can be written as
684-
# U = cos(|n|) I -i sin(|n|) *(n_x sigma_x + n_y sigma_y + n_z sigma_z)/|n|
685+
# U = cos(|n|) I -i sin(|n|) *(n_x SIGMA_x + n_y SIGMA_y + n_z SIGMA_z)/|n|
685686
# where n is a vector with components
686687
# n_x = rabi * cos(azimuthal)/2
687688
# n_y = rabi * sin(azimuthal)/2
@@ -697,9 +698,9 @@ def _pulses_produce_identity(sequence):
697698
mod_n = np.sqrt(n_x ** 2 + n_y ** 2 + n_z ** 2)
698699
unitary = (
699700
np.cos(mod_n) * np.identity(2)
700-
- 1.0j * (np.sin(mod_n) * n_x / mod_n) * sigma_x
701-
- 1.0j * (np.sin(mod_n) * n_y / mod_n) * sigma_y
702-
- 1.0j * (np.sin(mod_n) * n_z / mod_n) * sigma_z
701+
- 1.0j * (np.sin(mod_n) * n_x / mod_n) * SIGMA_X
702+
- 1.0j * (np.sin(mod_n) * n_y / mod_n) * SIGMA_Y
703+
- 1.0j * (np.sin(mod_n) * n_z / mod_n) * SIGMA_Z
703704
)
704705
matrix_product = np.matmul(unitary, matrix_product)
705706

@@ -708,7 +709,9 @@ def _pulses_produce_identity(sequence):
708709

709710
expected_matrix_product = np.identity(2)
710711

711-
return np.allclose(matrix_product, expected_matrix_product)
712+
return np.allclose(matrix_product, expected_matrix_product) or np.allclose(
713+
SIGMA_Z.dot(matrix_product), expected_matrix_product
714+
)
712715

713716

714717
def test_if_ramsey_sequence_is_identity():
@@ -762,7 +765,7 @@ def test_if_carr_purcell_sequence_with_even_pulses_is_identity():
762765
def test_if_cpmg_sequence_with_odd_pulses_is_identity():
763766
"""
764767
Tests if the product of the pulses in a CPMG sequence with pre/post
765-
pi/2-pulses is an identity, when the number of pulses is odd.
768+
pi/2-pulses and an extra Z rotation is an identity, when the number of pulses is odd.
766769
"""
767770
odd_cpmg_sequence = new_predefined_dds(
768771
scheme=CARR_PURCELL_MEIBOOM_GILL,
@@ -792,7 +795,7 @@ def test_if_cpmg_sequence_with_even_pulses_is_identity():
792795
def test_if_uhrig_sequence_with_odd_pulses_is_identity():
793796
"""
794797
Tests if the product of the pulses in an Uhrig sequence with pre/post
795-
pi/2-pulses is an identity, when the number of pulses is odd.
798+
pi/2-pulses and an extra Z rotation is an identity, when the number of pulses is odd.
796799
"""
797800
odd_uhrig_sequence = new_predefined_dds(
798801
scheme=UHRIG_SINGLE_AXIS,
@@ -924,7 +927,8 @@ def test_if_quadratic_sequence_with_even_pulses_is_identity():
924927
def test_if_quadratic_sequence_with_odd_inner_pulses_is_identity():
925928
"""
926929
Tests if the product of the pulses in a quadratic sequence with pre/post
927-
pi/2-pulses is an identity, when the total number of inner pulses is odd.
930+
pi/2-pulses and an extra rotation is an identity, when the total number
931+
of inner pulses is odd.
928932
"""
929933
inner_odd_quadratic_sequence = new_predefined_dds(
930934
scheme=QUADRATIC,

0 commit comments

Comments
 (0)