Skip to content

Commit 9998178

Browse files
authored
feat: Add predefined Gaussian driven control (#182)
1 parent 5628d74 commit 9998178

File tree

3 files changed

+155
-0
lines changed

3 files changed

+155
-0
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_gaussian_control,
2829
new_modulated_gaussian_control,
2930
new_primitive_control,
3031
new_scrofulous_control,
@@ -57,6 +58,7 @@
5758
"new_corpse_in_bb1_control",
5859
"new_corpse_in_scrofulous_control",
5960
"new_corpse_in_sk1_control",
61+
"new_gaussian_control",
6062
"new_modulated_gaussian_control",
6163
"new_primitive_control",
6264
"new_scrofulous_control",

qctrlopencontrols/driven_controls/predefined.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,108 @@ def new_wamf1_control(
973973
)
974974

975975

976+
def new_gaussian_control(
977+
rabi_rotation: float,
978+
segment_count: int,
979+
duration: float,
980+
width: float,
981+
name: Optional[str] = None,
982+
) -> DrivenControl:
983+
r"""
984+
Generates a Gaussian driven control sequence.
985+
986+
Gaussian driven controls mitigate leakage out of the qubit subspace.
987+
988+
Parameters
989+
----------
990+
rabi_rotation : float
991+
Total Rabi rotation :math:`\theta` to be performed by the driven control.
992+
segment_count : int
993+
Number of segments in the control sequence.
994+
duration : float
995+
Total duration :math:`t_g` of the control sequence.
996+
width : float
997+
Width (standard deviation) :math:`\sigma` of the ideal Gaussian pulse.
998+
name : str, optional
999+
An optional string to name the control. Defaults to ``None``.
1000+
1001+
Returns
1002+
-------
1003+
DrivenControl
1004+
A control sequence as an instance of DrivenControl.
1005+
1006+
See Also
1007+
--------
1008+
new_modulated_gaussian_control
1009+
1010+
Notes
1011+
-----
1012+
A Gaussian driven control [#]_ consists of a piecewise constant approximation
1013+
to an ideal Gaussian pulse:
1014+
1015+
.. math::
1016+
\mathcal{E}_G (t) = A \exp \left[- \frac{(t - t_g/2)^2}{2\sigma^2}\right] - B
1017+
1018+
where the two additional parameters :math:`A, B` chosen such that
1019+
:math:`\int_{0}^{t_g} \mathcal{E}_G \,dt = \theta` and :math:`\mathcal{E}_G(0) = 0`.
1020+
1021+
Relative values of segments are determined by sampling the ideal Gaussian at the midpoints
1022+
of the segments.
1023+
1024+
References
1025+
----------
1026+
.. [#] `Motzoi, F. et al. Physical Review Letters 103, 110501 (2009)
1027+
<https://doi.org/10.1103/PhysRevLett.103.110501>`_
1028+
"""
1029+
1030+
check_arguments(
1031+
duration > 0.0,
1032+
"Pulse duration must be greater than zero.",
1033+
{"duration": duration},
1034+
)
1035+
1036+
check_arguments(
1037+
segment_count > 0,
1038+
"Segment count must be greater than zero.",
1039+
{"segment_count": segment_count},
1040+
)
1041+
1042+
check_arguments(
1043+
width > 0.0,
1044+
"Width of ideal Gaussian pulse must be greater than zero.",
1045+
{"width": width},
1046+
)
1047+
1048+
# work out exact segment duration
1049+
segment_duration = duration / segment_count
1050+
segment_start_times = np.arange(segment_count) * segment_duration
1051+
segment_midpoints = segment_start_times + segment_duration / 2
1052+
1053+
# prepare a base (un-normalized) gaussian shaped pulse
1054+
gaussian_mean = duration / 2
1055+
base_gaussian_segments = np.exp(
1056+
-0.5 * ((segment_midpoints - gaussian_mean) / width) ** 2
1057+
)
1058+
1059+
# translate pulse by B/A (from Motzoi paper) to ensure output is 0 at t=0
1060+
y_translation = -np.exp(-0.5 * ((0 - gaussian_mean) / width) ** 2)
1061+
base_gaussian_segments += y_translation
1062+
1063+
# scale segments such that their net effect matches the desired rotation
1064+
base_gaussian_total_rotation = np.sum(base_gaussian_segments) * segment_duration
1065+
gaussian_segments = (
1066+
base_gaussian_segments / base_gaussian_total_rotation
1067+
) * rabi_rotation
1068+
1069+
return DrivenControl(
1070+
rabi_rates=gaussian_segments,
1071+
azimuthal_angles=np.zeros(segment_count),
1072+
detunings=np.zeros(segment_count),
1073+
durations=np.array([segment_duration] * segment_count),
1074+
name=name,
1075+
)
1076+
1077+
9761078
def new_modulated_gaussian_control(
9771079
maximum_rabi_rate: float,
9781080
minimum_segment_duration: float,
@@ -1003,6 +1105,10 @@ def new_modulated_gaussian_control(
10031105
-------
10041106
DrivenControl
10051107
A control sequence as an instance of DrivenControl.
1108+
1109+
See Also
1110+
--------
1111+
new_gaussian_control
10061112
"""
10071113

10081114
check_arguments(

tests/test_predefined_driven_controls.py

Lines changed: 47 additions & 0 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_gaussian_control,
2930
new_modulated_gaussian_control,
3031
new_primitive_control,
3132
new_scrofulous_control,
@@ -550,6 +551,52 @@ def test_walsh_control():
550551
assert np.allclose(pi_on_4_segments, _pi_on_4_segments)
551552

552553

554+
def test_gaussian_control():
555+
"""
556+
Tests Gaussian control.
557+
"""
558+
_rabi_rotation = np.pi
559+
_segment_count = 10
560+
_duration = 0.01
561+
_width = 0.002
562+
563+
gaussian_control = new_gaussian_control(
564+
rabi_rotation=_rabi_rotation,
565+
segment_count=_segment_count,
566+
duration=_duration,
567+
width=_width,
568+
)
569+
570+
# compute total rotation of generated pulse
571+
rabi_rotation = np.dot(gaussian_control.rabi_rates, gaussian_control.durations)
572+
573+
_segment_width = _duration / _segment_count
574+
midpoints = np.linspace(
575+
_segment_width / 2, _duration - _segment_width / 2, _segment_count
576+
)
577+
578+
def gauss(time):
579+
return np.exp(-0.5 * ((time - _duration / 2) / _width) ** 2)
580+
581+
expected_normalized_pulse = (gauss(midpoints) - gauss(0)) / max(
582+
gauss(midpoints) - gauss(0)
583+
)
584+
normalized_pulse = gaussian_control.rabi_rates / max(gaussian_control.rabi_rates)
585+
586+
# check pulse is Gaussian-shaped
587+
assert np.allclose(expected_normalized_pulse, normalized_pulse)
588+
589+
# compute total rotation of generated pulse
590+
rabi_rotation = np.dot(gaussian_control.rabi_rates, gaussian_control.durations)
591+
592+
# check number and duration of pulses
593+
assert len(gaussian_control.rabi_rates) == _segment_count
594+
assert np.allclose(gaussian_control.durations, _duration / _segment_count)
595+
596+
# check total rotation of pulse
597+
assert np.isclose(rabi_rotation, _rabi_rotation)
598+
599+
553600
def test_modulated_gaussian_control():
554601
"""
555602
Tests modulated Gaussian control at different modulate frequencies.

0 commit comments

Comments
 (0)