Skip to content

Commit 360bfa3

Browse files
authored
feat: Add predefined DRAG driven control (#183)
1 parent 9998178 commit 360bfa3

File tree

3 files changed

+202
-3
lines changed

3 files changed

+202
-3
lines changed

qctrlopencontrols/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
new_corpse_in_bb1_control,
2626
new_corpse_in_scrofulous_control,
2727
new_corpse_in_sk1_control,
28+
new_drag_control,
2829
new_gaussian_control,
2930
new_modulated_gaussian_control,
3031
new_primitive_control,
@@ -60,6 +61,7 @@
6061
"new_corpse_in_sk1_control",
6162
"new_gaussian_control",
6263
"new_modulated_gaussian_control",
64+
"new_drag_control",
6365
"new_primitive_control",
6466
"new_scrofulous_control",
6567
"new_sk1_control",

qctrlopencontrols/driven_controls/predefined.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,7 @@ def new_gaussian_control(
10061006
See Also
10071007
--------
10081008
new_modulated_gaussian_control
1009+
new_drag_control
10091010
10101011
Notes
10111012
-----
@@ -1206,3 +1207,132 @@ def new_modulated_gaussian_control(
12061207
detunings=np.zeros(segment_count),
12071208
durations=np.array([segment_duration] * segment_count),
12081209
)
1210+
1211+
1212+
def new_drag_control(
1213+
rabi_rotation: float,
1214+
segment_count: int,
1215+
duration: float,
1216+
width: float,
1217+
beta: float,
1218+
azimuthal_angle: float = 0.0,
1219+
name: Optional[str] = None,
1220+
) -> DrivenControl:
1221+
r"""
1222+
Generates a Gaussian driven control sequence with a first-order DRAG
1223+
(Derivative Removal by Adiabatic Gate) correction applied.
1224+
1225+
The addition of DRAG further reduces leakage out of the qubit subspace via an additional
1226+
off-quadrature corrective driving term proportional to the derivative of the Gaussian pulse.
1227+
1228+
Parameters
1229+
----------
1230+
rabi_rotation : float
1231+
Total Rabi rotation :math:`\theta` to be performed by the driven control.
1232+
segment_count : int
1233+
Number of segments in the control sequence.
1234+
duration : float
1235+
Total duration :math:`t_g` of the control sequence.
1236+
width : float
1237+
Width (standard deviation) :math:`\sigma` of the ideal Gaussian pulse.
1238+
beta : float
1239+
Amplitude scaling :math:`\beta` of the Gaussian derivative.
1240+
azimuthal_angle : float, optional
1241+
The azimuthal angle :math:`\phi` for the rotation. Defaults to 0.
1242+
name : str, optional
1243+
An optional string to name the control. Defaults to ``None``.
1244+
1245+
Returns
1246+
-------
1247+
DrivenControl
1248+
A control sequence as an instance of DrivenControl.
1249+
1250+
See Also
1251+
--------
1252+
new_gaussian_control
1253+
1254+
Notes
1255+
-----
1256+
A DRAG-corrected Gaussian driven control [#]_
1257+
applies a Hamiltonian consisting of a piecewise constant approximation to an ideal
1258+
Gaussian pulse controlling :math:`\sigma_x` while its derivative controls the
1259+
application of the :math:`\sigma_y` operator:
1260+
1261+
.. math::
1262+
H(t) = \frac{1}{2}(\Omega_G(t) \sigma_x + \beta \dot{\Omega}_G(t) \sigma_y)
1263+
1264+
where :math:`\Omega_G(t)` is simply given by :doc:`new_gaussian_control`. Optimally,
1265+
:math:`\beta = -\frac{\lambda_1^2}{4\Delta_2}` where :math:`\Delta_2` is the
1266+
anharmonicity of the system and :math:`\lambda_1` is the relative strength required
1267+
to drive a transition :math:`\lvert 1 \rangle \rightarrow \lvert 2 \rangle` vs.
1268+
:math:`\lvert 0 \rangle \rightarrow \lvert 1 \rangle`. Note
1269+
that this choice of :math:`\beta`, sometimes called "simple drag" or "half derivative",
1270+
is a first-order version of DRAG, and it excludes an additional detuning corrective term.
1271+
1272+
References
1273+
----------
1274+
.. [#] `Motzoi, F. et al. Physical Review Letters 103, 110501 (2009).
1275+
<https://doi.org/10.1103/PhysRevLett.103.110501>`_
1276+
.. [#] `J. M. Gambetta, F. Motzoi, S. T. Merkel, and F. K. Wilhelm,
1277+
Physical Review A 83, 012308 (2011).
1278+
<https://doi.org/10.1103/PhysRevA.83.012308>`_
1279+
"""
1280+
1281+
check_arguments(
1282+
duration > 0.0,
1283+
"Pulse duration must be greater than zero.",
1284+
{"duration": duration},
1285+
)
1286+
1287+
check_arguments(
1288+
segment_count > 0,
1289+
"Segment count must be greater than zero.",
1290+
{"segment_count": segment_count},
1291+
)
1292+
1293+
check_arguments(
1294+
width > 0.0,
1295+
"Width of ideal Gaussian pulse must be greater than zero.",
1296+
{"width": width},
1297+
)
1298+
1299+
# compute sampling parameters
1300+
segment_duration = duration / segment_count
1301+
segment_start_times = np.arange(segment_count) * segment_duration
1302+
segment_midpoints = segment_start_times + segment_duration / 2
1303+
1304+
# prepare a base (un-normalized) gaussian shaped pulse
1305+
gaussian_mean = duration / 2
1306+
base_gaussian_segments = np.exp(
1307+
-0.5 * ((segment_midpoints - gaussian_mean) / width) ** 2
1308+
)
1309+
1310+
# translate pulse by B/A (from Motzoi '09 paper) to ensure output is 0 at t=0
1311+
y_translation = -np.exp(-0.5 * ((0 - gaussian_mean) / width) ** 2)
1312+
base_gaussian_segments += y_translation
1313+
1314+
# compute A (from Motzoi '09 paper)
1315+
base_gaussian_total_rotation = np.sum(base_gaussian_segments) * segment_duration
1316+
normalization_factor = rabi_rotation / base_gaussian_total_rotation
1317+
1318+
x_quadrature_segments = base_gaussian_segments * normalization_factor
1319+
y_quadrature_segments = (
1320+
beta
1321+
* (gaussian_mean - segment_midpoints)
1322+
/ width ** 2
1323+
* (
1324+
x_quadrature_segments
1325+
- y_translation * normalization_factor # = B (from Motzoi '09 paper)
1326+
)
1327+
)
1328+
1329+
rabi_rates = np.sqrt(x_quadrature_segments ** 2 + y_quadrature_segments ** 2)
1330+
azimuthal_angles = np.arcsin(y_quadrature_segments / rabi_rates) + azimuthal_angle
1331+
1332+
return DrivenControl(
1333+
rabi_rates=rabi_rates,
1334+
azimuthal_angles=azimuthal_angles,
1335+
detunings=np.zeros(segment_count),
1336+
durations=np.array([segment_duration] * segment_count),
1337+
name=name,
1338+
)

tests/test_predefined_driven_controls.py

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
new_corpse_in_bb1_control,
2727
new_corpse_in_scrofulous_control,
2828
new_corpse_in_sk1_control,
29+
new_drag_control,
2930
new_gaussian_control,
3031
new_modulated_gaussian_control,
3132
new_primitive_control,
@@ -586,9 +587,6 @@ def gauss(time):
586587
# check pulse is Gaussian-shaped
587588
assert np.allclose(expected_normalized_pulse, normalized_pulse)
588589

589-
# compute total rotation of generated pulse
590-
rabi_rotation = np.dot(gaussian_control.rabi_rates, gaussian_control.durations)
591-
592590
# check number and duration of pulses
593591
assert len(gaussian_control.rabi_rates) == _segment_count
594592
assert np.allclose(gaussian_control.durations, _duration / _segment_count)
@@ -710,3 +708,72 @@ def test_modulated_gaussian_control_give_identity_gate():
710708

711709
for _u in unitaries:
712710
assert np.allclose(_u, np.eye(2))
711+
712+
713+
def test_drag_control():
714+
"""
715+
Tests DRAG control.
716+
"""
717+
_rabi_rotation = 0.5 * np.pi
718+
_segment_count = 25
719+
_duration = 0.002
720+
_width = 0.001
721+
_beta = 0.5
722+
_azimuthal_angle = np.pi / 8
723+
724+
drag_control = new_drag_control(
725+
rabi_rotation=_rabi_rotation,
726+
segment_count=_segment_count,
727+
duration=_duration,
728+
width=_width,
729+
beta=_beta,
730+
azimuthal_angle=_azimuthal_angle,
731+
)
732+
733+
_segment_width = _duration / _segment_count
734+
midpoints = np.linspace(
735+
_segment_width / 2, _duration - _segment_width / 2, _segment_count
736+
)
737+
738+
def gauss(time):
739+
return np.exp(-0.5 * ((time - _duration / 2) / _width) ** 2)
740+
741+
def d_gauss(time):
742+
return -(time - _duration / 2) / _width ** 2 * gauss(time)
743+
744+
# note: 'x' and 'y' here refer to x and y in the frame rotated by _azimuthal_angle
745+
746+
expected_normalized_x_pulse = (gauss(midpoints) - gauss(0)) / max(
747+
gauss(midpoints) - gauss(0)
748+
)
749+
expected_normalized_y_pulse = (
750+
_beta * d_gauss(midpoints) / max(abs(_beta * d_gauss(midpoints)))
751+
)
752+
753+
# compute x and y pulses in the rotated frame
754+
x_pulse = (
755+
np.cos(_azimuthal_angle) * drag_control.amplitude_x
756+
+ np.sin(_azimuthal_angle) * drag_control.amplitude_y
757+
)
758+
y_pulse = (
759+
np.cos(-_azimuthal_angle) * drag_control.amplitude_y
760+
+ np.sin(-_azimuthal_angle) * drag_control.amplitude_x
761+
)
762+
normalized_x_pulse = x_pulse / max(abs(x_pulse))
763+
normalized_y_pulse = y_pulse / max(abs(y_pulse))
764+
765+
# compute total rotation of generated pulses
766+
total_x_rotation = np.dot(x_pulse, drag_control.durations)
767+
total_y_rotation = np.dot(y_pulse, drag_control.durations)
768+
769+
# check pulses are correctly shaped
770+
assert np.allclose(expected_normalized_x_pulse, normalized_x_pulse)
771+
assert np.allclose(expected_normalized_y_pulse, normalized_y_pulse)
772+
773+
# check number and duration of pulses
774+
assert len(drag_control.rabi_rates) == _segment_count
775+
assert np.allclose(drag_control.durations, _duration / _segment_count)
776+
777+
# check total rotation of pulses
778+
assert np.isclose(total_x_rotation, _rabi_rotation)
779+
assert np.isclose(total_y_rotation, 0)

0 commit comments

Comments
 (0)