Skip to content

Algorithms & the registry

An algorithm is the unit of the catalogue: one spectroscopy operation, implemented once. This page explains the contract every algorithm follows and how the registry discovers them.

The BaseAlgorithm contract

Every algorithm is a subclass of BaseAlgorithm that implements exactly one method, run:

from spectro_kernel.base import BaseAlgorithm, AlgorithmOutput
from spectro_kernel.registry import register_algorithm
from spectro_kernel.types import AlgorithmCategory, WorkContext


@register_algorithm("snr_der", category=AlgorithmCategory.QUALITY, version="1.0.0")
class SnrDer(BaseAlgorithm):
    """Derivative-based SNR estimator (DER_SNR)."""

    backend = "numpy"
    references = ["Stoehr et al. 2008, ASP Conf. Ser. 394, 505"]
    default_params = {}
    input_requirements = ["spectrum"]
    output_produces = ["metrics.snr_der"]

    def run(self, ctx: WorkContext, params: dict) -> AlgorithmOutput:
        ...
        return AlgorithmOutput.ok(metrics={"snr_der": snr})

The class-level fields are metadata - they make the algorithm self-describing:

Field Purpose
default_params Every accepted parameter and its default value.
required_params Parameters the caller must supply.
param_descriptions Human-readable help for each parameter.
input_requirements WorkContext slots that must be filled before running.
output_produces WorkContext paths the algorithm populates (documentation).
backend What the algorithm leans on - see provenance below.
references Literature citations for the method or wrapped implementation.
long_description Optional detailed explanation, caveats.

run vs execute

You never call run directly. You call execute (or the run_algorithm helper), which wraps run with everything that makes a result trustworthy:

flowchart LR
    CALL["run_algorithm(name, ctx, params)"] --> M["merge params<br/>with defaults"]
    M --> V["validate params<br/>+ check inputs"]
    V --> H1["hash input state"]
    H1 --> RUN["run()"]
    RUN --> H2["hash output state<br/>+ time it"]
    H2 --> REC["append ProcessingStep<br/>to ctx.history"]
    REC --> OUT["AlgorithmOutput"]

So every execution is validated, timed, hashed and recorded - for free, identically, for every algorithm in the catalogue.

run returns an AlgorithmOutput: a small summary with success, scalar metrics, JSON-safe artifacts, and an error/message. The real scientific payload (a normalised spectrum, a fitted line) is written into the WorkContext.

The registry & discovery

@register_algorithm adds the class to a global registry. Discovery is automatic: importing the algorithms package walks every submodule so each decorator runs.

from spectro_kernel import list_algorithms, describe_algorithm, run_algorithm

list_algorithms()                       # every entry, as dicts
list_algorithms("line_fitting")         # filtered by category
describe_algorithm("fit_gaussian_line") # full metadata for one
run_algorithm("snr_der", ctx)           # resolve + execute in one call

The consequence: adding one file under algorithms/<category>/ is the entire process of extending the catalogue. It then appears in the library, the CLI and the MCP server with no other change. (Walked through in Tutorial: add an algorithm.)

Categories

Algorithms are grouped into a stable taxonomy (AlgorithmCategory): io, continuum, smoothing, resampling, transform, line_detection, line_fitting, quality, radial_velocity, timeseries, stacking, correction, catalog, visualization, export, plus the CCD-reduction categories. Categories are how the catalogue stays navigable as it grows.

Provenance

This is what makes spectro-kernel honest about the we-don't-reinvent principle. Every algorithm declares:

  • a backend - astropy / specutils / astroquery mean a domain-standard implementation is wrapped; scipy / numpy mean the method is implemented here on general numerical primitives; plotly renders figures.
  • its references - the literature for the method, or the wrapped library.

Both are visible in spectro describe <name>, in the catalogue, and in every MCP tool description. Nothing is a black box: you can always see what an algorithm stands on and where the method comes from.

Naming conventions

  • snake_case, verb-first: fit_gaussian_line, not gaussian_line_fitter.
  • An explicit suffix when several implementations of one operation coexist: normalize_polynomial, normalize_percentile, normalize_edges - spectro-kernel keeps them side by side rather than hiding a choice behind a magic normalize().
  • A backend suffix when it matters: extract_spectrum_native.