A Goblin Reactor product

Adding a new algorithm to stride-align

stride-align documentation

A grep-able checklist for the touch points a new algorithm has to hit before it's safe to ship. The library bundles three loosely-coupled things, each with its own integration surface:

Most "I'm adding a new algorithm" tasks mean scorer. The bulk of this doc is the scorer checklist; the other two cases get shorter sections at the end.

If you skip a step here, the library will probably still compile — backends fall back gracefully, the Python module imports something that "works" — and the regression won't show up until someone uses the new code through cdist_top_k or extract() and gets the wrong answer or a runtime crash. Walk the whole list.


Adding a new scorer

1. Public C++ header — include/stride_align/{name}.hpp

2. SIMD kernel — src/cpp/{name}_simd.hpp

3. Per-pair dispatch — src/cpp/{name}_dispatch.hpp

4. Backend Implementation bundles — src/cpp/backends/*.hpp

Every backend bundle (x86_sse41.hpp, x86_avx2.hpp, x86_avx512bwvl.hpp, x86_avx10_256.hpp, x86_avx10_512.hpp, linux_aarch64_neon.hpp, linux_aarch64_sve.hpp, linux_aarch64_sve2.hpp, macos_arm64_neon.hpp, arm_neon128.hpp, linux_loongarch64_lsx.hpp, linux_loongarch64_lasx.hpp, linux_powerpc64_vsx.hpp, linux_riscv64_rvv.hpp) exposes a static method per scorer:

static std::vector<double> {name}_similarities(...);  // or *_scores

If you don't add the method to every bundle, the binding layer's if constexpr (requires { ... }) falls back to the scalar dispatch on that backend silently — the user installs the wheel and notices that the new scorer is 10× slower on their machine than it should be.

5. Scorer enum — keep C++ and Python in sync

6. cdist_runtime.hpp helpers

Each of these has a switch (scorer) that the compiler will warn on when a case is missing (we build with -Wall -Wextra -Wpedantic). Don't dismiss the warning:

7. cdist row dispatch — src/cpp/cdist_simd.hpp

The default branch raises RuntimeError with the scorer integer — useful but only fires if you forgot the case.

8. Structural validators

If the scorer imposes a structural constraint on inputs (Hamming requires equal lengths, anything else might too), add a validator next to validate_hamming_lengths in cdist_runtime.hpp and call it from each cdist variant's entry point. Otherwise the SIMD path will return garbage or read past a buffer.

9. Python bindings — src/cpp/module_bindings.hpp

Per-scorer m.def(...) blocks for:

Plus the function-reference → Scorer enum registration so callers can pass sa.{name}_scores instead of Scorer.NAME:

register_attr("{name}_scores", Scorer::Name);
register_attr("{name}_normalized_scores", Scorer::NameNormalized);

This is what makes cdist(qs, ts, scorer=sa.jaro_similarities) work identically to scorer=sa.Scorer.JARO.

10. Python wrappers — src/stride_align/__init__.py

11. Pure-Python fallback — src/stride_align/_pybackend.py

Optional but recommended: Levenshtein has one, the others don't, which means stride-align is unusable for those scorers on architectures without a C++ backend. If you want the scorer to work on every platform (including ones we haven't built a wheel for yet), add a scalar reference implementation here. It's also the obvious place to read when a future contributor needs an unambiguous spec for what the scorer is supposed to compute.

12. Tests

The library has a strong convention that every scorer is tested through every public path. Mirror what exists for an existing scorer:

13. Cross-arch validation

The CI matrix doesn't cover every architecture we ship to (POWER8, RISC-V, LoongArch in particular get hand-tested). Before shipping a new scorer:

14. Documentation


Adding a new alignment algorithm (SW / NW variant)

Alignment algorithms (Smith-Waterman, Needleman-Wunsch, Farrar SW) don't go through the Scorer enum. They're wired through the per-backend Implementation bundle directly.

  1. Add the static methods to every Implementation<BackendKind::*> in src/cpp/backends/*.hpp and to the generic backend in src/cpp/backends/generic.hpp. The same warning from scorers applies: missing on one backend = silent scalar fallback.
  2. SIMD kernel headers under src/cpp/backends/ need the new kernel per ISA family (x86_fixed_kernel.hpp, arm_neon_kernel.hpp, arm_sve_kernel.hpp, etc.).
  3. Python bindings — m.def("smith_waterman_X_score", ...) etc. in src/cpp/module_bindings.hpp.
  4. Python wrappers and __all__src/stride_align/__init__.py.
  5. Pure-Python reference in _pybackend.py — strongly recommended, this is the spec for what the C++ should compute.
  6. Tests — same shape as the scorer checklist (scalar correctness, per-arch parity, the top-k/best wrappers).

There's no length-pruning bound for alignment algorithms because they don't go through the cdist variants. Skip step 6 from the scorer checklist.


Adding a new SIMD backend (architecture)

You're publishing a new Implementation<BackendKind::X> module; the algorithms themselves don't change.

  1. src/cpp/backends/{platform_arch_isa}.hpp — the Implementation struct exposing every scorer + alignment method, using the appropriate kernel header.
  2. src/cpp/backends/{platform_arch_isa}.cpp — one-line NB_MODULE(_name, m) that calls bindings::bind_backend_module<Implementation>(m, "...").
  3. CMakeLists.txtadd_stride_align_backend(_name src/cpp/backends/X.cpp) gated by the appropriate CMAKE_SYSTEM_PROCESSOR check and ISA feature flag. Also add to STRIDE_ALIGN_CPU_COMPILE_DEFINITIONS.
  4. src/cpp/cpu.cpp / cpu.hpp — runtime detection of the new ISA flag (CPUID on x86, HWCAP on Linux ARM, /proc/cpuinfo elsewhere).
  5. src/stride_align/_cpu.py — the BackendKind enum and its _backend_module_name() mapping in __init__.py.
  6. src/stride_align/__init__.py_select_backend() priority order. Put the new backend ahead of any backend it strictly supersedes (e.g. AVX-512 over AVX2 on systems with both).
  7. Wheelspyproject.toml cibuildwheel config if you want prebuilt wheels for the new arch.
  8. Testspytest tests/ on real hardware; force-select the new backend via BackendKind.X if you need to bypass auto-detection.
  9. Benchmarks — produce a CSV with tools/benchmark_libs.py and commit it under benchmarks/{arch}-{date}.csv.
  10. DocsREADME.md supported-architecture list.

Things that catch you out