A Goblin Reactor product

Changelog

stride-align documentation

All notable changes to stride-align are recorded here. The format follows Keep a Changelog and this project adheres to Semantic Versioning.

[0.6.0] - 2026-07-16

Added

Fixed

Performance

[0.5.1] - 2026-06-21

Performance

Matched workers=1 (the honest per-thread kernel comparison) vs rapidfuzz 3.14.5, bit-exact: x86 avx10_512 cdist geomean 1.36×, Mac M4 NEON 4.64× — every common scorer wins or reaches parity. Validated bit-exact on x86, Apple NEON, and LoongArch (LASX / LSX).

Fixed

Changed

[0.5.0] - 2026-06-10

Performance

Host Backend Geomean Wins/Ties/Losses
Intel AWS x86_avx10_512 0.94x 60 / 4 / 44
Mac M-series macos_arm64_neon 0.98x 76 / 1 / 31
Loongson linux_loongarch64_lasx 49.17x 108 / 0 / 0

Loongson is a clean sweep because upstream rapidfuzz ships no LoongArch wheel. Full per-workload tables in BENCHMARK.md#rapidfuzz-shim-full-surface-cross-arch-2026-06-10 and raw JSON under benchmarks/shim-full-*-2026-06-10.json.

Changed (breaking)

Migration for callers of the protein matrices: pass uppercase. The one-line fix at the call site is seq.upper() (or .casefold() for richer locales). Built-in protein matrices use uppercase single-letter codes, so any sequence already coming from FASTA / NCBI / UniProt is unaffected.

Motivation: the planned 128×128 case-sensitive text matrices need to map 'a' and 'A' to distinct indices. The previous design forced the text path to pay a .upper() round-trip on every encode just to be silently wrong for case-sensitive text users.

Added

The rapidfuzz shim plumbs score_cutoff through to the kernel for fuzz.ratio, fuzz.QRatio, and the four distance.Indel.* methods. This sets the pattern for adding kernel-level cutoff to Jaro, JaroWinkler, Damerau, and OSA in follow-up work.

Changed

The Phase D.3 partial_ratio family — sa.partial_ratio, sa.partial_token_sort_ratio, sa.partial_token_set_ratio — and the rapidfuzz shim's matching wrappers all benefit. Bit-exact match with rapidfuzz on every previously-divergent case in the documentation (color/colour, paul johnson/paul jones, the quick brown fox/the quick brown dog, Hello World, apple/an apple a day). The drop-in invariant "shim never overshoots upstream" is preserved on 2000 random-fuzz inputs (5 small overshoots in pathological 3-char-short cases, gap under 0.1 point).

Added

The shim's WRatio reimplements upstream's exact recipe (skip partial_* branch when len_ratio < 1.5; partial_scale = 0.6 when len_ratio >= 8) rather than routing through stride_align.WRatio (which always computes the partial branch and diverges from upstream when lengths are similar). Verified bit-exact against upstream on 11 contrast pairs including identity, case-swap, subset, length-disparate.

Known divergences: the partial_ratio family inherits Phase D.3's conservative-underestimate — stride-align enumerates fewer matching- block candidates than rapidfuzz, so for inputs where rapidfuzz finds a higher-scoring shifted window the shim returns a lower value. The invariant tested is "shim never overshoots upstream", not bit-exact parity. The weights= kwarg on Levenshtein.distance (custom insert/delete/replace costs) raises NotImplementedError; the rest of the kwargs are accepted with upstream semantics.

Known divergences: SW with multiple optimal alignments picks one path, parasail picks another (both score-correct, alignment differs); the sg_qb_de-style semi-global mode selectors and dnafull / nuc44 matrices are not yet provided; CIGAR for SW is the local-alignment-only CIGAR (parasail prepends leading edits — Cigar.beg_query / .beg_ref carry the same information).

sa.cdist gains match_score=, mismatch_score=, and width= kwargs (the affine-gap kwargs gap_open_score= / gap_extend_score= already existed for matrix-mode cdist and now also route through to the SW / NW per-row kernel). The module- level smith_waterman_scores, smith_waterman_farrar_scores, smith_waterman_normalized_scores, smith_waterman_farrar_normalized_scores, needleman_wunsch_scores, and needleman_wunsch_normalized_scores are also accepted as scorer= arguments. Dispatch happens Python-side via a ThreadPoolExecutor over rows because the C++ cdist kernel doesn't thread the SW / NW scoring parameters through its per-row dispatch; the per-row kernels themselves release the GIL so cpu_count > 1 is real parallelism. The C++ Scorer enum in src/cpp/topk.hpp gains four matching entries so the integer contract with the Python Scorer IntEnum stays one-to-one even though IDs 12-15 are Python-dispatched.

Documented

Added

Plus batch forms sa.jaccard_similarities(query, targets, n=2) and the three siblings, all returning ndarray[float64] with the query multiset built once and reused across targets. All four metrics are symmetric, bounded in [0, 1], and follow the identity convention (both empty → 1.0, one empty → 0.0). Engine runs in codepoint space — non-ASCII codepoints are first-class. N-gram keys are packed binary std::string (n · 4 bytes), which for the default n = 2 fits libstdc++'s small-string-optimisation buffer and avoids per-n-gram key allocation.

Both DPs are scalar O(m·n) time with two rolling rows for O(min(m,n)) (subsequence) or O(|b|) (substring) space. Engine runs in codepoint space — dispatch widens PyUnicode_DATA straight into std::vector<Codepoint>; non-ASCII codepoints are first-class.

Changed

Removed

[0.4.1] - 2026-06-01

Added

Changed

[0.4.0] - 2026-06-01

Added

All encoders dispatch through the same peel_to_ascii_string helper, accept str/bytes interchangeably, and skip non-letter / non-ASCII codepoints. Cross-checked against Apache Commons Codec reference data and the jellyfish, metaphone, doublemetaphone, and pyphonetics PyPI packages.

Changed

Fixed

Notes

[0.3.0] - 2026-05-24

Added

Changed

Fixed

[0.2.0] - 2026-05-19

Backfilled from git history; this entry was not in the tree at the v0.2.0 tag.

Added

Changed

[0.1.0] - 2026-05-18

Initial public release.

Added