Development Guide#

This page outlines the workflow, conventions, and extension points for contributing to sigpyproc.


Development Setup#

Clone the repository and install development dependencies:

git clone https://github.com/FRBs/sigpyproc3.git
cd sigpyproc3
uv sync --extra tests --extra dev --extra docs

This installs:

  • Runtime dependencies

  • Test suite requirements

  • Linting and static typing tools

  • Documentation build dependencies


Contribution Workflow#

Typical development cycle:

  1. Create a feature branch.

  2. Implement changes.

  3. Add or update tests.

  4. Run quality checks.

  5. Build documentation locally to verify changes.

  6. Open a pull request.

All contributions should maintain API consistency and pass the full test suite.


Extending sigpyproc#

The architecture separates:

  • User-facing API (high-level classes)

  • Core numerical kernels (Numba-accelerated)

  • Structured metadata handling

New functionality typically involves:

  1. Adding a method to a data object (e.g. Filterbank class)

  2. Implementing or extending a Numba kernel

  3. Writing tests

  4. Documenting the public API


Example: Adding a new function: bandpass()#

Goal: Add a new function called bandpass, which will return the total power as a function of frequency for the filterbank data.

Step 1 — Add the API Method#

As this function will run on data with both time and frequency resolution, it belongs in the Filterbank class.

def bandpass(self, gulp: int = 16384, **plan_kwargs: Unpack[PlanKwargs]) -> TimeSeries:
    bpass_ar = np.zeros(self.header.nchans, dtype=np.float32)
    num_samples = 0

    for nsamps, _, data in self.read_plan(**plan_kwargs):
        kernels.extract_bpass(data, bpass_ar, self.header.nchans, nsamps)
        num_samples += nsamps

    bpass_ar /= num_samples

    hdr_changes = {"nchans": 1, "nsamples": len(bpass_ar)}
    return TimeSeries(bpass_ar, self.header.new_header(hdr_changes))

This method:

  • Streams data in blocks

  • Calls a compiled kernel

  • Returns a new TimeSeries instance

Public-facing methods should remain clean and explicit.


Step 2 — Implement the Numba Kernel#

Add the corresponding kernel to sigpyproc/core/kernels.py:

@njit(
    ["void(u1[:], f4[:], i4, i4)", "void(f4[:], f4[:], i4, i4)"],
    cache=True,
    parallel=True,
    fastmath=True,
)
def extract_bpass(inarray, outarray, nchans, nsamps):
    for ichan in prange(nchans):
        for isamp in range(nsamps):
            outarray[ichan] += inarray[nchans * isamp + ichan]

This kernel:

  • Accumulates power along the time axis

  • Uses parallel=True to enable Numba’s parallel execution

  • Is intentionally simple and memory-linear

We keep kernels small, predictable, and side-effect free.


Quality Checks#

From the repository root:

ruff check
ruff format --check
ty check
pytest --cov=src --cov-report=html -v

All checks must pass before submitting a pull request.


Building Documentation#

sphinx-build -b html docs docs/_build/html -W --keep-going

Open docs/_build/html/index.html in your browser to verify the changes. Documentation builds must complete without warnings.


Reporting Issues#

Please report bugs or feature requests via the GitHub issue tracker.

Include:

  • Minimal reproducible example

  • Python version

  • Operating system

  • Relevant stack traces


Documentation Policy#

  • API documentation lives in src/sigpyproc/** using NumPy-style docstrings.

  • Narrative documentation lives in docs/*.md.

  • Per-module API pages are generated via autosummary.


Contributing code or documentation#

An excellent place to start is the AstroPy developer docs.