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/astroquerymean a domain-standard implementation is wrapped;scipy/numpymean the method is implemented here on general numerical primitives;plotlyrenders 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, notgaussian_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 magicnormalize(). - A backend suffix when it matters:
extract_spectrum_native.