Skip to content

sgnts.base.signal

Array operations not covered by the Python Array API standard.

The standard xp namespace (via array_namespace) covers creation and manipulation ops (zeros/ones/full/concat/stack/matmul/ sum/arange …) uniformly across numpy and torch. A few operations sgn-ts needs are not in the standard for every backend and so cannot be reached as xp.<op>:

  • pad — present as numpy.pad but absent from torch's array-API namespace (torch uses torch.nn.functional.pad with a different signature).
  • convolution / correlation (e.g. scipy.signal.correlate vs torch.nn.functional.conv1d) — genuinely backend-specific kernels.

This module is the single, named home for those escape-hatch operations: namespace-dispatched helpers so element code stays backend-blind where it can. Single-backend elements may still import their library directly — this module is for the backend-agnostic call sites.

mask_runs(mask)

Yield (start, stop, value) runs of a 1-D boolean mask.

Backend-agnostic replacement for numpy.ma.clump_masked / clump_unmasked (numpy.ma has no array-API equivalent): consecutive equal values are grouped into half-open index ranges [start, stop), in order. Run boundaries are data-dependent, so for device arrays this synchronizes with the host -- inherent to the use case, since run structure is host-side metadata (e.g. buffer boundaries).

Source code in src/sgnts/base/signal.py
def mask_runs(mask: Array) -> Iterator[tuple[int, int, bool]]:
    """Yield ``(start, stop, value)`` runs of a 1-D boolean mask.

    Backend-agnostic replacement for ``numpy.ma.clump_masked`` /
    ``clump_unmasked`` (``numpy.ma`` has no array-API equivalent):
    consecutive equal values are grouped into half-open index ranges
    ``[start, stop)``, in order.  Run boundaries are data-dependent, so
    for device arrays this synchronizes with the host -- inherent to the
    use case, since run structure is host-side metadata (e.g. buffer
    boundaries).
    """
    n = mask.shape[0]
    if n == 0:
        return
    xp = array_namespace(mask)
    assert xp is not None
    changes = xp.nonzero(mask[1:] != mask[:-1])[0]
    bounds = [0, *(int(i) + 1 for i in changes), n]
    for start, stop in zip(bounds[:-1], bounds[1:]):
        yield start, stop, bool(mask[start])

pad(data, pad_width)

Zero-pad the last axis of data by (before, after) samples.

Dispatches on the array's backend because pad is not uniformly available in the Array API standard namespace (numpy has it, torch does not).

Parameters:

Name Type Description Default
data Array

Array, the array to pad (numpy or torch).

required
pad_width tuple[int, int]

tuple[int, int], samples to pad before and after along the last axis.

required

Returns:

Type Description
Array

Array, the padded array, same backend as data.

Source code in src/sgnts/base/signal.py
def pad(data: Array, pad_width: tuple[int, int]) -> Array:
    """Zero-pad the last axis of ``data`` by ``(before, after)`` samples.

    Dispatches on the array's backend because ``pad`` is not uniformly available
    in the Array API standard namespace (numpy has it, torch does not).

    Args:
        data:
            Array, the array to pad (numpy or torch).
        pad_width:
            tuple[int, int], samples to pad before and after along the last axis.

    Returns:
        Array, the padded array, same backend as ``data``.
    """
    name = backend_name(data)
    if name == "torch":
        import torch

        return torch.nn.functional.pad(data, pad_width, "constant")
    # numpy (and anything else that quacks like numpy.pad)
    npad = [(0, 0)] * data.ndim
    npad[-1] = pad_width
    return numpy.pad(data, npad, "constant")