Repository: Qiskit/qiskit

Issue:
Add mathematical definition of pulses in pulse_lib documentation
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?
Mathematical definitions of pulses should be added in the `pulse_lib` documentation.




Discussion:
This should be easy to do with the formats added in docstrings of #3463.
Could this be part of #3676 @lcapelluto?
I can take this on during our docathon :)

Resolving patch:
diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py
--- a/qiskit/pulse/__init__.py
+++ b/qiskit/pulse/__init__.py
@@ -74,6 +74,14 @@
    LoConfig
    LoRange
 
+Pulse Library
+=============
+
+.. autosummary::
+   :toctree: ../stubs/
+
+   ~qiskit.pulse.pulse_lib.discrete
+
 Exceptions
 ==========
 
diff --git a/qiskit/pulse/pulse_lib/__init__.py b/qiskit/pulse/pulse_lib/__init__.py
--- a/qiskit/pulse/pulse_lib/__init__.py
+++ b/qiskit/pulse/pulse_lib/__init__.py
@@ -12,6 +12,6 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
-"""Module for builtin pulse_lib."""
+"""Module for builtin ``pulse_lib``."""
 
 from .discrete import *
diff --git a/qiskit/pulse/pulse_lib/discrete.py b/qiskit/pulse/pulse_lib/discrete.py
--- a/qiskit/pulse/pulse_lib/discrete.py
+++ b/qiskit/pulse/pulse_lib/discrete.py
@@ -30,9 +30,13 @@
 
 
 def constant(duration: int, amp: complex, name: Optional[str] = None) -> 'SamplePulse':
-    """Generates constant-sampled `SamplePulse`.
+    r"""Generates constant-sampled :class:`~qiskit.pulse.SamplePulse`.
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
+    For :math:`A=` ``amp``, samples from the function:
+
+    .. math::
+
+        f(x) = A
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
@@ -46,7 +50,13 @@ def constant(duration: int, amp: complex, name: Optional[str] = None) -> 'Sample
 
 
 def zero(duration: int, name: Optional[str] = None) -> 'SamplePulse':
-    """Generates zero-sampled `SamplePulse`.
+    """Generates zero-sampled :class:`~qiskit.pulse.SamplePulse`.
+
+    Samples from the function:
+
+    .. math::
+
+        f(x) = 0
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
@@ -60,14 +70,23 @@ def zero(duration: int, name: Optional[str] = None) -> 'SamplePulse':
 
 def square(duration: int, amp: complex, period: float = None,
            phase: float = 0, name: Optional[str] = None) -> 'SamplePulse':
-    """Generates square wave `SamplePulse`.
+    r"""Generates square wave :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`A=` ``amp``, :math:`T=` ``period``, and :math:`\phi=` ``phase``,
+    applies the `midpoint` sampling strategy to generate a discrete pulse sampled from
+    the continuous function:
+
+    .. math::
+
+        f(x) = A \text{sign}\left[ \sin\left(\frac{2 \pi x}{T} + 2\phi\right) \right]
+
+    with the convention :math:`\text{sign}(0) = 1`.
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
-        amp: Pulse amplitude. Wave range is [-amp, amp].
-        period: Pulse period, units of dt. If `None` defaults to single cycle.
+        amp: Pulse amplitude. Wave range is :math:`[-` ``amp`` :math:`,` ``amp`` :math:`]`.
+        period: Pulse period, units of dt. If ``None``, defaults to single cycle.
         phase: Pulse phase.
         name: Name of pulse.
     """
@@ -82,14 +101,35 @@ def square(duration: int, amp: complex, period: float = None,
 
 def sawtooth(duration: int, amp: complex, period: float = None,
              phase: float = 0, name: Optional[str] = None) -> 'SamplePulse':
-    """Generates sawtooth wave `SamplePulse`.
+    r"""Generates sawtooth wave :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`A=` ``amp``, :math:`T=` ``period``, and :math:`\phi=` ``phase``,
+    applies the `midpoint` sampling strategy to generate a discrete pulse sampled from
+    the continuous function:
+
+    .. math::
+
+        f(x) = 2 A \left( g(x) - \left\lfloor \frac{1}{2} + g(x) \right\rfloor\right)
+
+    where :math:`g(x) = x/T + \phi/\pi`.
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
-        amp: Pulse amplitude. Wave range is [-amp, amp].
-        period: Pulse period, units of dt. If `None` defaults to single cycle.
+        amp: Pulse amplitude. Wave range is :math:`[-` ``amp`` :math:`,` ``amp`` :math:`]`.
+        period: Pulse period, units of dt. If ``None``, defaults to single cycle.
         phase: Pulse phase.
         name: Name of pulse.
+
+    Example:
+        .. jupyter-execute::
+
+            import matplotlib.pyplot as plt
+            from qiskit.pulse.pulse_lib import sawtooth
+
+            duration = 100
+            amp = 1
+            period = duration
+            plt.plot(range(duration), sawtooth(duration, amp, period).samples)
     """
     if period is None:
         period = duration
@@ -102,16 +142,35 @@ def sawtooth(duration: int, amp: complex, period: float = None,
 
 def triangle(duration: int, amp: complex, period: float = None,
              phase: float = 0, name: Optional[str] = None) -> 'SamplePulse':
-    """Generates triangle wave `SamplePulse`.
+    r"""Generates triangle wave :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`A=` ``amp``, :math:`T=` ``period``, and :math:`\phi=` ``phase``,
+    applies the `midpoint` sampling strategy to generate a discrete pulse sampled from
+    the continuous function:
+
+    .. math::
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
+        f(x) = A \left(-2\left|\text{sawtooth}(x, A, T, \phi)\right| + 1\right)
+
+    This a non-sinusoidal wave with linear ramping.
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
-        amp: Pulse amplitude. Wave range is [-amp, amp].
-        period: Pulse period, units of dt. If `None` defaults to single cycle.
+        amp: Pulse amplitude. Wave range is :math:`[-` ``amp`` :math:`,` ``amp`` :math:`]`.
+        period: Pulse period, units of dt. If ``None``, defaults to single cycle.
         phase: Pulse phase.
         name: Name of pulse.
+
+    Example:
+        .. jupyter-execute::
+
+            import matplotlib.pyplot as plt
+            from qiskit.pulse.pulse_lib import triangle
+
+            duration = 100
+            amp = 1
+            period = duration
+            plt.plot(range(duration), triangle(duration, amp, period).samples)
     """
     if period is None:
         period = duration
@@ -124,14 +183,20 @@ def triangle(duration: int, amp: complex, period: float = None,
 
 def cos(duration: int, amp: complex, freq: float = None,
         phase: float = 0, name: Optional[str] = None) -> 'SamplePulse':
-    """Generates cosine wave `SamplePulse`.
+    r"""Generates cosine wave :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`A=` ``amp``, :math:`\omega=` ``freq``, and :math:`\phi=` ``phase``,
+    applies the `midpoint` sampling strategy to generate a discrete pulse sampled from
+    the continuous function:
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
+    .. math::
+
+        f(x) = A \cos(2 \pi \omega x + \phi)
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
         amp: Pulse amplitude.
-        freq: Pulse frequency, units of 1/dt. If `None` defaults to single cycle.
+        freq: Pulse frequency, units of 1/dt. If ``None`` defaults to single cycle.
         phase: Pulse phase.
         name: Name of pulse.
     """
@@ -146,12 +211,20 @@ def cos(duration: int, amp: complex, freq: float = None,
 
 def sin(duration: int, amp: complex, freq: float = None,
         phase: float = 0, name: Optional[str] = None) -> 'SamplePulse':
-    """Generates sine wave `SamplePulse`.
+    r"""Generates sine wave :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`A=` ``amp``, :math:`\omega=` ``freq``, and :math:`\phi=` ``phase``,
+    applies the `midpoint` sampling strategy to generate a discrete pulse sampled from
+    the continuous function:
+
+    .. math::
+
+        f(x) = A \sin(2 \pi \omega x + \phi)
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
         amp: Pulse amplitude.
-        freq: Pulse frequency, units of 1/dt. If `None` defaults to single cycle.
+        freq: Pulse frequency, units of 1/dt. If ``None`` defaults to single cycle.
         phase: Pulse phase.
         name: Name of pulse.
     """
@@ -166,18 +239,32 @@ def sin(duration: int, amp: complex, freq: float = None,
 
 def gaussian(duration: int, amp: complex, sigma: float, name: Optional[str] = None,
              zero_ends: bool = True) -> 'SamplePulse':
-    r"""Generates unnormalized gaussian `SamplePulse`.
+    r"""Generates unnormalized gaussian :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`A=` ``amp`` and :math:`\sigma=` ``sigma``, applies the `midpoint` sampling strategy
+    to generate a discrete pulse sampled from the continuous function:
 
-    Centered at `duration/2` and zeroed at `t=0` and `t=duration` to prevent large
-    initial/final discontinuities.
+    .. math::
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
+        f(x) = A\exp\left(\left(\frac{x - \mu}{2\sigma}\right)^2 \right),
 
-    Integrated area under curve is $\Omega_g(amp, sigma) = amp \times np.sqrt(2\pi \sigma^2)$
+    with the center :math:`\mu=` ``duration/2``.
+
+    If ``zero_ends==True``, each output sample :math:`y` is modifed according to:
+
+    .. math::
+
+        y \mapsto A\frac{y-y^*}{A-y^*},
+
+    where :math:`y^*` is the value of the endpoint samples. This sets the endpoints
+    to :math:`0` while preserving the amplitude at the center. If :math:`A=y^*`,
+    :math:`y` is set to :math:`1`.
+
+    Integrated area under the full curve is ``amp * np.sqrt(2*np.pi*sigma**2)``
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
-        amp: Pulse amplitude at `duration/2`.
+        amp: Pulse amplitude at ``duration/2``.
         sigma: Width (standard deviation) of pulse.
         name: Name of pulse.
         zero_ends: If True, make the first and last sample zero, but rescale to preserve amp.
@@ -195,13 +282,20 @@ def gaussian(duration: int, amp: complex, sigma: float, name: Optional[str] = No
 
 def gaussian_deriv(duration: int, amp: complex, sigma: float,
                    name: Optional[str] = None) -> 'SamplePulse':
-    r"""Generates unnormalized gaussian derivative `SamplePulse`.
+    r"""Generates unnormalized gaussian derivative :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`A=` ``amp`` and :math:`\sigma=` ``sigma`` applies the `midpoint` sampling strategy
+    to generate a discrete pulse sampled from the continuous function:
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
+    .. math::
+
+        f(x) = A\frac{(x - \mu)}{\sigma^2}\exp\left(\left(\frac{x - \mu}{2\sigma}\right)^2 \right)
+
+    i.e. the derivative of the Gaussian function, with center :math:`\mu=` ``duration/2``.
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
-        amp: Pulse amplitude at `center`.
+        amp: Pulse amplitude of corresponding Gaussian at the pulse center (``duration/2``).
         sigma: Width (standard deviation) of pulse.
         name: Name of pulse.
     """
@@ -214,11 +308,26 @@ def gaussian_deriv(duration: int, amp: complex, sigma: float,
 
 def sech(duration: int, amp: complex, sigma: float, name: str = None,
          zero_ends: bool = True) -> 'SamplePulse':
-    r"""Generates unnormalized sech `SamplePulse`.
+    r"""Generates unnormalized sech :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`A=` ``amp`` and :math:`\sigma=` ``sigma``, applies the `midpoint` sampling strategy
+    to generate a discrete pulse sampled from the continuous function:
+
+    .. math::
+
+        f(x) = A\text{sech}\left(\frac{x-\mu}{\sigma} \right)
+
+    with the center :math:`\mu=` ``duration/2``.
 
-    Centered at `duration/2` and zeroed at `t=0` to prevent large initial discontinuity.
+    If ``zero_ends==True``, each output sample :math:`y` is modifed according to:
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
+    .. math::
+
+        y \mapsto A\frac{y-y^*}{A-y^*},
+
+    where :math:`y^*` is the value of the endpoint samples. This sets the endpoints
+    to :math:`0` while preserving the amplitude at the center. If :math:`A=y^*`,
+    :math:`y` is set to :math:`1`.
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
@@ -239,9 +348,16 @@ def sech(duration: int, amp: complex, sigma: float, name: str = None,
 
 
 def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> 'SamplePulse':
-    r"""Generates unnormalized sech derivative `SamplePulse`.
+    r"""Generates unnormalized sech derivative :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`A=` ``amp``, :math:`\sigma=` ``sigma``, and center :math:`\mu=` ``duration/2``,
+    applies the `midpoint` sampling strategy to generate a discrete pulse sampled from
+    the continuous function:
+
+    .. math::
+        f(x) = \frac{d}{dx}\left[A\text{sech}\left(\frac{x-\mu}{\sigma} \right)\right],
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
+    i.e. the derivative of :math:`\text{sech}`.
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
@@ -259,25 +375,40 @@ def sech_deriv(duration: int, amp: complex, sigma: float, name: str = None) -> '
 def gaussian_square(duration: int, amp: complex, sigma: float,
                     risefall: Optional[float] = None, width: Optional[float] = None,
                     name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse':
-    """Generates gaussian square `SamplePulse`.
+    r"""Generates gaussian square :class:`~qiskit.pulse.SamplePulse`.
+
+    For :math:`d=` ``duration``, :math:`A=` ``amp``, :math:`\sigma=` ``sigma``,
+    and :math:`r=` ``risefall``, applies the `midpoint` sampling strategy to
+    generate a discrete pulse sampled from the continuous function:
+
+    .. math::
+
+        f(x) = \begin{cases}
+                    g(x - r) ) & x\leq r \\
+                    A & r\leq x\leq d-r \\
+                    g(x - (d - r)) & d-r\leq x
+                \end{cases}
 
-    Centered at `duration/2` and zeroed at `t=0` and `t=duration` to prevent
-    large initial/final discontinuities.
+    where :math:`g(x)` is the Gaussian function sampled from in :meth:`gaussian`
+    with :math:`A=` ``amp``, :math:`\mu=1`, and :math:`\sigma=` ``sigma``. I.e.
+    :math:`f(x)` represents a square pulse with smooth Gaussian edges.
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
+    If ``zero_ends == True``, the samples for the Gaussian ramps are remapped as in
+    :meth:`gaussian`.
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
         amp: Pulse amplitude.
         sigma: Width (standard deviation) of Gaussian rise/fall portion of the pulse.
         risefall: Number of samples over which pulse rise and fall happen. Width of
-            square portion of pulse will be `duration-2*risefall`.
-        width: The duration of the embedded square pulse. Only one of `width` or `risefall`
-               should be specified since width = duration - 2 * risefall.
+            square portion of pulse will be ``duration-2*risefall``.
+        width: The duration of the embedded square pulse. Only one of ``width`` or ``risefall``
+               should be specified as the functional form requires
+               ``width = duration - 2 * risefall``.
         name: Name of pulse.
-        zero_ends: If True, make the first and last sample zero, but rescale to preserve amp.
+        zero_ends: If ``True``, make the first and last sample zero, but rescale to preserve amp.
     Raises:
-        PulseError: If risefall and width arguments are inconsistent or not enough info.
+        PulseError: If ``risefall`` and ``width`` arguments are inconsistent or not enough info.
     """
     if risefall is None and width is None:
         raise PulseError("gaussian_square missing required argument: 'width' or 'risefall'.")
@@ -299,25 +430,36 @@ def gaussian_square(duration: int, amp: complex, sigma: float,
 
 def drag(duration: int, amp: complex, sigma: float, beta: float,
          name: Optional[str] = None, zero_ends: bool = True) -> 'SamplePulse':
-    r"""Generates Y-only correction DRAG `SamplePulse` for standard nonlinear oscillator (SNO) [1].
+    r"""Generates Y-only correction DRAG :class:`~qiskit.pulse.SamplePulse` for standard nonlinear
+    oscillator (SNO) [1].
+
+    For :math:`A=` ``amp``, :math:`\sigma=` ``sigma``, and :math:`\beta=` ``beta``, applies the
+    `midpoint` sampling strategy to generate a discrete pulse sampled from the
+    continuous function:
+
+    .. math::
 
-    Centered at `duration/2` and zeroed at `t=0` to prevent large initial discontinuity.
+        f(x) = g(x) + i \beta h(x),
 
-    Applies `midpoint` sampling strategy to generate discrete pulse from continuous function.
+    where :math:`g(x)` is the function sampled in :meth:`gaussian`, and :math:`h(x)`
+    is the function sampled in :meth:`gaussian_deriv`.
+
+    If ``zero_ends == True``, the samples from :math:`g(x)` are remapped as in :meth:`gaussian`.
 
     [1] Gambetta, J. M., Motzoi, F., Merkel, S. T. & Wilhelm, F. K.
-        Analytic control methods for high-fidelity unitary operations
-        in a weakly nonlinear oscillator. Phys. Rev. A 83, 012308 (2011).
+    Analytic control methods for high-fidelity unitary operations
+    in a weakly nonlinear oscillator. Phys. Rev. A 83, 012308 (2011).
 
     Args:
         duration: Duration of pulse. Must be greater than zero.
-        amp: Pulse amplitude at `center`.
+        amp: Pulse amplitude at center  ``duration/2``.
         sigma: Width (standard deviation) of pulse.
-        beta: Y correction amplitude. For the SNO this is $\beta=-\frac{\lambda_1^2}{4\Delta_2}$.
-            Where $\lambds_1$ is the relative coupling strength between the first excited and second
-            excited states and $\Delta_2$ is the detuning between the respective excited states.
+        beta: Y correction amplitude. For the SNO this is
+              :math:`\beta=-\frac{\lambda_1^2}{4\Delta_2}`. Where :math:`\lambda_1` is the
+              relative coupling strength between the first excited and second excited states
+              and :math:`\Delta_2` is the detuning between the respective excited states.
         name: Name of pulse.
-        zero_ends: If True, make the first and last sample zero, but rescale to preserve amp.
+        zero_ends: If ``True``, make the first and last sample zero, but rescale to preserve amp.
     """
     center = duration/2
     zeroed_width = duration if zero_ends else None


Test patch: