A Goblin Reactor product

Substitution matrices

stride-align documentation

stride_align.matrices.SubstitutionMatrix is the per-pair scoring table consumed by Smith-Waterman / Needleman-Wunsch and the matrix-aware cdist paths. A catalog of built-in protein (BLOSUM, PAM), nucleotide (NUC.4.4, simple DNA), and case-sensitive ASCII matrices — plus an NCBI-text parser and rule-built factories — covers the common bioinformatics and text-similarity workloads; fully custom matrices over any alphabet are first-class too.

The SubstitutionMatrix dataclass

from stride_align.matrices import SubstitutionMatrix
import numpy as np

m = SubstitutionMatrix(
    name="MyMatrix",
    alphabet="ACGT",                       # str — codepoints → row/col indices
    matrix=np.array([                      # int8 ndarray, shape (n, n)
        [ 2, -1, -1, -1],
        [-1,  2, -1, -1],
        [-1, -1,  2, -1],
        [-1, -1, -1,  2],
    ], dtype=np.int8),
    gap_score=-2,                          # default linear-gap penalty
    wildcard="*",                          # optional sentinel for "not in alphabet"
)

Required fields:

Field Type Meaning
name str human-readable identifier
alphabet str each codepoint maps to row/col index by position
matrix np.ndarray[int8] shape (n, n) symmetric or asymmetric per-pair score
gap_score int default linear-gap penalty
wildcard str (single char) or None codepoint that absorbs "outside the alphabet"

Optional gap_open / gap_extend fields override the alignment- call defaults; explicit gap_open_score= / gap_extend_score= keyword args on the alignment call still take precedence.

Methods

Method Purpose
encode(sequence: str) -> bytes translate a string to alphabet indices for direct kernel consumption (zero-copy when the alphabet is single-byte)
score(query: str, target: str) -> int look up the substitution score for two single characters
stride() -> int the alignment-kernel-internal stride (== len(alphabet))
score_step_limit(*, gap_score=None, gap_open=None, gap_extend=None) -> int maximum DP step size given the scoring scheme; useful for choosing width= on the alignment call
from_ncbi_text(text, *, name=None, gap_score=-4, wildcard='X') (classmethod) parse an NCBI-format text dump (e.g. a saved BLOSUM62 table) into a SubstitutionMatrix

encode is a pure translation-table lookup since 0.5.0 — case-folding is no longer implicit. Pass seq.upper() if your alphabet is uppercase and the input might not be (see CHANGELOG [0.5.0] SubstitutionMatrix.encode no longer case-folds).

Built-in catalog

Every matrix below is a SubstitutionMatrix instance you can pass directly as matrix= to any Smith-Waterman / Needleman-Wunsch / cdist entry point. The empirical biological matrices (BLOSUM, PAM, NUC.4.4) are the public-domain tables from the NCBI BLAST distribution — the numbers are bundled verbatim under stride_align/matrix_data/ and parsed once at import. Each carries the affine gap penalties NCBI conventionally pairs with it as gap_open / gap_extend defaults; override per call with gap_open_score= / gap_extend_score= (or gap_score= for a linear gap).

import stride_align as sa
from stride_align.matrices import blosum62

sa.smith_waterman_score(
    "MQDRVKRPMNAFIVWSRDQRRKMALEN",
    "KQDLRKLSVRNCAFRLRVKMWERPPNAFIVWSRDQRRKMALEN",
    matrix=blosum62,
    gap_open_score=-10,
    gap_extend_score=-1,
)

Protein — BLOSUM

from stride_align.matrices import (
    blosum30, blosum35, blosum40, blosum45, blosum50, blosum55,
    blosum60, blosum62, blosum65, blosum70, blosum75, blosum80,
    blosum85, blosum90, blosum100,
)

The full BLOSUM series, 30 through 100. Lower numbers target more divergent sequences, higher numbers more similar ones; blosum62 is the de-facto default for protein search. All use the NCBI 24-letter alphabet ARNDCQEGHILKMFPSTWYVBZX* with X as the wildcard and * the BLAST stop character.

Protein — PAM

from stride_align.matrices import pam10, pam30, pam250, pam500  # …and every step in between

The complete PAM (Point Accepted Mutation) series: pam10, pam20, …, pam500 — every multiple of 10 from 10 to 500 (50 matrices). The relationship is inverted relative to BLOSUM: low PAM numbers model closely related sequences, high numbers model distant ones (pam250 is the classic distant-homology choice; the high end pam400pam500 targets very deep, divergent comparisons). Same 24-letter alphabet as BLOSUM.

Nucleotide

from stride_align.matrices import nuc44, dna_match
Matrix Alphabet Use
nuc44 ATGCSWRYKMBVHDN (IUPAC) NCBI NUC.4.4 / "DNAfull" — scores IUPAC ambiguity codes (e.g. R = A/G), with N the any-base wildcard. Match +5, transversion -4.
dna_match ACGTN A plain +5 / -4 match-mismatch matrix over the four bases (N wildcard) — the classic BLASTN default; use when your sequences contain only A/C/G/T.

Text / generated

from stride_align.matrices import ascii_text, identity_matrix, ascii_matrix

For text-similarity work where capitalisation and punctuation are signal rather than noise:

Symbol Kind Description
ascii_text instance A ready-made case-sensitive 128×128 ASCII matrix ('a''A'): equal codepoints score +1, differing -1. chr(127) is the wildcard slot that absorbs any non-ASCII input.
ascii_matrix(*, match=1, mismatch=-1, ...) factory Build a custom-scored variant of the above.
identity_matrix(alphabet, *, match=1, mismatch=0, wildcard="*", ...) factory An identity matrix over any single-codepoint alphabet — equal symbols score match, everything else mismatch. The wildcard is appended if absent and never matches (not even itself), so unknown input cannot score a spurious hit.
import stride_align as sa
from stride_align.matrices import identity_matrix, ascii_text

# An identity matrix over a custom alphabet (RNA, +2 / -1, gap -2):
rna = identity_matrix("ACGU", match=2, mismatch=-1, gap_score=-2)
sa.smith_waterman_score("ACGUACGU", "ACGUUCGU", matrix=rna)

# Plain text, case matters; non-ASCII folds to the wildcard:
sa.needleman_wunsch_score("Café", "cafe", matrix=ascii_text)

Both factories accept name=, gap_score=, gap_open=, and gap_extend= so the resulting matrix carries the defaults you want.

Keyboard typo matrices

stride_align.matrices.keyboard provides example substitution matrices built from real human typing errors — the Aalto "136 Million Keystrokes" dataset, used with the authors' permission. They score how likely it is that one character was typed when another was meant, so an aligner treats a plausible slip as a near-match rather than a flat mismatch. The qwerty matrix ships bundled (keyboard.available() lists what's present); build matrices for other layouts or alphabets from your own confusion data with from_confusion_counts.

import stride_align as sa
from stride_align.matrices import keyboard

sa.smith_waterman_score("teh", "the", matrix=keyboard.qwerty)

Orientation: the query is the misspelled side

Like every stride_align matrix these are m[a][b] with a = query, b = target — and here a is the character actually typed (the mistake) and b is the character that was intended (the correction). So pass the misspelled / user-entered string as the query and the canonical string as the target.

These matrices are asymmetric (unlike symmetric BLOSUM/PAM), so orientation matters. If your query is instead the correct string, flip the matrix with NumPy's .T (transpose), which swaps the axes to m[intended][misspelled]:

import numpy as np
from stride_align.matrices import SubstitutionMatrix, keyboard

fwd = keyboard.qwerty
rev = SubstitutionMatrix(
    name=fwd.name + ".T", alphabet=fwd.alphabet,
    matrix=np.ascontiguousarray(fwd.matrix.T),   # .T swaps query/target
    gap_score=fwd.gap_score, wildcard=fwd.wildcard,
)

Building and loading

Function Purpose
from_confusion_counts(counts, *, alphabet=ASCII_ALPHABET, scale=2.0, match_margin=4, floor=None, ...) build a log-odds matrix from a {(typed, intended): count} mapping (or an (N, N) count array)
from_npy(path, *, transpose=False, ...) load an int8 (N, N) .npy matrix (e.g. from tools/build_keyboard_matrices.py); transpose=True applies the .T flip
available() names of the bundled matrices in this install

Scoring is a log-odds ratio (scale · log2(observed / expected), the same construction BLOSUM/PAM use), with the identity diagonal set above the strongest substitution so an exact match always wins. The bundled matrices are derived log-odds artifacts, not the raw keystroke data; see the repository NOTICE and docs/keyboard-matrix-external-sources.md for the attribution and the scope of the permission.

Fully custom matrices

The catalog covers the common cases, but SubstitutionMatrix is alphabet-agnostic — for full control, construct one directly from any single-codepoint alphabet and any (n, n) int8 matrix (see the dataclass example at the top of this page), or load one from NCBI text.

Loading from NCBI text

The from_ncbi_text classmethod handles the on-disk format NCBI distributes (whitespace-separated header line of letters, then one row per letter). Useful if you want to bundle a custom matrix without checking the values into Python source:

import pathlib
from stride_align.matrices import SubstitutionMatrix

m = SubstitutionMatrix.from_ncbi_text(
    pathlib.Path("/data/custom-matrix.txt").read_text(),
    name="custom",
    gap_score=-4,
    wildcard="X",
)