Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions qiskit/pulse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
Drag,
Gaussian,
GaussianSquare,
GaussianSquareDrag,
ParametricPulse,
SymbolicPulse,
Waveform,
Expand Down
10 changes: 9 additions & 1 deletion qiskit/pulse/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
Drag
Gaussian
GaussianSquare
GaussianSquareDrag

"""

Expand All @@ -111,6 +112,13 @@
drag,
)
from .parametric_pulses import ParametricPulse
from .symbolic_pulses import SymbolicPulse, Gaussian, GaussianSquare, Drag, Constant
from .symbolic_pulses import (
SymbolicPulse,
Gaussian,
GaussianSquare,
GaussianSquareDrag,
Drag,
Constant,
)
from .pulse import Pulse
from .waveform import Waveform
147 changes: 147 additions & 0 deletions qiskit/pulse/library/symbolic_pulses.py
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,153 @@ def __new__(
return instance


def GaussianSquareDrag(
Comment thread
wshanks marked this conversation as resolved.
duration: Union[int, ParameterExpression],
amp: Union[float, ParameterExpression],
sigma: Union[float, ParameterExpression],
beta: Union[float, ParameterExpression],
width: Optional[Union[float, ParameterExpression]] = None,
angle: Optional[Union[float, ParameterExpression]] = 0.0,
risefall_sigma_ratio: Optional[Union[float, ParameterExpression]] = None,
name: Optional[str] = None,
limit_amplitude: Optional[bool] = None,
) -> SymbolicPulse:
"""A square pulse with a Drag shaped rise and fall

This pulse shape is similar to :class:`~.GaussianSquare` but uses
:class:`~.Drag` for its rise and fall instead of :class:`~.Gaussian`. The
addition of the DRAG component of the rise and fall is sometimes helpful in
suppressing the spectral content of the pulse at frequencies near to, but
slightly offset from, the fundamental frequency of the drive. When there is
a spectator qubit close in frequency to the fundamental frequency,
suppressing the drive at the spectator's frequency can help avoid unwanted
excitation of the spectator.

Exactly one of the ``risefall_sigma_ratio`` and ``width`` parameters has to be specified.

If ``risefall_sigma_ratio`` is not ``None`` and ``width`` is ``None``:
Comment thread
wshanks marked this conversation as resolved.

.. math::

\\text{risefall} &= \\text{risefall_sigma_ratio} \\times \\text{sigma}\\\\
\\text{width} &= \\text{duration} - 2 \\times \\text{risefall}

If ``width`` is not None and ``risefall_sigma_ratio`` is None:

.. math:: \\text{risefall} = \\frac{\\text{duration} - \\text{width}}{2}

Gaussian :math:`g(x, c, σ)` and lifted gaussian :math:`g'(x, c, σ)` curves
can be written as:

.. math::

g(x, c, σ) &= \\exp\\Bigl(-\\frac12 \\frac{(x - c)^2}{σ^2}\\Bigr)\\\\
g'(x, c, σ) &= \\frac{g(x, c, σ)-g(-1, c, σ)}{1-g(-1, c, σ)}

From these, the lifted DRAG curve :math:`d'(x, c, σ, β)` can be written as

.. math::

d'(x, c, σ, β) = g'(x, c, σ) \\times \\Bigl(1 + 1j \\times β \\times\
\\Bigl(-\\frac{x - c}{σ^2}\\Bigr)\\Bigr)

The lifted gaussian square drag pulse :math:`f'(x)` is defined as:

.. math::

f'(x) &= \\begin{cases}\
\\text{A} \\times d'(x, \\text{risefall}, \\text{sigma}, \\text{beta})\
& x < \\text{risefall}\\\\
\\text{A}\
& \\text{risefall} \\le x < \\text{risefall} + \\text{width}\\\\
\\text{A} \\times \\times d'(\
x - (\\text{risefall} + \\text{width}),\
\\text{risefall},\
\\text{sigma},\
\\text{beta}\
)\
& \\text{risefall} + \\text{width} \\le x\
\\end{cases}\\\\

where :math:`\\text{A} = \\text{amp} \\times
\\exp\\left(i\\times\\text{angle}\\right)`.

Args:
duration: Pulse length in terms of the sampling period `dt`.
amp: The amplitude of the DRAG rise and fall and of the square pulse.
sigma: A measure of how wide or narrow the DRAG risefall is; see the class
docstring for more details.
beta: The DRAG correction amplitude.
width: The duration of the embedded square pulse.
angle: The angle in radians of the complex phase factor uniformly
scaling the pulse. Default value 0.
risefall_sigma_ratio: The ratio of each risefall duration to sigma.
name: Display name for this pulse envelope.
limit_amplitude: If ``True``, then limit the amplitude of the
waveform to 1. The default is ``True`` and the amplitude is constrained to 1.

Returns:
SymbolicPulse instance.

Raises:
PulseError: When width and risefall_sigma_ratio are both empty or both non-empty.
"""
# Convert risefall_sigma_ratio into width which is defined in OpenPulse spec
if width is None and risefall_sigma_ratio is None:
raise PulseError(
"Either the pulse width or the risefall_sigma_ratio parameter must be specified."
)
if width is not None and risefall_sigma_ratio is not None:
Comment thread
taalexander marked this conversation as resolved.
raise PulseError(
"Either the pulse width or the risefall_sigma_ratio parameter can be specified"
" but not both."
)
if width is None and risefall_sigma_ratio is not None:
Comment thread
taalexander marked this conversation as resolved.
width = duration - 2.0 * risefall_sigma_ratio * sigma

parameters = {"amp": amp, "sigma": sigma, "width": width, "beta": beta, "angle": angle}

# Prepare symbolic expressions
_t, _duration, _amp, _sigma, _beta, _width, _angle = sym.symbols(
"t, duration, amp, sigma, beta, width, angle"
)
_center = _duration / 2

_sq_t0 = _center - _width / 2
_sq_t1 = _center + _width / 2

_gaussian_ledge = _lifted_gaussian(_t, _sq_t0, -1, _sigma)
_gaussian_redge = _lifted_gaussian(_t, _sq_t1, _duration + 1, _sigma)
_deriv_ledge = -(_t - _sq_t0) / (_sigma**2) * _gaussian_ledge
Comment thread
wshanks marked this conversation as resolved.
_deriv_redge = -(_t - _sq_t1) / (_sigma**2) * _gaussian_redge

envelope_expr = (
_amp
* sym.exp(sym.I * _angle)
* sym.Piecewise(
(_gaussian_ledge + sym.I * _beta * _deriv_ledge, _t <= _sq_t0),
(_gaussian_redge + sym.I * _beta * _deriv_redge, _t >= _sq_t1),
(1, True),
)
)
consts_expr = sym.And(_sigma > 0, _width >= 0, _duration >= _width)
valid_amp_conditions_expr = sym.And(sym.Abs(_amp) <= 1.0, sym.Abs(_beta) < _sigma)

instance = SymbolicPulse(
pulse_type="GaussianSquareDrag",
duration=duration,
parameters=parameters,
name=name,
limit_amplitude=limit_amplitude,
envelope=envelope_expr,
constraints=consts_expr,
valid_amp_conditions=valid_amp_conditions_expr,
)
instance.validate_parameters()

return instance


class Drag(metaclass=_PulseType):
"""The Derivative Removal by Adiabatic Gate (DRAG) pulse is a standard Gaussian pulse
with an additional Gaussian derivative component and lifting applied.
Expand Down
1 change: 1 addition & 0 deletions qiskit/qobj/converters/pulse_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class ParametricPulseShapes(Enum):

gaussian = "Gaussian"
gaussian_square = "GaussianSquare"
gaussian_square_drag = "GaussianSquareDrag"
drag = "Drag"
constant = "Constant"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
features:
- |
Add new :class:`~qiskit.pulse.GaussianSquareDrag` pulse shape. This pulse
shape is similar to :class:`~qiskit.pulse.GaussianSquare` but uses the
:class:`~qiskit.pulse.Drag` shape during its rise and fall. The correction
from the DRAG pulse shape can suppress part of the frequency spectrum of
the rise and fall of the pulse which can help avoid exciting spectator
qubits when they are close in frequency to the drive frequency of the
pulse.
111 changes: 111 additions & 0 deletions test/python/pulse/test_pulse_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
Constant,
Gaussian,
GaussianSquare,
GaussianSquareDrag,
Drag,
gaussian,
gaussian_square,
Expand Down Expand Up @@ -192,6 +193,79 @@ def test_gauss_square_passes_validation_after_construction(self):
pulse = GaussianSquare(duration=125, sigma=4, amp=0.5j, width=100)
pulse.validate_parameters()

def test_gaussian_square_drag_pulse(self):
"""Test that GaussianSquareDrag sample pulse matches expectations.

Test that the real part of the envelop matches GaussianSquare and that
the rise and fall match Drag.
"""
risefall = 32
sigma = 4
amp = 0.5
width = 100
beta = 1
duration = width + 2 * risefall

gsd = GaussianSquareDrag(duration=duration, sigma=sigma, amp=amp, width=width, beta=beta)
gsd_samples = gsd.get_waveform().samples

gs_pulse = GaussianSquare(duration=duration, sigma=sigma, amp=amp, width=width)
np.testing.assert_almost_equal(
np.real(gsd_samples),
np.real(gs_pulse.get_waveform().samples),
)
gsd2 = GaussianSquareDrag(
duration=duration,
sigma=sigma,
amp=amp,
beta=beta,
risefall_sigma_ratio=risefall / sigma,
)
np.testing.assert_almost_equal(
gsd_samples,
gsd2.get_waveform().samples,
)

drag_pulse = Drag(duration=2 * risefall, amp=amp, sigma=sigma, beta=beta)
np.testing.assert_almost_equal(
gsd_samples[:risefall],
drag_pulse.get_waveform().samples[:risefall],
)
np.testing.assert_almost_equal(
gsd_samples[-risefall:],
drag_pulse.get_waveform().samples[-risefall:],
)

def test_gauss_square_drag_extreme(self):
"""Test that the gaussian square drag pulse can build a drag pulse."""
duration = 125
sigma = 4
amp = 0.5
angle = 1.5
beta = 1
gsd = GaussianSquareDrag(
duration=duration, sigma=sigma, amp=amp, width=0, beta=beta, angle=angle
)
drag = Drag(duration=duration, sigma=sigma, amp=amp, beta=beta, angle=angle)
np.testing.assert_almost_equal(gsd.get_waveform().samples, drag.get_waveform().samples)

def test_gaussian_square_drag_validation(self):
"""Test drag beta parameter validation."""

GaussianSquareDrag(duration=50, width=0, sigma=16, amp=1, beta=2)
GaussianSquareDrag(duration=50, width=0, sigma=16, amp=1, beta=4)
GaussianSquareDrag(duration=50, width=0, sigma=16, amp=0.5, beta=20)
GaussianSquareDrag(duration=50, width=0, sigma=16, amp=-1, beta=2)
GaussianSquareDrag(duration=50, width=0, sigma=16, amp=1, beta=-2)
GaussianSquareDrag(duration=50, width=0, sigma=16, amp=1, beta=6)
GaussianSquareDrag(duration=50, width=0, sigma=16, amp=-0.5, beta=25, angle=1.5)
with self.assertRaises(PulseError):
GaussianSquareDrag(duration=50, width=0, sigma=16, amp=1, beta=20)
with self.assertRaises(PulseError):
GaussianSquareDrag(duration=50, width=0, sigma=4, amp=0.8, beta=20)
with self.assertRaises(PulseError):
GaussianSquareDrag(duration=50, width=0, sigma=4, amp=0.8, beta=-20)

def test_drag_pulse(self):
"""Test that the Drag sample pulse matches the pulse library."""
drag = Drag(duration=25, sigma=4, amp=0.5j, beta=1)
Expand Down Expand Up @@ -274,6 +348,16 @@ def test_repr(self):
repr(gaus_square),
"GaussianSquare(duration=20, amp=1.0, sigma=30, width=14.0, angle=0.2)",
)
gsd = GaussianSquareDrag(duration=20, sigma=30, amp=1.0, width=3, beta=1)
self.assertEqual(
repr(gsd),
"GaussianSquareDrag(duration=20, amp=1.0, sigma=30, width=3, beta=1, angle=0.0)",
)
gsd = GaussianSquareDrag(duration=20, sigma=30, amp=1.0, risefall_sigma_ratio=0.1, beta=1)
self.assertEqual(
repr(gsd),
"GaussianSquareDrag(duration=20, amp=1.0, sigma=30, width=14.0, beta=1, angle=0.0)",
)
drag = Drag(duration=5, amp=0.5, sigma=7, beta=1)
self.assertEqual(repr(drag), "Drag(duration=5, amp=0.5, sigma=7, beta=1, angle=0)")
const = Constant(duration=150, amp=0.1, angle=0.3)
Expand All @@ -291,6 +375,14 @@ def test_param_validation(self):
GaussianSquare(duration=150, amp=0.2, sigma=8, width=160)
with self.assertRaises(PulseError):
GaussianSquare(duration=150, amp=0.2, sigma=8, risefall_sigma_ratio=10)

with self.assertRaises(PulseError):
GaussianSquareDrag(duration=150, amp=0.2, sigma=8, beta=1)
with self.assertRaises(PulseError):
GaussianSquareDrag(duration=150, amp=0.2, sigma=8, width=160, beta=1)
with self.assertRaises(PulseError):
GaussianSquareDrag(duration=150, amp=0.2, sigma=8, risefall_sigma_ratio=10, beta=1)

with self.assertRaises(PulseError):
Constant(duration=150, amp=0.9 + 0.8j)
with self.assertRaises(PulseError):
Expand Down Expand Up @@ -343,6 +435,25 @@ def test_gaussian_square_limit_amplitude_per_instance(self):
)
self.assertGreater(np.abs(waveform.amp), 1.0)

def test_gaussian_square_drag_limit_amplitude(self):
"""Test that the check for amplitude less than or equal to 1 can be disabled."""
with self.assertRaises(PulseError):
GaussianSquareDrag(duration=100, sigma=1.0, amp=1.1, beta=0.1, width=10)

with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False):
waveform = GaussianSquareDrag(duration=100, sigma=1.0, amp=1.1, beta=0.1, width=10)
self.assertGreater(np.abs(waveform.amp), 1.0)

def test_gaussian_square_drag_limit_amplitude_per_instance(self):
"""Test that the check for amplitude per instance."""
with self.assertRaises(PulseError):
GaussianSquareDrag(duration=100, sigma=1.0, amp=1.1, beta=0.1, width=10)

waveform = GaussianSquareDrag(
duration=100, sigma=1.0, amp=1.1, beta=0.1, width=10, limit_amplitude=False
)
self.assertGreater(np.abs(waveform.amp), 1.0)

def test_drag_limit_amplitude(self):
"""Test that the check for amplitude less than or equal to 1 can be disabled."""
with self.assertRaises(PulseError):
Expand Down