Skip to content

Commit 31fcc7a

Browse files
authored
[QENG-716] Fix DDS that do not produce identities (#61)
* Modify private function _add_pre_post_rotations() so that the pi/2-pulses always make the predefined DDS add up to an identity. This involves changing the direction of rotation of the final pi/2-pulse, or changing both pulses from X pulses into Y pulses or Z pulses. * Add unit tests to verify if all kinds of predefined DDS produce identities.
1 parent e413281 commit 31fcc7a

File tree

2 files changed

+392
-10
lines changed

2 files changed

+392
-10
lines changed

qctrlopencontrols/dynamic_decoupling_sequences/predefined.py

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ def _add_pre_post_rotations(
3838
"""Adds a pre-post pi.2 rotation at the
3939
start and end of the sequence.
4040
41+
The parameters of the pi/2-pulses are chosen in order to cancel out the
42+
product of the pulses in the DSS, so that its total effect in the
43+
absence of noise is an identity.
44+
45+
For a DSS that already produces an identity, this function adds X pi/2-pulses
46+
in opposite directions, so that they cancel out. If the DDS produces an X
47+
gate, the X pi/2-pulses will be in the same direction. If the DDS produces
48+
a Y (Z) gate, the pi/2-pulses are around the Y (Z) axis.
49+
50+
This function assumes that the sequences only have X, Y, and Z pi-pulses.
51+
An exception is thrown if that is not the case.
4152
4253
Parameters
4354
----------
@@ -57,23 +68,90 @@ def _add_pre_post_rotations(
5768
tuple
5869
Containing the (offsets, rabi_rotations, azimuthal_angles, detuning_rotations)
5970
resulting after the addition of pi/2 pulses at the start and end of the sequence.
71+
72+
Raises
73+
-----
74+
ArgumentsValueError
75+
Raised when sequence does not consist solely of X, Y, and Z pi-pulses.
6076
"""
77+
# Count the number of X, Y, and Z pi-pulses
78+
x_pi_pulses = np.count_nonzero(np.logical_and.reduce((np.isclose(rabi_rotations, np.pi),
79+
np.isclose(azimuthal_angles, 0.),
80+
np.isclose(detuning_rotations, 0.))))
81+
y_pi_pulses = np.count_nonzero(np.logical_and.reduce((np.isclose(rabi_rotations, np.pi),
82+
np.isclose(azimuthal_angles, np.pi/2.),
83+
np.isclose(detuning_rotations, 0.))))
84+
z_pi_pulses = np.count_nonzero(np.logical_and.reduce((np.isclose(rabi_rotations, 0.),
85+
np.isclose(azimuthal_angles, 0.),
86+
np.isclose(detuning_rotations, np.pi))))
87+
88+
# Check if the sequence consists solely of X, Y, and Z pi-pulses
89+
if len(offsets) != x_pi_pulses + y_pi_pulses + z_pi_pulses:
90+
raise ArgumentsValueError(
91+
'Sequence contains pulses that are not X, Y, or Z pi-pulses.',
92+
{'rabi_rotations': rabi_rotations,
93+
'azimuthal_angles': azimuthal_angles,
94+
'detuning_rotations': detuning_rotations})
95+
96+
# The sequence will preserve the state |0> is it has an even number
97+
# of X and Y pi-pulses
98+
preserves_10 = ((x_pi_pulses + y_pi_pulses)%2 == 0)
99+
100+
# The sequence will preserve the state |0>+|1> is it has an even number
101+
# of Y and Z pi-pulses
102+
preserves_11 = ((y_pi_pulses + z_pi_pulses)%2 == 0)
103+
104+
# When states |0> and |0>+|1> are preserved, the sequence already produces
105+
# an identity, so that we want the the pi/2-pulses to cancel each other out
106+
if preserves_10 and preserves_11:
107+
rabi_value = np.pi / 2
108+
initial_azimuthal = 0
109+
final_azimuthal = np.pi
110+
detuning_value = 0
111+
112+
# When only state |0>+|1> is not preserved, the sequence results in a Z rotation.
113+
# In this case, we want both pi/2-pulses to be in the Z direction,
114+
# so that the remaining rotation is cancelled out
115+
if preserves_10 and not preserves_11:
116+
rabi_value = 0
117+
initial_azimuthal = 0
118+
final_azimuthal = 0
119+
detuning_value = np.pi / 2
120+
121+
# When only state |0> is not preserved, the sequence results in an X rotation.
122+
# In this case, we want both pi/2-pulses to be in the X direction,
123+
# so that the remaining rotation is cancelled out
124+
if not preserves_10 and preserves_11:
125+
rabi_value = np.pi / 2
126+
initial_azimuthal = 0
127+
final_azimuthal = 0
128+
detuning_value = 0
129+
130+
# When neither state is preserved, the sequence results in a Y rotation.
131+
# In this case, we want both pi/2-pulses to be in the Y direction,
132+
# so that the remaining rotation is cancelled out
133+
if not preserves_10 and not preserves_11:
134+
rabi_value = np.pi / 2
135+
initial_azimuthal = np.pi / 2
136+
final_azimuthal = np.pi / 2
137+
detuning_value = 0
138+
61139

62140
offsets = np.insert(offsets,
63141
[0, offsets.shape[0]], # pylint: disable=unsubscriptable-object
64142
[0, duration])
65143
rabi_rotations = np.insert(
66144
rabi_rotations,
67145
[0, rabi_rotations.shape[0]], # pylint: disable=unsubscriptable-object
68-
[np.pi / 2, np.pi / 2])
146+
[rabi_value, rabi_value])
69147
azimuthal_angles = np.insert(
70148
azimuthal_angles,
71149
[0, azimuthal_angles.shape[0]], # pylint: disable=unsubscriptable-object
72-
[0, 0])
150+
[initial_azimuthal, final_azimuthal])
73151
detuning_rotations = np.insert(
74152
detuning_rotations,
75153
[0, detuning_rotations.shape[0]], # pylint: disable=unsubscriptable-object
76-
[0, 0])
154+
[detuning_value, detuning_value])
77155

78156
return offsets, rabi_rotations, azimuthal_angles, detuning_rotations
79157

@@ -209,7 +287,7 @@ def _new_ramsey_sequence(duration=None,
209287
if pre_post_rotation:
210288
offsets = duration * np.array([0.0, 1.])
211289
rabi_rotations = np.array([np.pi/2, np.pi/2])
212-
azimuthal_angles = np.zeros(offsets.shape)
290+
azimuthal_angles = np.array([0., np.pi])
213291
detuning_rotations = np.zeros(offsets.shape)
214292

215293
return DynamicDecouplingSequence(

0 commit comments

Comments
 (0)