Begin deprecation cycle of the discrete pulse library (#10222)

* Add SymbolicPulse counterparts to the discrete library, and start deprecation process.

* Typo fix

* Release notes styling

* Docs and style corrections

* autosummary update to avoid name clashes

* Remove deprecation section from release notes

* Minor changes to Square()
This commit is contained in:
TsafrirA 2023-07-04 08:32:12 +03:00 committed by GitHub
parent df5d413d78
commit 8a8609f93a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 652 additions and 17 deletions

View File

@ -68,7 +68,7 @@ intersphinx_mapping = {
"rustworkx": ("https://qiskit.org/ecosystem/rustworkx/", None),
"qiskit-ibm-runtime": ("https://qiskit.org/ecosystem/ibm-runtime/", None),
"qiskit-aer": ("https://qiskit.org/ecosystem/aer/", None),
"numpy": ("https://numpy.org/doc/stable/", None)
"numpy": ("https://numpy.org/doc/stable/", None),
}
# -- Options for HTML output -------------------------------------------------
@ -112,6 +112,8 @@ autosummary_filename_map = {
"qiskit.pulse.library.Sin": "qiskit.pulse.library.Sin_class.rst",
"qiskit.pulse.library.Gaussian": "qiskit.pulse.library.Gaussian_class.rst",
"qiskit.pulse.library.Drag": "qiskit.pulse.library.Drag_class.rst",
"qiskit.pulse.library.Square": "qiskit.pulse.library.Square_fun.rst",
"qiskit.pulse.library.Sech": "qiskit.pulse.library.Sech_fun.rst",
}
autoclass_content = "both"
@ -119,10 +121,15 @@ autoclass_content = "both"
# -- Options for Doctest --------------------------------------------------------
doctest_default_flags = doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.IGNORE_EXCEPTION_DETAIL | doctest.DONT_ACCEPT_TRUE_FOR_1
doctest_default_flags = (
doctest.ELLIPSIS
| doctest.NORMALIZE_WHITESPACE
| doctest.IGNORE_EXCEPTION_DETAIL
| doctest.DONT_ACCEPT_TRUE_FOR_1
)
# Leaving this string empty disables testing of doctest blocks from docstrings.
# Doctest blocks are structures like this one:
# >> code
# output
doctest_test_doctest_blocks = ""
doctest_test_doctest_blocks = ""

View File

@ -147,6 +147,10 @@ from qiskit.pulse.library import (
Cos,
Sawtooth,
Triangle,
Square,
GaussianDeriv,
Sech,
SechDeriv,
ParametricPulse,
SymbolicPulse,
ScalableSymbolicPulse,

View File

@ -89,10 +89,14 @@ Parametric Pulse Representation
GaussianSquare
GaussianSquareDrag
gaussian_square_echo
GaussianDeriv
Sin
Cos
Sawtooth
Triangle
Square
Sech
SechDeriv
"""
@ -119,12 +123,16 @@ from .symbolic_pulses import (
GaussianSquare,
GaussianSquareDrag,
gaussian_square_echo,
GaussianDeriv,
Drag,
Constant,
Sin,
Cos,
Sawtooth,
Triangle,
Square,
Sech,
SechDeriv,
)
from .pulse import Pulse
from .waveform import Waveform

View File

@ -222,7 +222,7 @@ def gaussian_deriv(
rescale_amp=rescale_amp,
ret_x=True,
)
gauss_deriv = -x / sigma * gauss
gauss_deriv = -x / sigma * gauss # Note that x is shifted and normalized by sigma
if ret_gaussian:
return gauss_deriv, gauss
return gauss_deriv

View File

@ -17,6 +17,7 @@ Note the sampling strategy use for all discrete pulses is ``midpoint``.
"""
from typing import Optional
from qiskit.utils.deprecation import deprecate_func
from ..exceptions import PulseError
from .waveform import Waveform
from . import continuous
@ -26,6 +27,16 @@ from . import samplers
_sampled_constant_pulse = samplers.midpoint(continuous.constant)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including constant() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Constant(...).get_waveform(). "
" Note that complex value support for the `amp` parameter is pending deprecation"
" in the SymbolicPulse library. It is therefore recommended to use two float values"
" for (`amp`, `angle`) instead of complex `amp`",
pending=True,
)
def constant(duration: int, amp: complex, name: Optional[str] = None) -> Waveform:
r"""Generates constant-sampled :class:`~qiskit.pulse.library.Waveform`.
@ -46,6 +57,13 @@ def constant(duration: int, amp: complex, name: Optional[str] = None) -> Wavefor
_sampled_zero_pulse = samplers.midpoint(continuous.zero)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including zero() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Constant(amp=0,...).get_waveform().",
pending=True,
)
def zero(duration: int, name: Optional[str] = None) -> Waveform:
"""Generates zero-sampled :class:`~qiskit.pulse.library.Waveform`.
@ -65,6 +83,15 @@ def zero(duration: int, name: Optional[str] = None) -> Waveform:
_sampled_square_pulse = samplers.midpoint(continuous.square)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including square() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Square(...).get_waveform()."
" Note that pulse.Square() does not support complex values for `amp`,"
" and that the phase is defined differently. See documentation.",
pending=True,
)
def square(
duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None
) -> Waveform:
@ -97,6 +124,17 @@ def square(
_sampled_sawtooth_pulse = samplers.midpoint(continuous.sawtooth)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including sawtooth() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Sawtooth(...).get_waveform()."
" Note that pulse.Sawtooth() does not support complex values for `amp`."
" Instead, use two float values for (`amp`, `angle`)."
" Also note that the phase is defined differently, such that 2*pi phase"
" shifts by a full cycle.",
pending=True,
)
def sawtooth(
duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None
) -> Waveform:
@ -143,6 +181,15 @@ def sawtooth(
_sampled_triangle_pulse = samplers.midpoint(continuous.triangle)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including triangle() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Triangle(...).get_waveform()."
" Note that pulse.Triangle() does not support complex values for `amp`."
" Instead, use two float values for (`amp`, `angle`).",
pending=True,
)
def triangle(
duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None
) -> Waveform:
@ -189,6 +236,15 @@ def triangle(
_sampled_cos_pulse = samplers.midpoint(continuous.cos)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including cos() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Cos(...).get_waveform()."
" Note that pulse.Cos() does not support complex values for `amp`."
" Instead, use two float values for (`amp`, `angle`).",
pending=True,
)
def cos(
duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None
) -> Waveform:
@ -218,6 +274,15 @@ def cos(
_sampled_sin_pulse = samplers.midpoint(continuous.sin)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including sin() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Sin(...).get_waveform()."
" Note that pulse.Sin() does not support complex values for `amp`."
" Instead, use two float values for (`amp`, `angle`).",
pending=True,
)
def sin(
duration: int, amp: complex, freq: float = None, phase: float = 0, name: Optional[str] = None
) -> Waveform:
@ -247,6 +312,16 @@ def sin(
_sampled_gaussian_pulse = samplers.midpoint(continuous.gaussian)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including gaussian() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Gaussian(...).get_waveform()."
" Note that complex value support for the `amp` parameter is pending deprecation"
" in the SymbolicPulse library. It is therefore recommended to use two float values"
" for (`amp`, `angle`) instead of complex `amp`",
pending=True,
)
def gaussian(
duration: int, amp: complex, sigma: float, name: Optional[str] = None, zero_ends: bool = True
) -> Waveform:
@ -291,6 +366,15 @@ def gaussian(
_sampled_gaussian_deriv_pulse = samplers.midpoint(continuous.gaussian_deriv)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including gaussian_deriv() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.GaussianDeriv(...).get_waveform()."
" Note that pulse.GaussianDeriv() does not support complex values for `amp`."
" Instead, use two float values for (`amp`, `angle`).",
pending=True,
)
def gaussian_deriv(
duration: int, amp: complex, sigma: float, name: Optional[str] = None
) -> Waveform:
@ -301,7 +385,8 @@ def gaussian_deriv(
.. math::
f(x) = A\frac{(x - \mu)}{\sigma^2}\exp\left(\left(\frac{x - \mu}{2\sigma}\right)^2 \right)
f(x) = -A\frac{(x - \mu)}{\sigma^2}\exp
\left(-\frac{1}{2}\left(\frac{x - \mu}{\sigma}\right)^2 \right)
i.e. the derivative of the Gaussian function, with center :math:`\mu=` ``duration/2``.
@ -318,6 +403,15 @@ def gaussian_deriv(
_sampled_sech_pulse = samplers.midpoint(continuous.sech)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including sech() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Sech(...).get_waveform()."
" Note that pulse.Sech() does not support complex values for `amp`."
" Instead, use two float values for (`amp`, `angle`).",
pending=True,
)
def sech(
duration: int, amp: complex, sigma: float, name: str = None, zero_ends: bool = True
) -> Waveform:
@ -360,6 +454,15 @@ def sech(
_sampled_sech_deriv_pulse = samplers.midpoint(continuous.sech_deriv)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including sech_deriv() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.SechDeriv(...).get_waveform()."
" Note that pulse.SechDeriv() does not support complex values for `amp`."
" Instead, use two float values for (`amp`, `angle`).",
pending=True,
)
def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> Waveform:
r"""Generates unnormalized sech derivative :class:`~qiskit.pulse.library.Waveform`.
@ -385,6 +488,16 @@ def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> W
_sampled_gaussian_square_pulse = samplers.midpoint(continuous.gaussian_square)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including gaussian_square() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.GaussianSquare(...).get_waveform()."
" Note that complex value support for the `amp` parameter is pending deprecation"
" in the SymbolicPulse library. It is therefore recommended to use two float values"
" for (`amp`, `angle`) instead of complex `amp`",
pending=True,
)
def gaussian_square(
duration: int,
amp: complex,
@ -451,6 +564,16 @@ def gaussian_square(
_sampled_drag_pulse = samplers.midpoint(continuous.drag)
@deprecate_func(
since="0.25.0",
additional_msg="The discrete pulses library, including drag() is pending deprecation."
" Instead, use the SymbolicPulse library to create the waveform with"
" pulse.Drag(...).get_waveform()."
" Note that complex value support for the `amp` parameter is pending deprecation"
" in the SymbolicPulse library. It is therefore recommended to use two float values"
" for (`amp`, `angle`) instead of complex `amp`",
pending=True,
)
def drag(
duration: int,
amp: complex,

View File

@ -1085,9 +1085,9 @@ def gaussian_square_echo(
The Gaussian Square Echo pulse is composed of three pulses. First, a Gaussian Square pulse
:math:`f_{echo}(x)` with amplitude ``amp`` and phase ``angle`` playing for half duration,
followed by a second Gaussian Square pulse :math:`-f_{echo}(x)` with opposite amplitude
and same phase playing for the rest of the duration. Third a Gaussian Square pulse
and same phase playing for the rest of the duration. Third a Gaussian Square pulse
:math:`f_{active}(x)` with amplitude ``active_amp`` and phase ``active_angle``
playing for the entire duration. The Gaussian Square Echo pulse :math:`g_e()`
playing for the entire duration. The Gaussian Square Echo pulse :math:`g_e()`
can be written as:
.. math::
@ -1099,11 +1099,11 @@ def gaussian_square_echo(
& \\frac{\\text{duration}}{2} < x\
\\end{cases}\\\\
One case where this pulse can be used is when implementing a direct CNOT gate with
a cross-resonance superconducting qubit architecture. When applying this pulse to
the target qubit, the active portion can be used to cancel IX terms from the
One case where this pulse can be used is when implementing a direct CNOT gate with
a cross-resonance superconducting qubit architecture. When applying this pulse to
the target qubit, the active portion can be used to cancel IX terms from the
cross-resonance drive while the echo portion can reduce the impact of a static ZZ coupling.
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``:
@ -1122,10 +1122,10 @@ def gaussian_square_echo(
.. _citation1: https://iopscience.iop.org/article/10.1088/2058-9565/abe519
.. |citation1| replace:: *Jurcevic, P., Javadi-Abhari, A., Bishop, L. S.,
Lauer, I., Bogorin, D. F., Brink, M., Capelluto, L., G{\"u}nl{\"u}k, O.,
.. |citation1| replace:: *Jurcevic, P., Javadi-Abhari, A., Bishop, L. S.,
Lauer, I., Bogorin, D. F., Brink, M., Capelluto, L., G{\"u}nl{\"u}k, O.,
Itoko, T., Kanazawa, N. & others
Demonstration of quantum volume 64 on a superconducting quantum
Demonstration of quantum volume 64 on a superconducting quantum
computing system. (Section V)*
Args:
duration: Pulse length in terms of the sampling period `dt`.
@ -1269,6 +1269,72 @@ def gaussian_square_echo(
return instance
def GaussianDeriv(
duration: Union[int, ParameterExpression],
amp: Union[float, ParameterExpression],
sigma: Union[float, ParameterExpression],
angle: Optional[Union[float, ParameterExpression]] = 0.0,
name: Optional[str] = None,
limit_amplitude: Optional[bool] = None,
) -> ScalableSymbolicPulse:
"""An unnormalized Gaussian derivative pulse.
The Gaussian function is centered around the halfway point of the pulse,
and the envelope of the pulse is given by:
.. math::
f(x) = -\\text{A}\\frac{x-\\mu}{\\text{sigma}^{2}}\\exp
\\left[-\\left(\\frac{x-\\mu}{2\\text{sigma}}\\right)^{2}\\right] , 0 <= x < duration
where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`,
and :math:`\\mu=\\text{duration}/2`.
Args:
duration: Pulse length in terms of the sampling period `dt`.
amp: The magnitude of the amplitude of the pulse
(the value of the corresponding Gaussian at the midpoint `duration`/2).
sigma: A measure of how wide or narrow the corresponding Gaussian peak is in terms of `dt`;
described mathematically in the class docstring.
angle: The angle in radians of the complex phase factor uniformly
scaling the pulse. Default value 0.
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:
ScalableSymbolicPulse instance.
"""
parameters = {"sigma": sigma}
# Prepare symbolic expressions
_t, _duration, _amp, _angle, _sigma = sym.symbols("t, duration, amp, angle, sigma")
envelope_expr = (
-_amp
* sym.exp(sym.I * _angle)
* ((_t - (_duration / 2)) / _sigma**2)
* sym.exp(-(1 / 2) * ((_t - (_duration / 2)) / _sigma) ** 2)
)
consts_expr = _sigma > 0
valid_amp_conditions_expr = sym.Abs(_amp / _sigma) <= sym.exp(1 / 2)
instance = ScalableSymbolicPulse(
pulse_type="GaussianDeriv",
duration=duration,
amp=amp,
angle=angle,
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.
@ -1649,7 +1715,7 @@ def Triangle(
name: Optional[str] = None,
limit_amplitude: Optional[bool] = None,
) -> ScalableSymbolicPulse:
"""A triangle pulse.
"""A triangle wave pulse.
The envelope of the pulse is given by:
@ -1709,3 +1775,221 @@ def Triangle(
instance.validate_parameters()
return instance
def Square(
duration: Union[int, ParameterExpression],
amp: Union[float, ParameterExpression],
phase: Union[float, ParameterExpression],
freq: Optional[Union[float, ParameterExpression]] = None,
angle: Optional[Union[float, ParameterExpression]] = 0.0,
name: Optional[str] = None,
limit_amplitude: Optional[bool] = None,
) -> ScalableSymbolicPulse:
"""A square wave pulse.
The envelope of the pulse is given by:
.. math::
f(x) = \\text{A}\\text{sign}\\left[\\sin
\\left(2\\pi x\\times\\text{freq}+\\text{phase}\\right)\\right] , 0 <= x < duration
where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`,
and :math:`\\text{sign}`
is the sign function with the convention :math:`\\text{sign}\\left(0\\right)=1`.
Args:
duration: Pulse length in terms of the sampling period `dt`.
amp: The magnitude of the amplitude of the square wave. Wave range is [-`amp`,`amp`].
phase: The phase of the square wave (note that this is not equivalent to the angle of
the complex amplitude).
freq: The frequency of the square wave, in terms of 1 over sampling period.
If not provided defaults to a single cycle (i.e :math:'\\frac{1}{\\text{duration}}').
The frequency is limited to the range :math:`\\left(0,0.5\\right]` (the Nyquist frequency).
angle: The angle in radians of the complex phase factor uniformly
scaling the pulse. Default value 0.
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:
ScalableSymbolicPulse instance.
"""
if freq is None:
freq = 1 / duration
parameters = {"freq": freq, "phase": phase}
# Prepare symbolic expressions
_t, _duration, _amp, _angle, _freq, _phase = sym.symbols("t, duration, amp, angle, freq, phase")
_x = _freq * _t + _phase / (2 * sym.pi)
envelope_expr = (
_amp * sym.exp(sym.I * _angle) * (2 * (2 * sym.floor(_x) - sym.floor(2 * _x)) + 1)
)
consts_expr = sym.And(_freq > 0, _freq < 0.5)
# This might fail for waves shorter than a single cycle
valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0
instance = ScalableSymbolicPulse(
pulse_type="Square",
duration=duration,
amp=amp,
angle=angle,
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
def Sech(
duration: Union[int, ParameterExpression],
amp: Union[float, ParameterExpression],
sigma: Union[float, ParameterExpression],
angle: Optional[Union[float, ParameterExpression]] = 0.0,
name: Optional[str] = None,
zero_ends: Optional[bool] = True,
limit_amplitude: Optional[bool] = None,
) -> ScalableSymbolicPulse:
"""An unnormalized sech pulse.
The sech function is centered around the halfway point of the pulse,
and the envelope of the pulse is given by:
.. math::
f(x) = \\text{A}\\text{sech}\\left(
\\frac{x-\\mu}{\\text{sigma}}\\right) , 0 <= x < duration
where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`,
and :math:`\\mu=\\text{duration}/2`.
If `zero_ends` is set to `True`, the output `y` is modified:
.. math::
y\\left(x\\right) \\mapsto \\text{A}\\frac{y-y^{*}}{\\text{A}-y^{*}},
where :math:`y^{*}` is the value of :math:`y` at the endpoints (at :math:`x=-1
and :math:`x=\\text{duration}+1`). This shifts the endpoints value to zero, while also
rescaling to preserve the amplitude at `:math:`\\text{duration}/2``.
Args:
duration: Pulse length in terms of the sampling period `dt`.
amp: The magnitude of the amplitude of the pulse (the value at the midpoint `duration`/2).
sigma: A measure of how wide or narrow the sech peak is in terms of `dt`;
described mathematically in the class docstring.
angle: The angle in radians of the complex phase factor uniformly
scaling the pulse. Default value 0.
name: Display name for this pulse envelope.
zero_ends: If True, zeros the ends at x = -1, x = `duration` + 1,
but rescales to preserve `amp`. Default value True.
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:
ScalableSymbolicPulse instance.
"""
parameters = {"sigma": sigma}
# Prepare symbolic expressions
_t, _duration, _amp, _angle, _sigma = sym.symbols("t, duration, amp, angle, sigma")
complex_amp = _amp * sym.exp(sym.I * _angle)
envelope_expr = complex_amp * sym.sech((_t - (_duration / 2)) / _sigma)
if zero_ends:
shift_val = complex_amp * sym.sech((-1 - (_duration / 2)) / _sigma)
envelope_expr = complex_amp * (envelope_expr - shift_val) / (complex_amp - shift_val)
consts_expr = _sigma > 0
valid_amp_conditions_expr = sym.Abs(_amp) <= 1.0
instance = ScalableSymbolicPulse(
pulse_type="Sech",
duration=duration,
amp=amp,
angle=angle,
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
def SechDeriv(
duration: Union[int, ParameterExpression],
amp: Union[float, ParameterExpression],
sigma: Union[float, ParameterExpression],
angle: Optional[Union[float, ParameterExpression]] = 0.0,
name: Optional[str] = None,
limit_amplitude: Optional[bool] = None,
) -> ScalableSymbolicPulse:
"""An unnormalized sech derivative pulse.
The sech function is centered around the halfway point of the pulse, and the envelope of the
pulse is given by:
.. math::
f(x) = \\text{A}\\frac{d}{dx}\\left[\\text{sech}
\\left(\\frac{x-\\mu}{\\text{sigma}}\\right)\\right] , 0 <= x < duration
where :math:`\\text{A} = \\text{amp} \\times\\exp\\left(i\\times\\text{angle}\\right)`,
:math:`\\mu=\\text{duration}/2`, and :math:`d/dx` is a derivative with respect to `x`.
Args:
duration: Pulse length in terms of the sampling period `dt`.
amp: The magnitude of the amplitude of the pulse (the value of the corresponding sech
function at the midpoint `duration`/2).
sigma: A measure of how wide or narrow the corresponding sech peak is, in terms of `dt`;
described mathematically in the class docstring.
angle: The angle in radians of the complex phase factor uniformly
scaling the pulse. Default value 0.
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:
ScalableSymbolicPulse instance.
"""
parameters = {"sigma": sigma}
# Prepare symbolic expressions
_t, _duration, _amp, _angle, _sigma = sym.symbols("t, duration, amp, angle, sigma")
time_argument = (_t - (_duration / 2)) / _sigma
sech_deriv = -sym.tanh(time_argument) * sym.sech(time_argument) / _sigma
envelope_expr = _amp * sym.exp(sym.I * _angle) * sech_deriv
consts_expr = _sigma > 0
valid_amp_conditions_expr = sym.Abs(_amp) / _sigma <= 2.0
instance = ScalableSymbolicPulse(
pulse_type="SechDeriv",
duration=duration,
amp=amp,
angle=angle,
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

View File

@ -0,0 +1,14 @@
---
features:
- |
The :class:`~qiskit.pulse.SymbolicPulse` library was extended. The new pulses in the library are:
* :func:`~qiskit.pulse.library.GaussianDeriv`
* :func:`~qiskit.pulse.library.Sech`
* :func:`~qiskit.pulse.library.SechDeriv`
* :func:`~qiskit.pulse.library.Square`
The new functions return a :class:`ScalableSymbolicPulse`, and match the functionality
of the corresponding functions in the discrete pulse library, with the exception of
`Square()` for which a phase of :math:`2\\pi` shifts by a full cycle (contrary to the
discrete `square()` where such a shift was induced by a :math:`\\pi` phase).

View File

@ -224,3 +224,32 @@ class TestDiscretePulses(QiskitTestCase):
drag_pulse = library.drag(duration, amp, sigma, beta=beta)
self.assertIsInstance(drag_pulse, Waveform)
np.testing.assert_array_almost_equal(drag_pulse.samples, drag_ref)
def test_pending_deprecation_warnings(self):
"""Test that pending deprecation warnings are raised when the discrete library is used."""
with self.assertWarns(PendingDeprecationWarning):
library.drag(duration=10, amp=0.5, sigma=0.1, beta=0.1)
with self.assertWarns(PendingDeprecationWarning):
library.gaussian_square(duration=10, amp=0.5, sigma=0.1, risefall=2, width=6)
with self.assertWarns(PendingDeprecationWarning):
library.gaussian(duration=10, amp=0.5, sigma=0.1)
with self.assertWarns(PendingDeprecationWarning):
library.sin(duration=10, amp=0.5)
with self.assertWarns(PendingDeprecationWarning):
library.cos(duration=10, amp=0.5)
with self.assertWarns(PendingDeprecationWarning):
library.sawtooth(duration=10, amp=0.5)
with self.assertWarns(PendingDeprecationWarning):
library.zero(duration=10)
with self.assertWarns(PendingDeprecationWarning):
library.constant(duration=10, amp=0.5)
with self.assertWarns(PendingDeprecationWarning):
library.triangle(duration=10, amp=0.5)
with self.assertWarns(PendingDeprecationWarning):
library.gaussian_deriv(duration=10, amp=0.5, sigma=3)
with self.assertWarns(PendingDeprecationWarning):
library.sech_deriv(duration=10, amp=0.5, sigma=3)
with self.assertWarns(PendingDeprecationWarning):
library.sech(duration=10, amp=0.5, sigma=3)
with self.assertWarns(PendingDeprecationWarning):
library.square(duration=10, amp=0.5)

View File

@ -25,18 +25,26 @@ from qiskit.pulse.library import (
GaussianSquare,
GaussianSquareDrag,
gaussian_square_echo,
GaussianDeriv,
Drag,
Sin,
Cos,
Sawtooth,
Triangle,
Square,
Sech,
SechDeriv,
gaussian,
gaussian_square,
gaussian_deriv,
drag as pl_drag,
sin,
cos,
triangle,
sawtooth,
square,
sech,
sech_deriv,
)
from qiskit.pulse import functional_pulse, PulseError
@ -138,12 +146,17 @@ class TestParametricPulses(QiskitTestCase):
Gaussian(duration=25, sigma=4, amp=0.5j)
GaussianSquare(duration=150, amp=0.2, sigma=8, width=140)
GaussianSquare(duration=150, amp=0.2, sigma=8, risefall_sigma_ratio=2.5)
GaussianDeriv(duration=150, amp=0.2, sigma=8)
Constant(duration=150, amp=0.1 + 0.4j)
Drag(duration=25, amp=0.2 + 0.3j, sigma=7.8, beta=4)
Sin(duration=25, amp=0.5, freq=0.1, phase=0.5, angle=0.5)
Cos(duration=30, amp=0.5, freq=0.1, phase=-0.5)
Sawtooth(duration=40, amp=0.5, freq=0.2, phase=3.14)
Triangle(duration=50, amp=0.5, freq=0.01, phase=0.5)
Square(duration=50, amp=0.5, freq=0.01, phase=0.5)
Sech(duration=50, amp=0.5, sigma=10)
Sech(duration=50, amp=0.5, sigma=10, zero_ends=False)
SechDeriv(duration=50, amp=0.5, sigma=10)
# This test should be removed once deprecation of complex amp is completed.
def test_complex_amp_deprecation(self):
@ -412,7 +425,7 @@ class TestParametricPulses(QiskitTestCase):
Sin(duration=duration, amp=amp, freq=5, phase=phase)
def test_cos_pulse(self):
"""Test that Cin sample pulse matches expectations, and parameter validation"""
"""Test that Cos sample pulse matches expectations, and parameter validation"""
duration = 100
amp = 0.5
freq = 0.1
@ -428,6 +441,20 @@ class TestParametricPulses(QiskitTestCase):
with self.assertRaises(PulseError):
Cos(duration=duration, amp=amp, freq=5, phase=phase)
def test_square_pulse(self):
"""Test that Square sample pulse matches expectations, and parameter validation"""
duration = 100
amp = 0.5
freq = 0.1
phase = 0.3
square_pulse = Square(duration=duration, amp=amp, freq=freq, phase=phase)
square_waveform = square(duration=duration, amp=amp, freq=freq, phase=phase / 2)
np.testing.assert_almost_equal(square_pulse.get_waveform().samples, square_waveform.samples)
with self.assertRaises(PulseError):
Square(duration=duration, amp=amp, freq=5, phase=phase)
def test_sawtooth_pulse(self):
"""Test that Sawtooth sample pulse matches expectations, and parameter validation"""
duration = 100
@ -449,7 +476,7 @@ class TestParametricPulses(QiskitTestCase):
Sawtooth(duration=duration, amp=amp, freq=5, phase=phase)
def test_triangle_pulse(self):
"""Test that Sawtooth sample pulse matches expectations, and parameter validation"""
"""Test that Triangle sample pulse matches expectations, and parameter validation"""
duration = 100
amp = 0.5
freq = 0.1
@ -467,6 +494,51 @@ class TestParametricPulses(QiskitTestCase):
with self.assertRaises(PulseError):
Triangle(duration=duration, amp=amp, freq=5, phase=phase)
def test_gaussian_deriv_pulse(self):
"""Test that GaussianDeriv sample pulse matches expectations"""
duration = 300
amp = 0.5
sigma = 100
gaussian_deriv_pulse = GaussianDeriv(duration=duration, amp=amp, sigma=sigma)
gaussian_deriv_waveform = gaussian_deriv(duration=duration, amp=amp, sigma=sigma)
np.testing.assert_almost_equal(
gaussian_deriv_pulse.get_waveform().samples, gaussian_deriv_waveform.samples
)
with self.assertRaises(PulseError):
Sech(duration=duration, amp=amp, sigma=0)
def test_sech_pulse(self):
"""Test that Sech sample pulse matches expectations, and parameter validation"""
duration = 100
amp = 0.5
sigma = 10
# Zero ends = True
sech_pulse = Sech(duration=duration, amp=amp, sigma=sigma)
sech_waveform = sech(duration=duration, amp=amp, sigma=sigma)
np.testing.assert_almost_equal(sech_pulse.get_waveform().samples, sech_waveform.samples)
# Zero ends = False
sech_pulse = Sech(duration=duration, amp=amp, sigma=sigma, zero_ends=False)
sech_waveform = sech(duration=duration, amp=amp, sigma=sigma, zero_ends=False)
np.testing.assert_almost_equal(sech_pulse.get_waveform().samples, sech_waveform.samples)
with self.assertRaises(PulseError):
Sech(duration=duration, amp=amp, sigma=-5)
def test_sech_deriv_pulse(self):
"""Test that SechDeriv sample pulse matches expectations, and parameter validation"""
duration = 100
amp = 0.5
sigma = 10
sech_deriv_pulse = SechDeriv(duration=duration, amp=amp, sigma=sigma)
sech_deriv_waveform = sech_deriv(duration=duration, amp=amp, sigma=sigma)
np.testing.assert_almost_equal(
sech_deriv_pulse.get_waveform().samples, sech_deriv_waveform.samples
)
with self.assertRaises(PulseError):
SechDeriv(duration=duration, amp=amp, sigma=-5)
def test_constant_samples(self):
"""Test the constant pulse and its sampled construction."""
const = Constant(duration=150, amp=0.1 + 0.4j)
@ -531,6 +603,32 @@ class TestParametricPulses(QiskitTestCase):
self.assertEqual(repr(drag), "Drag(duration=5, sigma=7, beta=1, amp=0.5, angle=0)")
const = Constant(duration=150, amp=0.1, angle=0.3)
self.assertEqual(repr(const), "Constant(duration=150, amp=0.1, angle=0.3)")
sin_pulse = Sin(duration=150, amp=0.1, angle=0.3, freq=0.2, phase=0)
self.assertEqual(
repr(sin_pulse), "Sin(duration=150, freq=0.2, phase=0, amp=0.1, angle=0.3)"
)
cos_pulse = Cos(duration=150, amp=0.1, angle=0.3, freq=0.2, phase=0)
self.assertEqual(
repr(cos_pulse), "Cos(duration=150, freq=0.2, phase=0, amp=0.1, angle=0.3)"
)
triangle_pulse = Triangle(duration=150, amp=0.1, angle=0.3, freq=0.2, phase=0)
self.assertEqual(
repr(triangle_pulse), "Triangle(duration=150, freq=0.2, phase=0, amp=0.1, angle=0.3)"
)
sawtooth_pulse = Sawtooth(duration=150, amp=0.1, angle=0.3, freq=0.2, phase=0)
self.assertEqual(
repr(sawtooth_pulse), "Sawtooth(duration=150, freq=0.2, phase=0, amp=0.1, angle=0.3)"
)
sech_pulse = Sech(duration=150, amp=0.1, angle=0.3, sigma=10)
self.assertEqual(repr(sech_pulse), "Sech(duration=150, sigma=10, amp=0.1, angle=0.3)")
sech_deriv_pulse = SechDeriv(duration=150, amp=0.1, angle=0.3, sigma=10)
self.assertEqual(
repr(sech_deriv_pulse), "SechDeriv(duration=150, sigma=10, amp=0.1, angle=0.3)"
)
gaussian_deriv_pulse = GaussianDeriv(duration=150, amp=0.1, angle=0.3, sigma=10)
self.assertEqual(
repr(gaussian_deriv_pulse), "GaussianDeriv(duration=150, sigma=10, amp=0.1, angle=0.3)"
)
def test_param_validation(self):
"""Test that parametric pulse parameters are validated when initialized."""
@ -727,6 +825,74 @@ class TestParametricPulses(QiskitTestCase):
waveform = Triangle(duration=100, amp=1.1, phase=0, limit_amplitude=False)
self.assertGreater(np.abs(waveform.amp), 1.0)
def test_square_limit_amplitude(self):
"""Test that the check for amplitude less than or equal to 1 can be disabled."""
with self.assertRaises(PulseError):
Square(duration=100, amp=1.1, phase=0)
with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False):
waveform = Square(duration=100, amp=1.1, phase=0)
self.assertGreater(np.abs(waveform.amp), 1.0)
def test_square_limit_amplitude_per_instance(self):
"""Test that the check for amplitude per instance."""
with self.assertRaises(PulseError):
Square(duration=100, amp=1.1, phase=0)
waveform = Square(duration=100, amp=1.1, phase=0, limit_amplitude=False)
self.assertGreater(np.abs(waveform.amp), 1.0)
def test_gaussian_deriv_limit_amplitude(self):
"""Test that the check for amplitude less than or equal to 1 can be disabled."""
with self.assertRaises(PulseError):
GaussianDeriv(duration=100, amp=5, sigma=1)
with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False):
waveform = GaussianDeriv(duration=100, amp=5, sigma=1)
self.assertGreater(np.abs(waveform.amp / waveform.sigma), np.exp(0.5))
def test_gaussian_deriv_limit_amplitude_per_instance(self):
"""Test that the check for amplitude per instance."""
with self.assertRaises(PulseError):
GaussianDeriv(duration=100, amp=5, sigma=1)
waveform = GaussianDeriv(duration=100, amp=5, sigma=1, limit_amplitude=False)
self.assertGreater(np.abs(waveform.amp / waveform.sigma), np.exp(0.5))
def test_sech_limit_amplitude(self):
"""Test that the check for amplitude less than or equal to 1 can be disabled."""
with self.assertRaises(PulseError):
Sech(duration=100, amp=5, sigma=1)
with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False):
waveform = Sech(duration=100, amp=5, sigma=1)
self.assertGreater(np.abs(waveform.amp), 1.0)
def test_sech_limit_amplitude_per_instance(self):
"""Test that the check for amplitude per instance."""
with self.assertRaises(PulseError):
Sech(duration=100, amp=5, sigma=1)
waveform = Sech(duration=100, amp=5, sigma=1, limit_amplitude=False)
self.assertGreater(np.abs(waveform.amp), 1.0)
def test_sech_deriv_limit_amplitude(self):
"""Test that the check for amplitude less than or equal to 1 can be disabled."""
with self.assertRaises(PulseError):
SechDeriv(duration=100, amp=5, sigma=1)
with patch("qiskit.pulse.library.pulse.Pulse.limit_amplitude", new=False):
waveform = SechDeriv(duration=100, amp=5, sigma=1)
self.assertGreater(np.abs(waveform.amp) / waveform.sigma, 2.0)
def test_sech_deriv_limit_amplitude_per_instance(self):
"""Test that the check for amplitude per instance."""
with self.assertRaises(PulseError):
SechDeriv(duration=100, amp=5, sigma=1)
waveform = SechDeriv(duration=100, amp=5, sigma=1, limit_amplitude=False)
self.assertGreater(np.abs(waveform.amp) / waveform.sigma, 2.0)
def test_get_parameters(self):
"""Test getting pulse parameters as attribute."""
drag_pulse = Drag(duration=100, amp=0.1, sigma=40, beta=3)