From ea0d71e91fe00ae8c094b2ce03de6f7b666a43c2 Mon Sep 17 00:00:00 2001 From: DanPuzzuoli Date: Wed, 28 Feb 2024 12:49:33 -0800 Subject: [PATCH] reformatting docs in signals module --- qiskit_dynamics/signals/__init__.py | 99 +++++++++---------- qiskit_dynamics/signals/signals.py | 87 ++++++++-------- qiskit_dynamics/signals/transfer_functions.py | 48 ++++----- 3 files changed, 110 insertions(+), 124 deletions(-) diff --git a/qiskit_dynamics/signals/__init__.py b/qiskit_dynamics/signals/__init__.py index a0663d1a5..8e4e41225 100644 --- a/qiskit_dynamics/signals/__init__.py +++ b/qiskit_dynamics/signals/__init__.py @@ -19,11 +19,11 @@ .. currentmodule:: qiskit_dynamics.signals -This module contains classes for representing the time-dependent coefficients in -matrix differential equations. +This module contains classes for representing the time-dependent coefficients in matrix differential +equations. -These classes, referred to as *signals*, represent classes of real-valued functions, either -of the form, or built from functions of the following form: +These classes, referred to as *signals*, represent classes of real-valued functions, either of the +form, or built from functions of the following form: .. math:: s(t) = \textnormal{Re}[f(t)e^{i(2 \pi \nu t + \phi)}], @@ -34,23 +34,22 @@ * :math:`\nu \in \mathbb{R}` is the *carrier frequency*, and * :math:`\phi \in \mathbb{R}` is the *phase*. -Furthermore, this module contains *transfer functions* which transform one or more signal -into other signals. +Furthermore, this module contains *transfer functions* which transform one or more signal into other +signals. Signal API summary ================== All signal classes share a common API for evaluation and visualization: - * The signal value at a given time ``t`` is evaluated by treating the ``signal`` as a - callable: ``signal(t)``. + * The signal value at a given time ``t`` is evaluated by treating the ``signal`` as a callable: + ``signal(t)``. * The envelope :math:`f(t)` is evaluated via: ``signal.envelope(t)``. * The complex value :math:`f(t)e^{i(2 \pi \nu t + \phi)}` via: ``signal.complex_value(t)``. * The ``signal.draw`` method provides a common visualization interface. In addition to the above, all signal types allow for algebraic operations, which should be -understood in terms of algebraic operations on functions. E.g. two signals can be added -together via +understood in terms of algebraic operations on functions. E.g. two signals can be added together via .. code-block:: python @@ -62,12 +61,12 @@ signal_sum(t) == signal1(t) + signal2(t) -Signal multiplication is defined similarly, and signals can be added or multiplied with constants -as well. +Signal multiplication is defined similarly, and signals can be added or multiplied with constants as +well. -The remainder of this document gives further detail about some special functionality of -these classes, but the following table provides a list of the different signal classes, -along with a high level description of their role. +The remainder of this document gives further detail about some special functionality of these +classes, but the following table provides a list of the different signal classes, along with a high +level description of their role. .. list-table:: Types of signal objects :widths: 10 50 @@ -82,11 +81,11 @@ performance. * - :class:`~qiskit_dynamics.signals.SignalSum` - A sum of :class:`~qiskit_dynamics.signals.Signal` or - :class:`~qiskit_dynamics.signals.DiscreteSignal` objects. - Evaluation of envelopes returns an array of envelopes in the sum. + :class:`~qiskit_dynamics.signals.DiscreteSignal` objects. Evaluation of envelopes returns an + array of envelopes in the sum. * - :class:`~qiskit_dynamics.signals.DiscreteSignalSum` - - A sum of :class:`~qiskit_dynamics.signals.DiscreteSignal` objects with the same - start time, number of samples, and sample duration. Implemented with array-based operations. + - A sum of :class:`~qiskit_dynamics.signals.DiscreteSignal` objects with the same start time, + number of samples, and sample duration. Implemented with array-based operations. Constant Signal @@ -98,19 +97,18 @@ const = Signal(2.) -This initializes the object to always return the constant ``2.``, and allows constants to be -treated on the same footing as arbitrary :class:`~qiskit_dynamics.signals.Signal` instances. -A :class:`~qiskit_dynamics.signals.Signal` operating in constant-mode can be checked via the -boolean attribute ``const.is_constant``. +This initializes the object to always return the constant ``2.``, and allows constants to be treated +on the same footing as arbitrary :class:`~qiskit_dynamics.signals.Signal` instances. A +:class:`~qiskit_dynamics.signals.Signal` operating in constant-mode can be checked via the boolean +attribute ``const.is_constant``. Algebraic operations ==================== -Algebraic operations are supported by the :class:`~qiskit_dynamics.signals.SignalSum` -object. Any two signal classes can be added together, producing a -:class:`~qiskit_dynamics.signals.SignalSum`. Multiplication is also supported -via :class:`~qiskit_dynamics.signals.SignalSum` using the identity: +Algebraic operations are supported by the :class:`~qiskit_dynamics.signals.SignalSum` object. Any +two signal classes can be added together, producing a :class:`~qiskit_dynamics.signals.SignalSum`. +Multiplication is also supported via :class:`~qiskit_dynamics.signals.SignalSum` using the identity: .. math:: @@ -118,19 +116,18 @@ \\&= Re[\frac{1}{2} f(t)g(t)e^{i(2\pi (\omega + \nu)t + (\phi + \psi))} ] + Re[\frac{1}{2} f(t)\overline{g(t)}e^{i(2\pi (\omega - \nu)t + (\phi - \psi))} ]. -I.e. multiplication of two base signals produces a :class:`~qiskit_dynamics.signals.SignalSum` -with two elements, whose envelopes, frequencies, and phases are as given by the above formula. +I.e. multiplication of two base signals produces a :class:`~qiskit_dynamics.signals.SignalSum` with +two elements, whose envelopes, frequencies, and phases are as given by the above formula. Multiplication of sums is handled via distribution of this formula over the sum. -In the special case that -:class:`~qiskit_dynamics.signals.DiscreteSignal`\s with compatible sample structure -(same number of samples, ``dt``, and start time) are added together, -a :class:`~qiskit_dynamics.signals.DiscreteSignalSum` is produced. +In the special case that :class:`~qiskit_dynamics.signals.DiscreteSignal`\s with compatible sample +structure (same number of samples, ``dt``, and start time) are added together, a +:class:`~qiskit_dynamics.signals.DiscreteSignalSum` is produced. :class:`~qiskit_dynamics.signals.DiscreteSignalSum` stores a sum of compatible -:class:`~qiskit_dynamics.signals.DiscreteSignal`\s by joining the underlying arrays, -so that the sum can be evaluated using purely array-based operations. Multiplication -of :class:`~qiskit_dynamics.signals.DiscreteSignal`\s with compatible sample structure -is handled similarly. +:class:`~qiskit_dynamics.signals.DiscreteSignal`\s by joining the underlying arrays, so that the sum +can be evaluated using purely array-based operations. Multiplication of +:class:`~qiskit_dynamics.signals.DiscreteSignal`\s with compatible sample structure is handled +similarly. Sampling ======== @@ -138,13 +135,12 @@ Both :class:`~qiskit_dynamics.signals.DiscreteSignal` and :class:`~qiskit_dynamics.signals.DiscreteSignalSum` feature constructors (:meth:`~qiskit_dynamics.signals.DiscreteSignal.from_Signal` and -:meth:`~qiskit_dynamics.signals.DiscreteSignalSum.from_SignalSum` respectively) -which build an instance by sampling a :class:`~qiskit_dynamics.signals.Signal` or -:class:`~qiskit_dynamics.signals.SignalSum`. These constructors have the -option to just sample the envelope (and keep the carrier analog), or to also -sample the carrier. Below is a visualization of a signal superimposed with -sampled versions, both in the case of sampling the carrier, and in the case of -sampling just the envelope (and keeping the carrier analog). +:meth:`~qiskit_dynamics.signals.DiscreteSignalSum.from_SignalSum` respectively) which build an +instance by sampling a :class:`~qiskit_dynamics.signals.Signal` or +:class:`~qiskit_dynamics.signals.SignalSum`. These constructors have the option to just sample the +envelope (and keep the carrier analog), or to also sample the carrier. Below is a visualization of a +signal superimposed with sampled versions, both in the case of sampling the carrier, and in the case +of sampling just the envelope (and keeping the carrier analog). .. jupyter-execute:: :hide-code: @@ -154,18 +150,21 @@ # discretize a signal with and without samplying the carrier signal = Signal(lambda t: t, carrier_freq=2.) - discrete_signal = DiscreteSignal.from_Signal(signal, dt=0.1, start_time=0., - n_samples=10, sample_carrier=True) + discrete_signal = DiscreteSignal.from_Signal( + signal, dt=0.1, start_time=0., n_samples=10, sample_carrier=True + ) discrete_signal2 = DiscreteSignal.from_Signal(signal, dt=0.1, start_time=0., n_samples=10) # plot the signal against each discretization fig, axs = plt.subplots(1, 2, figsize=(14, 4)) signal.draw(t0=0., tf=1., n=100, axis=axs[0]) - discrete_signal.draw(t0=0., tf=1., n=100, axis=axs[0], - title='Signal v.s. Sampled envelope and carrier') + discrete_signal.draw( + t0=0., tf=1., n=100, axis=axs[0], title='Signal v.s. Sampled envelope and carrier' + ) signal.draw(t0=0., tf=1., n=100, axis=axs[1]) - discrete_signal2.draw(t0=0., tf=1., n=100, axis=axs[1], - title='Signal v.s. Sampled envelope') + discrete_signal2.draw( + t0=0., tf=1., n=100, axis=axs[1], title='Signal v.s. Sampled envelope' + ) Transfer Functions diff --git a/qiskit_dynamics/signals/signals.py b/qiskit_dynamics/signals/signals.py index acd9d301b..bb4359652 100644 --- a/qiskit_dynamics/signals/signals.py +++ b/qiskit_dynamics/signals/signals.py @@ -46,18 +46,17 @@ class Signal: - :math:`\nu` is the carrier frequency. - :math:`\phi` is the phase. - The envelope function can be specified either as a constant numeric value - (indicating a constant function), or as a complex-valued callable, - and the frequency and phase must be real. + The envelope function can be specified either as a constant numeric value (indicating a constant + function), or as a complex-valued callable, and the frequency and phase must be real. .. note:: - :class:`~qiskit_dynamics.signals.Signal` assumes the envelope ``f`` is - *array vectorized* in the sense that ``f`` can operate on arrays of arbitrary shape - and satisfy ``f(x)[idx] == f(x[idx])`` for a multidimensional index ``idx``. This - can be ensured either by writing ``f`` to be vectorized, or by using the ``vectorize`` - function in ``numpy`` or ``jax.numpy``. + :class:`~qiskit_dynamics.signals.Signal` assumes the envelope ``f`` is *array vectorized* in + the sense that ``f`` can operate on arrays of arbitrary shape and satisfy + ``f(x)[idx] == f(x[idx])`` for a multidimensional index ``idx``. This can be ensured either + by writing ``f`` to be vectorized, or by using the ``vectorize`` function in ``numpy`` or + ``jax.numpy``. For example, for an unvectorized envelope function ``f``: @@ -80,10 +79,10 @@ def __init__( Args: envelope: Envelope function of the signal, must be vectorized. - carrier_freq: Frequency of the carrier. Subclasses such as SignalSums - represent the carriers of each signal in an array. - phase: The phase of the carrier. Subclasses such as SignalSums - represent the phase of each signal in an array. + carrier_freq: Frequency of the carrier. Subclasses such as SignalSums represent the + carriers of each signal in an array. + phase: The phase of the carrier. Subclasses such as SignalSums represent the phase of + each signal in an array. name: Name of the signal. """ self._name = name @@ -125,8 +124,9 @@ def carrier_freq(self) -> ArrayLike: @carrier_freq.setter def carrier_freq(self, carrier_freq: ArrayLike): - """Carrier frequency setter. List handling is to support subclasses storing a - list of frequencies.""" + """Carrier frequency setter. List handling is to support subclasses storing a list of + frequencies. + """ self._carrier_freq = unp.asarray(carrier_freq) self._carrier_arg = 1j * 2 * np.pi * self._carrier_freq @@ -137,8 +137,7 @@ def phase(self) -> ArrayLike: @phase.setter def phase(self, phase: ArrayLike): - """Phase setter. List handling is to support subclasses storing a - list of phases.""" + """Phase setter. List handling is to support subclasses storing a list of phases.""" self._phase = unp.asarray(phase) self._phase_arg = 1j * self._phase @@ -205,8 +204,7 @@ def draw( ): """Plot the signal over an interval. - The ``function`` arg specifies which function to - plot: + The ``function`` arg specifies which function to plot: - ``function == 'signal'`` plots the full signal. - ``function == 'envelope'`` plots the complex envelope. @@ -262,9 +260,9 @@ class DiscreteSignal(Signal): The envelope is specified by an array of samples ``s = [s_0, ..., s_k]``, sample width ``dt``, and a start time ``t_0``, with the envelope being evaluated as :math:`f(t) =` ``s[floor((t - t0)/dt)]`` if ``t`` is in the interval with endpoints - ``start_time`` and ``start_time + dt * len(samples)``, and ``0.0`` otherwise. - By default a :class:`~qiskit_dynamics.signals.DiscreteSignal` is defined to start at - :math:`t=0` but a custom start time can be set via the ``start_time`` kwarg. + ``start_time`` and ``start_time + dt * len(samples)``, and ``0.0`` otherwise. By default a + :class:`~qiskit_dynamics.signals.DiscreteSignal` is defined to start at :math:`t=0` but a custom + start time can be set via the ``start_time`` kwarg. """ def __init__( @@ -282,11 +280,11 @@ def __init__( dt: The duration of each sample. samples: The array of samples. start_time: The time at which the signal starts. - carrier_freq: Frequency of the carrier. Subclasses such as SignalSums - represent the carriers of each signal in an array. - phase: The phase of the carrier. Subclasses such as SignalSums - represent the phase of each signal in an array. - name: name of the signal. + carrier_freq: Frequency of the carrier. Subclasses such as SignalSums represent the + carriers of each signal in an array. + phase: The phase of the carrier. Subclasses such as SignalSums represent the phase of + each signal in an array. + name: Name of the signal. """ self._dt = dt @@ -325,8 +323,8 @@ def from_Signal( ): r"""Constructs a ``DiscreteSignal`` object by sampling a ``Signal``\. - The optional argument ``sample_carrier`` controls whether or not to include the carrier - in the sampling. I.e.: + The optional argument ``sample_carrier`` controls whether or not to include the carrier in + the sampling. I.e.: - If ``sample_carrier == False``\, a ``DiscreteSignal`` is constructed with: - ``samples`` obtained by sampling ``signal.envelope``\. @@ -521,8 +519,8 @@ class SignalSum(SignalCollection, Signal): frequencies/phases for each term in the sum, and the ``envelope`` method returns an ``ArrayLike`` of the envelopes for each summand. - Internally, the signals are stored as a list in the ``components`` attribute, which can - be accessed via direct subscripting of the object. + Internally, the signals are stored as a list in the ``components`` attribute, which can be + accessed via direct subscripting of the object. """ def __init__(self, *signals, name: Optional[str] = None): @@ -612,8 +610,8 @@ def merged_env(t): class DiscreteSignalSum(DiscreteSignal, SignalSum): - """Represents a sum of piecewise constant signals, all with the same - time parameters: dt, number of samples, and start time. + """Represents a sum of piecewise constant signals, all with the same time parameters: dt, number + of samples, and start time. """ def __init__( @@ -626,13 +624,13 @@ def __init__( name: str = None, ): r"""Directly initialize a ``DiscreteSignalSum``\. Samples of all terms in the - sum are specified as a 2d array, with 0th axis indicating time, and 1st axis - indicating a term in the sum. + sum are specified as a 2d array, with 0th axis indicating time, and 1st axis indicating a + term in the sum. Args: dt: The duration of each sample. - samples: The 2d array representing a list whose elements are all envelope values - at a given time. + samples: The 2d array representing a list whose elements are all envelope values at a + given time. start_time: The time at which the signal starts. carrier_freq: Array with the carrier frequencies of each term in the sum. phase: Array with the phases of each term in the sum. @@ -682,8 +680,8 @@ def from_SignalSum( ): r"""Constructs a ``DiscreteSignalSum`` object by sampling a ``SignalSum``\. - The optional argument ``sample_carrier`` controls whether or not to include the carrier - in the sampling. I.e.: + The optional argument ``sample_carrier`` controls whether or not to include the carrier in + the sampling. I.e.: - If ``sample_carrier == False``, a ``DiscreteSignalSum`` is constructed with: - ``samples`` obtained by sampling ``signal_sum.envelope``\. @@ -960,18 +958,17 @@ def signal_multiply(sig1: Signal, sig2: Signal) -> SignalSum: def base_signal_multiply(sig1: Signal, sig2: Signal) -> Signal: - r"""Utility function for multiplying two elementary (non ``SignalSum``\) signals. - This function assumes ``sig1`` and ``sig2`` are legitimate instances of ``Signal`` - subclasses. + r"""Utility function for multiplying two elementary (non ``SignalSum``\) signals. This function + assumes ``sig1`` and ``sig2`` are legitimate instances of ``Signal`` subclasses. Special cases: - Multiplication of two constant ``Signal``\s returns a constant ``Signal``\. - - Multiplication of a constant ``Signal`` and a ``DiscreteSignal`` returns - a ``DiscreteSignal``\. + - Multiplication of a constant ``Signal`` and a ``DiscreteSignal`` returns a + ``DiscreteSignal``\. - If two ``DiscreteSignal``\s have compatible parameters, the resulting signals are - ``DiscreteSignal``\, with the multiplication being implemented by array multiplication of - the samples. + ``DiscreteSignal``\, with the multiplication being implemented by array multiplication of + the samples. - Lastly, if no special rules apply, the two ``Signal``\s are multiplied generically via multiplication of the envelopes as functions. diff --git a/qiskit_dynamics/signals/transfer_functions.py b/qiskit_dynamics/signals/transfer_functions.py index da9188b65..442defd41 100644 --- a/qiskit_dynamics/signals/transfer_functions.py +++ b/qiskit_dynamics/signals/transfer_functions.py @@ -38,8 +38,7 @@ def n_inputs(self): pass def __call__(self, *args, **kwargs) -> Union[Signal, List[Signal]]: - """ - Apply the transfer function to the input signals. + """Apply the transfer function to the input signals. Args: *args: The signals to which the transfer function will be applied. @@ -62,9 +61,7 @@ def __call__(self, *args, **kwargs) -> Union[Signal, List[Signal]]: @abstractmethod def _apply(self, *args, **kwargs) -> Union[Signal, List[Signal]]: - """ - Applies a transformation on a signal, such as a convolution, - low pass filter, etc. + """Applies a transformation on a signal, such as a convolution, low pass filter, etc. Args: *args: The signals to which the transfer function will be applied. @@ -87,9 +84,8 @@ class Convolution(BaseTransferFunction): def __init__(self, func: Callable): """ Args: - func: The convolution function specified in time. - This function will be normalized to one before doing - the convolution. To scale signals multiply them by a float. + func: The convolution function specified in time. This function will be normalized to + one before doing the convolution. To scale signals multiply them by a float. """ self._func = func @@ -99,15 +95,12 @@ def n_inputs(self): # pylint: disable=arguments-differ def _apply(self, signal: Signal) -> Signal: - """ - Applies a transformation on a signal, such as a convolution, - low pass filter, etc. Once a convolution is applied the signal - can longer have a carrier as the carrier is part of the signal - value and gets convolved. + """Applies a transformation on a signal, such as a convolution, low pass filter, etc. Once a + convolution is applied the signal can longer have a carrier as the carrier is part of the + signal value and gets convolved. Args: - signal: A signal or list of signals to which the - transfer function will be applied. + signal: A signal or list of signals to which the transfer function will be applied. Returns: signal: The transformed signal or list of signals. @@ -130,9 +123,7 @@ def _apply(self, signal: Signal) -> Signal: class FFTConvolution(BaseTransferFunction): - """ - Applies a convolution by moving into the fourier domain. - """ + """Applies a convolution by moving into the fourier domain.""" def __init__(self, func: Callable): self._func = func @@ -147,9 +138,7 @@ def _apply(self, signal: Signal) -> Signal: class Sampler(BaseTransferFunction): - """ - Re sample a signal by wrapping DiscreteSignal.from_Signal. - """ + """Resample a signal by wrapping DiscreteSignal.from_Signal.""" def __init__(self, dt: float, n_samples: int, start_time: float = 0): """ @@ -176,15 +165,16 @@ def _apply(self, signal: Signal) -> Signal: class IQMixer(BaseTransferFunction): - """ - Implements an IQ Mixer. The IQ mixer takes as input three signals: + """Implements an IQ Mixer. + + The IQ mixer takes as input three signals: - in-phase signal: I cos(w_if t + phi_I) - quadrature: Q cos(w_if t + phi_Q) - local oscillator: K cos(w_lo t) - In this implementation the local oscillator is specified by its frequency - w_lo and, without loss of generality we assume K = 1. Furthermore, we - require that the carrier frequency of the I and Q be identical. + In this implementation the local oscillator is specified by its frequency w_lo and, without loss + of generality we assume K = 1. Furthermore, we require that the carrier frequency of the I and Q + be identical. The output RF signal is defined by @@ -193,9 +183,9 @@ class IQMixer(BaseTransferFunction): where wp = w_lo + w_if and wp = w_lo - w_if. - The output of this transfer function will produce a piece-wise constant - that does not have a carrier frequency or phase. All information is in the - samples. Mixer imperfections are not included. + The output of this transfer function will produce a piece-wise constant that does not have a + carrier frequency or phase. All information is in the samples. Mixer imperfections are not + included. """ def __init__(self, lo: float):