# index.html.md
# relag
**relag** is a Python binding for the `relag` Rust crate. It provides fast,
SIMD-accelerated tools for working with datasets generated by a *Random Generative
Process* (RGP) — datasets where items share latent structure (e.g. protein families, molecules)
that makes naive random splits leak information between train and test.
This is the human-readable doc, but we also have a LLM doc so you favorite chatbot or agent can read the documentation.
We follow the [llms.txt](https://llmstxt.org) standard, which means you can access the llm doc index with
llms.txt or the full documentation in one document with llms-full.txt.
Happy programming!
## Modules
* [relag.utils](api/utils.md)
* [`BitFingerprint`](api/utils.md#relag.utils.BitFingerprint)
* [`RealFingerprint`](api/utils.md#relag.utils.RealFingerprint)
* [`Vector`](api/utils.md#relag.utils.Vector)
* [`PdbStructure`](api/utils.md#relag.utils.PdbStructure)
* [`read_fasta()`](api/utils.md#relag.utils.read_fasta)
* [`largest_cluster()`](api/utils.md#relag.utils.largest_cluster)
* [`SWPattern`](api/utils.md#relag.utils.SWPattern)
* [`SWPatternSet`](api/utils.md#relag.utils.SWPatternSet)
* [`SWSequence`](api/utils.md#relag.utils.SWSequence)
* [`SWWord`](api/utils.md#relag.utils.SWWord)
* [relag.kernels](api/kernels.md)
* [Kernel variant selector](api/kernels.md#module-relag.kernels)
* [relag.kernels.alignments](api/kernels.md#relag-kernels-alignments)
* [relag.kernels.molecules](api/kernels.md#relag-kernels-molecules)
* [relag.kernels.structures](api/kernels.md#relag-kernels-structures)
* [relag.kernels.protspam](api/kernels.md#relag-kernels-protspam)
* [relag.kernels.vectors](api/kernels.md#relag-kernels-vectors)
* [relag.core](api/core.md)
* [Graph construction](api/core.md#graph-construction)
* [HNSW approximate nearest neighbours](api/core.md#hnsw-approximate-nearest-neighbours)
* [Graph algorithms](api/core.md#graph-algorithms)
* [Exact search](api/core.md#exact-search)
# core.html.md
# relag.core
## Graph construction
### *class* relag.core.EdgeStore(node_count: int, edges: Sequence[tuple[int, int, float]])
Bases: `object`
A compact, flat list of weighted directed edges between integer node IDs.
`EdgeStore` is the central data carrier in relag: it is produced by
`exact_edges` and `HNSWState.edges`, consumed by `CsrGraph`, and can be
persisted to disk for later reuse.
Each edge is a triple `(src, dst, weight)` where `src` and `dst` are
zero-based node indices in `[0, node_count)` and `weight` is a `float32`
value.
Example:
```default
from relag.core import EdgeStore
store = EdgeStore(node_count=3, edges=[(0, 1, 0.9), (1, 2, 0.7)])
print(len(store)) # 2
print(store[0]) # (0, 1, 0.9)
for src, dst, w in store:
print(src, dst, w)
# numpy-style boolean masking: keep only the edges where mask[i] is True
store[[True, False]] # EdgeStore with only (0, 1, 0.9)
```
#### edges() → list[tuple[int, int, float]]
Return all edges as a list of `(src, dst, weight)` triples.
#### graph(inweight_type: [INWeightType](#relag.core.INWeightType) = INWeightType.Distance) → [CsrGraph](#relag.core.CsrGraph)
Build a `CsrGraph` from this edge store.
* **Parameters:**
**inweight_type** – How to convert the raw edge weights to similarity-like weights.
See `INWeightType`. Pass `INWeightType.Unweighted` to ignore
weights entirely (every edge gets weight `1.0`). Defaults to
`INWeightType.Distance`, meaning weights are assumed to be a distance
measurement, and are converted to similarities.
* **Returns:**
A `CsrGraph` backed by this edge list.
#### *static* load(path: str) → [EdgeStore](#relag.core.EdgeStore)
Load an EdgeStore that was previously saved with `EdgeStore.save`. It infers the format
from the extension of the path, `.edgelist` or `.edgestr`.
**Version compatibility (binary format only):** The binary `.edgestr` format is versioned
to the exact package version. A file saved with a different package version will fail to
load with a version mismatch error. There is no forward or backward compatibility. The text
`.edgelist` format is not versioned and may be portable across versions.
* **Parameters:**
**path** – Path to the file produced by `save`.
* **Returns:**
The deserialized `EdgeStore`.
* **Raises:**
**IOError** – If the file cannot be read, the format is invalid, or (for `.edgestr` files)
the saved version does not match the running package version.
#### node_count() → int
Return the total number of nodes this store was created with.
#### save(path: str) → None
Serialize this EdgeStore to disk. It supports two file formats: `text` with
`.edgelist` extension or `binary` with `.edgestr` extension. Binary is usually 2x
more space efficient at the cost of not being human-readable.
**Version compatibility (binary format only):** The binary `.edgestr` format is versioned
to the exact package version and is not forward or backward compatible. A file saved with
a different package version will fail to load with a version mismatch error. The text
`.edgelist` format has no version stamp and may be portable across versions.
* **Parameters:**
**path** – Destination file path.
* **Raises:**
**IOError** – On any I/O failure.
Example:
```default
from relag.core import EdgeStore
store = EdgeStore(node_count=3, edges=[(0, 1, 0.9), (1, 2, 0.7)])
store.save("my/path/myedges.edgelist") # Text format
store.save("my/path/myedges.edgestr") # Bin format
```
#### return *: [EdgeStore](#relag.core.EdgeStore)*
### *class* relag.core.CsrGraph(edges: [EdgeStore](#relag.core.EdgeStore), inweight_type: [INWeightType](#relag.core.INWeightType) = INWeightType.Distance)
Bases: `object`
Compressed Sparse Row graph built from an `EdgeStore`. A CSR graph is compact, and highly
efficient for traversal, exploiting cache structure and maximizing cache hits. However, it is
immutable, so adding a new node or edge require building the full graph again.
`CsrGraph` is an immutable adjacency structure. Because of its property of being very
efficient for traversal, it is used by graph algorithms such as `partition` and
`connected_components`.
Properties:
: - `n`: Number of nodes.
- `m`: Sum of all edge weights (or total edge count when `inweight_type=INWeightType.Unweighted`).
Example:
```default
from relag.core import EdgeStore, CsrGraph
store = EdgeStore(4, [(0,1,0.9),(1,2,0.8),(2,3,0.6)])
g = CsrGraph(store)
print(g.n) # 4
print(g.neighbors(1)) # [(0, 0.9), (2, 0.8)]
print(g.strength(1)) # 1.7
```
#### m
Total weight (each edge counted once)
#### n
Number of nodes
#### neighbors(v: int) → list[tuple[int, float]]
Return the neighbors of node `v` as a list of `(node_id, weight)` pairs.
* **Parameters:**
**v** – Zero-based node index.
#### strength(v: int) → float
Return the strength (weighted degree) of node `v`.
When the graph is unweighted this equals the degree (number of neighbors).
* **Parameters:**
**v** – Zero-based node index.
#### subgraph(nodes: Sequence[int]) → tuple[[CsrGraph](#relag.core.CsrGraph), dict[int, int]]
Extract a subgraph composed of a subset of nodes.
New node ids are assigned contiguously in the order `nodes` is given.
All edges pointing outside the subgraph are ignores.
* **Parameters:**
**nodes** – Node ids to keep, in the desired new order.
* **Returns:**
A tuple `(subgraph, mapping)` where `mapping` is a dict from old node id
to new node id.
Example:
```default
sub, mapping = g.subgraph([2, 0, 3])
print(sub.n) # 3
print(mapping) # {0: 1, 2: 0, 3: 2}
```
#### edges *: [EdgeStore](#relag.core.EdgeStore)*
#### inweight_type *: [INWeightType](#relag.core.INWeightType)*
#### return *: [CsrGraph](#relag.core.CsrGraph)*
## HNSW approximate nearest neighbours
### *class* relag.core.HNSWConfig(proximity_threshold: float = 0.0, ef_construction: int = 64, m: int = 16, m_max: int = 16, m_max0: int = 32, m_l: float = 0.36, ef_init: int = 1, extend_candidates: bool = False, keep_pruned_connections: bool = True, keep_all_edges: bool = True, cache_capacity: int = 2000000, cache_shards: int = 64, n_threads: int = 0, shuffle: bool = False, use_heuristic: bool = True, strict_ef: bool = False, threshold_based_neighbourhood: bool = False)
Bases: `object`
Configuration for the HNSW approximate nearest-neighbour index.
All parameters have sensible defaults; in most cases you only need to `ef_construction`, and
`proximity_threshold`.
**Parameters:**
- `proximity_threshold` *(float)* — Distance threshold under which a connection is established in the proximity graph.
- `ef_construction` *(int)* — Candidate list size during build. Default `64`.
- `m` *(int)* — Bidirectional links per node at layers > 0.
Higher = better recall, more memory, slower build. Typical: 8–64.
- `m_max` *(int)* — Maximum connections per node at layers > 0. Usually equal to `m`.
- `m_max0` *(int)* — Maximum connections at layer 0. Usually `2 * m`.
- `m_l` *(float)* — Level generation factor. Usually `0.36 ≈ 1 / ln(m)`.
- `ef_init` *(int)* — Candidate list size during initial-layer insertion. Almost always 1 for
a greedy search.
- `extend_candidates` *(bool)* — Use neighbors of neighbors as candidates, making search more exhaustive.
- `keep_pruned_connections` *(bool)* — Retain discarded candidates to fill up to `m` connections when not enough connections are found.
- `keep_all_edges` *(bool)* — Record every below-threshold distance computed during build into
the proximity graph returned by `edges()`. Default `True`. Setting it to `False` makes
`edges()` return `None`, but removes a per-hit locked hashmap insert from the hot path.
Worth doing when the proximity graph is not required.
- `cache_capacity` *(int)* — Maximum cached kernel scores. Increasing it increase the memory
footprint, but also cache hits, which can improve runtime performances for computationally expensive kernels.
Set to `0` to disable the cache entirely — every distance is recomputed directly, which is
faster for cheap kernels (e.g. `TanimotoBit`/`TanimotoReal`) where cache overhead exceeds the
cost of the kernel itself.
- `cache_shards` *(int)* — Number of cache shards (reduces lock contention).
- `n_threads` *(int)* — Threads used during build. `0` = all available cores.
- `shuffle` *(bool)* — Shuffle insertion order before building. Can create a less biased
graph, but reduces cache hits, which increases runtime.
- `use_heuristic` *(bool)* — Use the heuristic neighbour-selection from the paper (recommended).
- `strict_ef` *(bool)* — If `True`, enforces the result set size to exactly `ef` during search. Empirically, setting this to `False` can improve runtime performance, as it allows halving `ef_construction` without sacrificing accuracy.
- `threshold_based_neighbourhood` *(bool)* — Select a minimum of `m` neighbors like the classic algorithm, but doesn’t bound the neighbourhood size as all candidates that are closer than the threshold are kept.
#### cache_capacity *: int*
#### cache_shards *: int*
#### dict() → dict
Return the configuration as a plain Python dict.
#### ef_construction *: int*
#### ef_init *: int*
#### extend_candidates *: bool*
#### keep_all_edges *: bool*
#### keep_pruned_connections *: bool*
#### m *: int*
#### m_l *: float*
#### m_max *: int*
#### m_max0 *: int*
#### n_threads *: int*
#### proximity_threshold *: float*
#### shuffle *: bool*
#### strict_ef *: bool*
#### threshold_based_neighbourhood *: bool*
#### use_heuristic *: bool*
#### return *: [HNSWConfig](#relag.core.HNSWConfig)*
### *class* relag.core.HNSWState(variant: [kernels.KernelVariant](kernels.md#relag.kernels.KernelVariant), data: Any, \*args: Any, proximity_threshold: float = 0.0, ef_construction: int = 64, m: int = 16, m_max: int = 16, m_max0: int = 32, m_l: float = 0.36, ef_init: int = 1, extend_candidates: bool = False, keep_pruned_connections: bool = True, keep_all_edges: bool = True, cache_capacity: int = 2000000, cache_shards: int = 64, n_threads: int = 0, shuffle: bool = False, use_heuristic: bool = True, strict_ef: bool = False, threshold_based_neighbourhood: bool = False, \*\*kwargs: Any)
Bases: `object`
Stateful Hierarchical Navigable Small World (HNSW) approximate nearest-neighbour index. This
class is used to build and search in the index, whereas `HNSWIndex` is a read-only *internal*
representation of the index used for saving / loading / structure exploration.
`HNSWState` wraps the dataset and the graph index together. The typical
workflow is:
1. Construct with the data and a `KernelVariant`.
2. Call `build()` to insert all items and form the graph.
3. Call `search()` to query, or `edges()` to extract the proximity graph
for downstream clustering / splitting.
HNSW parameters (`proximity_threshold`, `ef_construction`, …) are the same as `HNSWConfig`
and can be passed directly to the constructor as keyword arguments.
`data` may be any Python iterable — a `list`, a `tuple`, a generator, anything with
`__iter__`. It’s drained one item at a time into the constructor’s own Rust-owned copy,
so a generator never needs to be fully materialized into a Python-side list first — both
copies never need to coexist at once. That matters when a single item’s Python
representation is large enough that two full-dataset copies wouldn’t fit in memory
together. When `data` supports `len()` (a `list`/`tuple`), that’s used to
pre-size the Rust-owned copy exactly; it doesn’t change how `data` is walked.
* **Parameters:**
* **variant** – Kernel to use (e.g. `KernelVariant.AlignmentGlobal`, `KernelVariant.AlignmentLocal`, `KernelVariant.TanimotoBit`, *etc*).
* **data** – The dataset — a list of items matching the kernel type (e.g. `list[str]` or
`list[np.ndarray]`), or any other Python iterable of such items (e.g. a generator).
* **proximity_threshold, ef_construction, m, m_max, m_max0, m_l, ef_init, extend_candidates,** – keep_pruned_connections, keep_all_edges, cache_capacity, cache_shards,
n_threads, shuffle, use_heuristic, strict_ef,
threshold_based_neighbourhood: See `HNSWConfig` for descriptions.
Properties:
: config (HNSWConfig): The config in use.
index (HNSWIndex): A snapshot of the current graph structure.
Example:
```default
from relag import HNSWState, KernelVariant
seqs = ["MKTAYIAK", "MKTAYIAKQR", "ACDEFGHIKLM", "MKTAYIAKQRQIS"]
state = HNSWState(KernelVariant.AlignmentGlobal, seqs, proximity_threshold=0.3, ef_construction=64)
state.build()
results = state.search(["MKTAYIAK"], k=2)
# results[0] -> [(0, 1.0), (1, 0.88)]
store = state.edges() # EdgeStore for graph-based splitting
state.save("index.hnsw")
state2 = HNSWState.load(KernelVariant.AlignmentGlobal, "index.hnsw", seqs)
```
#### build(progress: bool = True) → None
Build the HNSW index by inserting all data items.
Must be called before `search` or `edges`. Calling `build` a second
time raises a `RuntimeError`; construct a new `HNSWState` instead.
* **Parameters:**
**progress** – Display a progress bar. Defaults to `True`.
* **Raises:**
**RuntimeError** – If the index has already been built.
#### config
Config used to initiate this object.
#### edges() → Optional[[EdgeStore](#relag.core.EdgeStore)]
Extract the proximity graph as an `EdgeStore`.
Returns all edges found during `build`. All distance measurements below the
`proximity_threshold`.
* **Returns:**
An `EdgeStore` with `node_count = dataset_size`, or `None` if the index was built
with `keep_all_edges=False`.
#### extend_build(data: Any, progress: bool = True) → None
Extend an already-built index with more data: appended to the dataset, then
inserted into the graph. Existing nodes and edges are untouched.
`data` accepts any Python iterable, same as the constructor (see `HNSWState`’s
class docstring) – drained one item at a time so a generator never needs to be
fully materialized into a Python-side list first.
* **Parameters:**
* **data** – The new items to add (same type as the original dataset).
* **progress** – Display a progress bar. Defaults to `True`.
* **Raises:**
**RuntimeError** – If `build` has not been called yet.
Example:
```default
from relag import HNSWState, KernelVariant
seqs = ["MKTAYIAK", "MKTAYIAKQR", "ACDEFGHIKLM"]
state = HNSWState(KernelVariant.AlignmentGlobal, seqs, proximity_threshold=0.3)
state.build()
more_seqs = ["MKTAYIAKQRQIS", "ACDEFGHIKLMNP"]
state.extend_build(more_seqs)
state.index.dataset_size # 5
results = state.search(["MKTAYIAKQRQIS"], k=2)
# results[0] -> [(3, 0.0), (1, 0.23)] -- found itself among the new items
```
#### get_layer(layer_idx: int = 0, directed: bool = False, weights: bool = True, progress: bool = True) → [EdgeStore](#relag.core.EdgeStore)
Return the adjacency lists for a specific HNSW layer.
* **Parameters:**
**layer_idx** – Zero-based layer index (0 = base layer with most nodes).
* **Returns:**
A list of length `dataset_size` where element `i` is the list of
neighbour IDs of node `i` at this layer. Node IDs are their index in the original dataset.
* **Raises:**
**IndexError** – If `layer_idx` is out of range.
Edges of one HNSW layer, as an `EdgeStore`.
* **Parameters:**
* **layer_idx** – Zero-based layer index (0 = base layer with most nodes).
* **directed** – If `True`, every edge is returned exactly as internally
recorded – an undirected connection contributes one entry per
endpoint, i.e. `(x, y)` is distinct from `(y, x)`. If
`False` (default), edges are canonicalized and deduplicated,
so each undirected pair appears exactly once.
* **weights** – If `True` (default), each edge’s weight is its real
distance, computed on the fly since not stored in the hierarchical graph.
If `False`, every edge gets weight
`1.0` – much cheaper when the real distance isn’t needed.
* **progress** – Display a progress bar while distances are computed.
Only meaningful when `weights=True`. Defaults to `False`.
* **Returns:**
An `EdgeStore` with `node_count = dataset_size`.
* **Raises:**
**IndexError** – If `layer_idx` is out of range.
#### index
HNSWIndex snapshot.
#### is_built
Whether `build` has been called on this index.
#### *static* load(variant: [kernels.KernelVariant](kernels.md#relag.kernels.KernelVariant), path: str, data: Any, \*args: Any, \*\*kwargs: Any) → [HNSWState](#relag.core.HNSWState)
Load an HNSWState form a binary file saved with `HNSWState.save`.
**Version compatibility:** The binary format is versioned to the exact package version.
A file saved with a different package version (older or newer) will fail to load with
a version mismatch error. There is no forward or backward compatibility guarantee during
the unstable pre-0.1.0 phase.
`data` accepts any Python iterable, same as the constructor (see `HNSWState`’s
class docstring) — including a generator re-reading a cache file.
* **Parameters:**
* **variant** – Must match the kernel used during the original build.
* **path** – Path to the saved file.
* **data** – The original dataset (required to re-attach the kernel).
* **Returns:**
The restored `HNSWState`, ready to call `search` or `edges` if the index was built.
* **Raises:**
**RuntimeError** – If the file cannot be read, the format is invalid, or the saved
version does not match the running package version.
#### save(path: str) → None
Serialize the full state (index + config) to a binary file.
The saved file can be loaded back with `HNSWState.load`. The original
data must be provided again at load time (it is not embedded in the file).
**Version compatibility:** The binary format is versioned to the exact package version.
A file saved with a different package version (older or newer) will fail to load with
a version mismatch error. There is no forward or backward compatibility guarantee during
the unstable pre-0.1.0 phase.
* **Parameters:**
**path** – Destination file path.
* **Raises:**
**RuntimeError** – On serialisation or I/O failure.
#### search(queries: Any, k: int = 1, ef: int = 64, threads: int = 0, progress: bool = True) → list[list[tuple[int, float]]]
Search the index for approximate nearest neighbours.
For each query item, returns the `k` most similar items in the dataset,
sorted by descending similarity. The quality of the approximation is
controlled by `ef`: larger values explore more candidates and improve
recall at the cost of speed.
* **Parameters:**
* **queries** – List of query items (same type as the indexed data).
* **k** – Number of nearest neighbours to return per query. Defaults to `1`.
* **ef** – Size of the dynamic candidate list during search. Must be ≥ `k`.
Defaults to `64`.
* **threads** – Number of parallel threads. `0` = all available cores.
* **progress** – Display a progress bar. Defaults to `True`.
* **Returns:**
A list of length `len(queries)`. Each element is a sorted list of
up to `k` tuples `(dataset_index, similarity_score)`.
* **Raises:**
**RuntimeError** – If `build` has not been called yet.
#### variant *: [KernelVariant](kernels.md#relag.kernels.KernelVariant)*
#### data *: Any*
#### args *: Any*
#### proximity_threshold *: float*
#### ef_construction *: int*
#### m *: int*
#### m_max *: int*
#### m_max0 *: int*
#### m_l *: float*
#### ef_init *: int*
#### extend_candidates *: bool*
#### keep_pruned_connections *: bool*
#### keep_all_edges *: bool*
#### cache_capacity *: int*
#### cache_shards *: int*
#### n_threads *: int*
#### shuffle *: bool*
#### use_heuristic *: bool*
#### strict_ef *: bool*
#### threshold_based_neighbourhood *: bool*
#### kwargs *: Any*
#### return *: [HNSWState](#relag.core.HNSWState)*
### *class* relag.core.HNSWIndex
Bases: `object`
Read-only snapshot of the HNSW graph structure after a build.
Obtained via `HNSWState.index`. Primarily useful for inspection,
serialisation, and debugging; normal users interact with `HNSWState`
instead.
Properties:
: dataset_size (int): Number of items indexed.
layers (list): Nested multi-layer adjacency list `layers[layer][node] = [neighbor_ids]`.
entry_point (tuple | None): `(layer, node_id)` of the global entry point, or `None` if empty.
max_layers (int): Number of layers in the hierarchy.
proximity_edges (list): List of `((src, dst), score)` for proximity-threshold edges.
config (HNSWConfig): The config used to build this index.
#### config
#### dataset_size
#### entry_point
#### layers
Nested multi-layer adjacency list, layers[layer][node] = [neighbor_ids]. Layers
above 0 may be stored sparsely internally (most nodes aren’t present at those
layers); this densifies them into the full node-id-indexed shape on access.
#### *static* load(path: str) → [HNSWIndex](#relag.core.HNSWIndex)
Load an index previously saved with `HNSWIndex.save`.
**Version compatibility:** The binary format is versioned to the exact package version.
A file saved with a different package version (older or newer) will fail to load with
a version mismatch error. There is no forward or backward compatibility guarantee during
the unstable pre-0.1.0 phase.
* **Parameters:**
**path** – Path to the saved file.
* **Returns:**
The deserialised `HNSWIndex`.
* **Raises:**
**RuntimeError** – If the file cannot be read, the format is invalid, or the saved
version does not match the running package version.
#### max_layers
#### proximity_edges
#### save(path: str) → None
Save the object to a binary representation (e.g. *.hnsw* file).
The binary format is versioned to the exact package version and is not
forward or backward compatible. A file saved with version X can only be
loaded by the exact same version X. Files saved with a different version
will fail to load with a version mismatch error.
* **Parameters:**
**path** – Destination file path (recommended with a .hnsw extension)
* **Raises:**
**RuntimeError** – On serialisation or I/O failure.
## Graph algorithms
### *class* relag.core.INWeightType
Bases: `object`
How to convert a raw `EdgeStore` weight into the similarity-like edge weight
used internally by `CsrGraph` and the Leiden algorithm.
- `Similarity` — the raw weight is already a similarity; used as-is.
- `Distance` — the raw weight is a distance in `[0, ∞)`; mapped to
`1 / (1 + w)` so closer nodes get a higher weight.
- `SimilarityComplement` — the raw weight is `1 - similarity`; mapped
back to a similarity via `1 - w`.
- `Unweighted` — the raw weight is ignored; every edge weight is set to `1.0`.
#### Distance *= INWeightType.Distance*
#### Similarity *= INWeightType.Similarity*
#### SimilarityComplement *= INWeightType.SimilarityComplement*
#### Unweighted *= INWeightType.Unweighted*
### *class* relag.core.LeidenObjective
Bases: `object`
Objective function used by the Leiden community-detection algorithm.
- `Modularity` — Maximise Newman-Girvan modularity. Good default for most graphs.
- `CPM` — Constant Potts Model. Finds communities of a fixed internal density.
#### CPM *= LeidenObjective.CPM*
#### Modularity *= LeidenObjective.Modularity*
### relag.core.find_communities(graph: [CsrGraph](#relag.core.CsrGraph), gamma: float = 1.0, beta: float = 0.01, n_iterations: int = 2, objective: [LeidenObjective](#relag.core.LeidenObjective) = LeidenObjective.Modularity) → list[int]
Detect communities in a graph with the Leiden algorithm.
The Leiden algorithm is an improvement over Louvain that guarantees
well-connected communities. Use the returned cluster labels with
`partition` to produce train/test splits that consider community boundaries.
* **Parameters:**
* **graph** – The graph to partition into communities.
* **gamma** – Resolution parameter. Higher values produce more, smaller communities.
For `Modularity`, typical range is 0.5–2.0; default `1.0`.
For `CPM`, it represents the minimum internal edge density.
* **beta** – Randomness parameter controlling the refinement phase.
Smaller values yield more deterministic results. Default `0.01`.
* **n_iterations** – Number of optimisation passes. More iterations improve
quality at the cost of runtime.
* **objective** – `LeidenObjective.Modularity` (default) or `LeidenObjective.CPM`.
* **Returns:**
A list of length `graph.n` where element `i` is the cluster ID of node `i`.
Cluster IDs are arbitrary non-negative integers.
Example:
```default
from relag.core import CsrGraph, EdgeStore, find_communities, LeidenObjective, INWeightType
store = EdgeStore(4, [(0,1,0.9),(1,2,0.8),(2,3,0.6)])
g = CsrGraph(store, inweight_type=INWeightType.Similarity)
clusters = find_communities(g, gamma=1.0, n_iterations=20) # e.g. [0, 1, 2, 3]
```
### relag.core.find_components(graph: [CsrGraph](#relag.core.CsrGraph)) → list[int]
Finds the connected components of a graph.
Runs a standard BFS/union-find over the graph and assigns each node to
its component.
* **Parameters:**
**graph** – The graph to find components.
* **Returns:**
A list of length `graph.n` where element `i` is the component ID of
node `i`. IDs are arbitrary non-negative integers.
Example:
```default
from relag.core import EdgeStore, find_components
store = EdgeStore(6, [(0,1,0.9),(1,2,0.8),(3,4,0.7),(4,5,0.6)])
g = store.graph()
clusters = find_components(g) # [0, 0, 0, 1, 1, 1]
```
### relag.core.partition(clusters: Sequence[int], graph: [CsrGraph](#relag.core.CsrGraph), test_ratio: float = 0.2, seed: Optional[int] = None, post_filtering: bool = False) → tuple[list[int], list[int]]
Split a clustered dataset into train and test index sets along clusters
Clusters are kept intact — no cluster is split across train and test.
The function greedily and randomly assigns whole clusters to the test set until the test size
is larger than `test_ratio`, then assigns the rest to train.
This is the key function for leakage-free dataset splitting: run
`find_communities`, `find_components`, or your own clustering, then call `partition`
to obtain indices you can use to slice your data arrays.
* **Parameters:**
* **clusters** – A list of cluster IDs, one per data point (as returned by
`find_communities` or `connected_components`). Length must
equal the number of nodes in `graph`.
* **graph** – The graph used to derive community structure. Its node count
must match `len(clusters)`.
* **test_ratio** – Target fraction of data points to place in the test set.
The actual fraction may differ slightly because clusters
are indivisible. Defaults to `0.2`.
* **seed** – Optional integer seed for reproducible cluster shuffling.
Pass `None` for a random assignment.
* **post_filtering** – If `True`, remove train nodes that share an edge with
any test node (stricter leakage prevention at the
cost of a smaller train set). Use only when clusters are found from
communities (`find_communities`). It won’t have any effect when used with
`connected_components` as this prevents inter-cluster connections.
* **Returns:**
A tuple `(train_indices, test_indices)` of zero-based data-point indices.
* **Raises:**
**ValueError** – If `clusters` length does not match `graph.n`, or if
`test_ratio` is not in `(0, 1)`.
Example:
```default
from relag.core import (
EdgeStore, CsrGraph, find_communities, partition, INWeightType
)
store = EdgeStore(6, [(0,1,0.9),(1,2,0.8),(3,4,0.7),(4,5,0.6)])
g = CsrGraph(store, inweight_type=INWeightType.Similarity)
clusters = find_communities(g) # or connected_components(g)
train_idx, test_idx = partition(clusters, g, test_ratio=0.3, seed=42)
```
## Exact search
### relag.core.exact_edges(variant: [kernels.KernelVariant](kernels.md#relag.kernels.KernelVariant), data: Any, proximity_threshold: float = 0.5, n_threads: int = 0, progress: bool = True, \*args: Any, \*\*kwargs: Any) → [EdgeStore](#relag.core.EdgeStore)
Compute all pairs of data points whose similarity exceeds a threshold (exact, brute-force).
Evaluates every unordered pair `(i, j)` with `i < j` and records an edge
when `kernel(data[i], data[j]) <= proximity_threshold`. This is O(n²) in the number
of data points; prefer the approximate\`\`HNSWState\`\` for large datasets.
Extra positional and keyword arguments are forwarded to the kernel constructor.
* **Parameters:**
* **variant** – Which kernel to use (`KernelVariant.AlignmentGlobal` or
`KernelVariant.AlignmentLocal`).
* **data** – Sequence of data items (e.g. `list[str]` for protein sequences).
* **proximity_threshold** – Maximum distance for an edge to be recorded.
* **n_threads** – Number of parallel threads. `0` uses all available cores.
* **progress** – Show a progress bar. Defaults to `True`.
* **Returns:**
An `EdgeStore` containing all edges whose distance ≤ `proximity_threshold`.
Example:
```default
from relag import KernelVariant, exact_edges
seqs = ["MKTAYIAK", "MKTAYIAKQR", "ACDEFGHIKLM"]
store = exact_edges(KernelVariant.AlignmentGlobal, seqs, proximity_threshold=0.5)
print(len(store)) # number of similar pairs
```
### relag.core.exact_nearest_neighbors(variant: [kernels.KernelVariant](kernels.md#relag.kernels.KernelVariant), queries: Any, references: Any, k: int, threads: int = 0, progress: bool = True, \*args: Any, \*\*kwargs: Any) → list[list[tuple[int, float]]]
Find the k nearest neighbors of each query in a reference set (exact, brute-force).
For every query item, scores it against every reference item using the
chosen kernel and returns the top-k references sorted by ascending
distance. Complexity is O(n_queries × n_references); prefer
`HNSWState.search` for approximate nearest neighbors with large datasets.
Extra positional and keyword arguments are forwarded to the kernel constructor.
* **Parameters:**
* **variant** – Which kernel to use (`KernelVariant.AlignmentGlobal` or
`KernelVariant.AlignmentsLocal`).
* **queries** – Sequence of query items.
* **references** – Sequence of reference items to search over. List of items of the same type of the selected kernel variant.
* **k** – Number of nearest neighbors to return per query. List of items of the same type of the selected kernel variant.
* **threads** – Number of parallel threads. `0` uses all available cores.
* **progress** – Show a progress bar. Defaults to `True`.
* **Returns:**
A list of length `len(queries)`. Each element is a list of up to `k`
tuples `(reference_index, similarity_score)` sorted by ascending distance.
Example:
```default
from relag import KernelVariant, exact_nearest_neighbors
queries = ["MKTAYIAK"]
refs = ["MKTAYIAKQR", "ACDEFGHIKLM", "MKTAYIAKQRQ"]
results = exact_nearest_neighbors(
KernelVariant.AlignmentGlobal, queries, refs, k=2
)
# results[0] -> [(0, 0.20), (2, 0.27)]
```
# kernels.html.md
# relag.kernels
## Kernel variant selector
### *class* relag.kernels.KernelVariant
Bases: `object`
#### AlignmentGlobal *= KernelVariant.AlignmentGlobal*
#### AlignmentLocal *= KernelVariant.AlignmentLocal*
#### Cosine *= KernelVariant.Cosine*
#### L1 *= KernelVariant.L1*
#### L2 *= KernelVariant.L2*
#### ProtSpam *= KernelVariant.ProtSpam*
#### Structure *= KernelVariant.Structure*
#### TanimotoBit *= KernelVariant.TanimotoBit*
#### TanimotoReal *= KernelVariant.TanimotoReal*
### relag.kernels.zip_kernel(variant: [KernelVariant](#relag.kernels.KernelVariant), data1: Any, data2: Any, n_threads: int = 0, progress: bool = True, \*args: Any, \*\*kwargs: Any) → list[float]
Compute pairwise kernel scores between two equal-length sequences of data items in parallel.
Evaluates `kernel(data1[i], data2[i])` for each index `i` and returns the scores
as a flat `list[float]` of length `len(data1)`.
Extra positional and keyword arguments are forwarded to the kernel constructor.
* **Parameters:**
* **variant** – Which kernel to use.
* **data1** – First sequence of items.
* **data2** – Second sequence of items. Must have the same length as `data1`.
* **n_threads** – Number of parallel threads. `0` uses all available cores.
* **progress** – Show a progress bar. Defaults to `True`.
* **Returns:**
A `list[float]` of length `len(data1)` where `result[i] = kernel(data1[i], data2[i])`.
* **Raises:**
**ValueError** – If `data1` and `data2` have different lengths.
Example:
```default
from relag import KernelVariant
from relag.kernels import zip_kernel
seqs1 = ["MKTAYIAK", "ACDEFGHIKLM"]
seqs2 = ["MKTAYIAKQR", "ACDEF"]
scores = zip_kernel(KernelVariant.AlignmentGlobal, seqs1, seqs2)
# scores[i] == kernel(seqs1[i], seqs2[i])
```
## relag.kernels.alignments
Protein or Nucleotide sequence aligners and their configuration enums.
### *class* relag.kernels.alignments.GlobalAligner(gap_open: int = 11, gap_extend: int = 1, matrix: [ScoringMatrix](#relag.kernels.alignments.ScoringMatrix) = ScoringMatrix.Blosum62, identity_mode: [GlobalIdentityMode](#relag.kernels.alignments.GlobalIdentityMode) = GlobalIdentityMode.MaxLength, vectorization: [VectorizationStrategy](#relag.kernels.alignments.VectorizationStrategy) = VectorizationStrategy.Scan, width: [DatatypeWidth](#relag.kernels.alignments.DatatypeWidth) = DatatypeWidth.Sat)
Bases: `object`
Needleman–Wunsch global sequence aligner returning a normalised identity score.
Wraps the parasail SIMD alignment library. The identity score is computed as
the number of identical aligned positions divided by the denominator selected
by `identity_mode`.
`GlobalAligner` is used as a kernel in `HNSWState`, `exact_edges`, and
`exact_nearest_neighbors` via `KernelVariant.AlignmentGlobal`.
* **Parameters:**
* **gap_open** – Affine gap-open penalty (positive integer, subtracted). Default `11`.
* **gap_extend** – Affine gap-extend penalty (positive integer, subtracted). Default `1`.
* **matrix** – Amino-acid substitution matrix. Default `ScoringMatrix.Blosum62`.
* **identity_mode** – Normalisation denominator. Default `GlobalIdentityMode.MaxLength`.
* **vectorization** – SIMD layout. Default `VectorizationStrategy.Scan`.
* **width** – Integer precision. Default `DatatypeWidth.Sat`.
Example:
```default
from relag.kernels.alignments import GlobalAligner
aligner = GlobalAligner(gap_open=11, gap_extend=1)
score = aligner.call("MKTAYIAK", "MKTAYIAKQR")
score = aligner("MKTAYIAK", "MKTAYIAKQR") # Alternative
# score in [0.0, 1.0]
```
#### call(ref_sample: str, query: str) → float
Compute the global alignment identity between two sequences.
* **Parameters:**
* **ref_sample** – Reference sequence (single-letter amino acid codes or nucleotides).
* **query** – Query sequence.
* **Returns:**
Identity score in `[0.0, 1.0]`.
### *class* relag.kernels.alignments.LocalAligner(gap_open: int = 11, gap_extend: int = 1, min_coverage: float = 0.800000011920929, cov_mode: [CoverageMode](#relag.kernels.alignments.CoverageMode) = CoverageMode.BothQueryTarget, matrix: [ScoringMatrix](#relag.kernels.alignments.ScoringMatrix) = ScoringMatrix.Blosum62, identity_mode: [LocalIdentityMode](#relag.kernels.alignments.LocalIdentityMode) = LocalIdentityMode.AlignmentLength, vectorization: [VectorizationStrategy](#relag.kernels.alignments.VectorizationStrategy) = VectorizationStrategy.Striped, width: [DatatypeWidth](#relag.kernels.alignments.DatatypeWidth) = DatatypeWidth.Sat)
Bases: `object`
Smith–Waterman local sequence aligner returning a normalised identity score.
Like `GlobalAligner` but aligns only the most similar sub-region of each
sequence. Pairs that do not meet the `min_coverage` criterion after alignment
receive a score of `0.0`.
`LocalAligner` is used as a kernel via `KernelVariant.AlignmentLocal`.
* **Parameters:**
* **gap_open** – Affine gap-open penalty. Default `11`.
* **gap_extend** – Affine gap-extend penalty. Default `1`.
* **min_coverage** – Minimum fraction of sequence covered by the local alignment
(per `cov_mode`) for the pair to be accepted. Default `0.8`.
* **cov_mode** – Which sequence(s) must meet `min_coverage`. Default
`CoverageMode.BothQueryTarget`.
* **matrix** – Substitution matrix. Default `ScoringMatrix.Blosum62`.
* **identity_mode** – Normalisation denominator. Default `LocalIdentityMode.AlignmentLength`.
* **vectorization** – SIMD layout. Default `VectorizationStrategy.Striped`.
* **width** – Integer precision. Default `DatatypeWidth.Sat`.
Example:
```default
from relag.kernels.alignments import LocalAligner, CoverageMode
aligner = LocalAligner(min_coverage=0.5, cov_mode=CoverageMode.Query)
score = aligner.call("ACDEFGHIKLM", "CDEFGHI")
score = aligner("ACDEFGHIKLM", "CDEFGHI") # Alternative
```
#### call(ref_sample: str, query: str) → float
Compute the local alignment identity between two sequences.
* **Parameters:**
* **ref_sample** – Reference alignments sequence (single-letter amino acid codes).
* **query** – Query alignments sequence.
* **Returns:**
Identity score in `[0.0, 1.0]`, or `1.0` if the coverage filter fails.
### *class* relag.kernels.alignments.ScoringMatrix
Bases: `object`
#### Blosum100 *= ScoringMatrix.Blosum100*
#### Blosum30 *= ScoringMatrix.Blosum30*
#### Blosum35 *= ScoringMatrix.Blosum35*
#### Blosum40 *= ScoringMatrix.Blosum40*
#### Blosum45 *= ScoringMatrix.Blosum45*
#### Blosum50 *= ScoringMatrix.Blosum50*
#### Blosum55 *= ScoringMatrix.Blosum55*
#### Blosum60 *= ScoringMatrix.Blosum60*
#### Blosum62 *= ScoringMatrix.Blosum62*
#### Blosum65 *= ScoringMatrix.Blosum65*
#### Blosum70 *= ScoringMatrix.Blosum70*
#### Blosum75 *= ScoringMatrix.Blosum75*
#### Blosum80 *= ScoringMatrix.Blosum80*
#### Blosum85 *= ScoringMatrix.Blosum85*
#### Blosum90 *= ScoringMatrix.Blosum90*
#### Blosum95 *= ScoringMatrix.Blosum95*
#### Dnafull *= ScoringMatrix.Dnafull*
#### Identity *= ScoringMatrix.Identity*
#### Nuc44 *= ScoringMatrix.Nuc44*
#### Pam10 *= ScoringMatrix.Pam10*
#### Pam100 *= ScoringMatrix.Pam100*
#### Pam110 *= ScoringMatrix.Pam110*
#### Pam120 *= ScoringMatrix.Pam120*
#### Pam130 *= ScoringMatrix.Pam130*
#### Pam140 *= ScoringMatrix.Pam140*
#### Pam150 *= ScoringMatrix.Pam150*
#### Pam160 *= ScoringMatrix.Pam160*
#### Pam170 *= ScoringMatrix.Pam170*
#### Pam180 *= ScoringMatrix.Pam180*
#### Pam190 *= ScoringMatrix.Pam190*
#### Pam20 *= ScoringMatrix.Pam20*
#### Pam200 *= ScoringMatrix.Pam200*
#### Pam210 *= ScoringMatrix.Pam210*
#### Pam220 *= ScoringMatrix.Pam220*
#### Pam230 *= ScoringMatrix.Pam230*
#### Pam240 *= ScoringMatrix.Pam240*
#### Pam250 *= ScoringMatrix.Pam250*
#### Pam260 *= ScoringMatrix.Pam260*
#### Pam270 *= ScoringMatrix.Pam270*
#### Pam280 *= ScoringMatrix.Pam280*
#### Pam290 *= ScoringMatrix.Pam290*
#### Pam30 *= ScoringMatrix.Pam30*
#### Pam300 *= ScoringMatrix.Pam300*
#### Pam310 *= ScoringMatrix.Pam310*
#### Pam320 *= ScoringMatrix.Pam320*
#### Pam330 *= ScoringMatrix.Pam330*
#### Pam340 *= ScoringMatrix.Pam340*
#### Pam350 *= ScoringMatrix.Pam350*
#### Pam360 *= ScoringMatrix.Pam360*
#### Pam370 *= ScoringMatrix.Pam370*
#### Pam380 *= ScoringMatrix.Pam380*
#### Pam390 *= ScoringMatrix.Pam390*
#### Pam40 *= ScoringMatrix.Pam40*
#### Pam400 *= ScoringMatrix.Pam400*
#### Pam410 *= ScoringMatrix.Pam410*
#### Pam420 *= ScoringMatrix.Pam420*
#### Pam430 *= ScoringMatrix.Pam430*
#### Pam440 *= ScoringMatrix.Pam440*
#### Pam450 *= ScoringMatrix.Pam450*
#### Pam460 *= ScoringMatrix.Pam460*
#### Pam470 *= ScoringMatrix.Pam470*
#### Pam480 *= ScoringMatrix.Pam480*
#### Pam490 *= ScoringMatrix.Pam490*
#### Pam50 *= ScoringMatrix.Pam50*
#### Pam500 *= ScoringMatrix.Pam500*
#### Pam60 *= ScoringMatrix.Pam60*
#### Pam70 *= ScoringMatrix.Pam70*
#### Pam80 *= ScoringMatrix.Pam80*
#### Pam90 *= ScoringMatrix.Pam90*
### *class* relag.kernels.alignments.GlobalIdentityMode
Bases: `object`
Denominator used to normalise a global-alignments identity score.
After counting identical aligned positions the raw count is divided by:
- `AlignmentLength`: the total length of the alignment (including gaps).
- `MaxSeqLength`: the length of the longer of the two sequences.
- `MinSeqLength`: the length of the shorter of the two sequences.
- `MaxLength` (default): same as `MaxSeqLength` — recommended for RGP datasets.
#### AlignmentLength *= GlobalIdentityMode.AlignmentLength*
#### MaxLength *= GlobalIdentityMode.MaxLength*
#### MaxSeqLength *= GlobalIdentityMode.MaxSeqLength*
#### MinSeqLength *= GlobalIdentityMode.MinSeqLength*
### *class* relag.kernels.alignments.VectorizationStrategy
Bases: `object`
SIMD vectorization layout used by the parasail alignment engine.
- `Striped` (default for local): interleaved layout, best for short sequences.
- `Scan`: sequential scan layout, often faster for long sequences or global alignment.
- `Diag`: diagonal layout; niche use-case, rarely needed.
In practice the default per-aligner is a good choice; change only if profiling
shows a bottleneck.
#### Diag *= VectorizationStrategy.Diag*
#### Scan *= VectorizationStrategy.Scan*
#### Striped *= VectorizationStrategy.Striped*
### *class* relag.kernels.alignments.DatatypeWidth
Bases: `object`
Integer precision used for alignment score accumulation.
- `Short` (8-bit), `Half` (16-bit), `Full` (32-bit), `Long` (64-bit):
fixed-width integers — lower width is faster but can overflow on long sequences.
- `Sat` (default): 8-bit saturating arithmetic; If it saturates, silently restart with 16-bit.
#### Full *= DatatypeWidth.Full*
#### Half *= DatatypeWidth.Half*
#### Long *= DatatypeWidth.Long*
#### Sat *= DatatypeWidth.Sat*
#### Short *= DatatypeWidth.Short*
### *class* relag.kernels.alignments.CoverageMode
Bases: `object`
Coverage filter applied before accepting a local alignment as valid.
A pair is scored only when the alignment covers enough of the sequences
as specified by the mode and `min_coverage` threshold:
- `BothQueryTarget` (default): both query and target must meet `min_coverage`.
- `Target`: only the target must meet `min_coverage`.
- `Query`: only the query must meet `min_coverage`.
- `LengthRatio`: the shorter / longer length ratio must meet `min_coverage`.
- `ShorterSeq`: coverage computed relative to the shorter sequence.
#### BothQueryTarget *= CoverageMode.BothQueryTarget*
#### LengthRatio *= CoverageMode.LengthRatio*
#### Query *= CoverageMode.Query*
#### ShorterSeq *= CoverageMode.ShorterSeq*
#### Target *= CoverageMode.Target*
### *class* relag.kernels.alignments.LocalIdentityMode
Bases: `object`
Denominator used to normalise a local-alignment identity score.
- `AlignmentLength` (default): divide by the length of the local alignment.
- `MinSeqLength`: divide by the shorter sequence length.
#### AlignmentLength *= LocalIdentityMode.AlignmentLength*
#### MinSeqLength *= LocalIdentityMode.MinSeqLength*
## relag.kernels.molecules
Molecules Tanimoto kernels.
### *class* relag.kernels.molecules.TanimotoBit
Bases: `object`
Tanimoto distance kernel for binary (bit) molecular fingerprints.
Measures structural dissimilarity between two molecules as the complement of
the Jaccard index over their feature sets. A distance of 0 means the two
fingerprints are identical; 1 means they share no features at all.
Formula: `1 - |A ∩ B| / |A ∪ B|`
This kernel is the standard choice when working with `ExplicitBitVect`
fingerprints such as Morgan, RDKit, or MACCS keys.
Example:
```default
from rdkit.Chem import MolFromSmiles, rdFingerprintGenerator
from relag.utils import BitFingerprint
from relag.kernels.molecules import TanimotoBit
mfpgen = rdFingerprintGenerator.GetMorganGenerator(fpSize=1024, radius=2)
benzene = BitFingerprint(mfpgen.GetFingerprint(MolFromSmiles("c1ccccc1")))
naphthalene = BitFingerprint(mfpgen.GetFingerprint(MolFromSmiles("c1ccc2ccccc2c1")))
acetic_acid = BitFingerprint(mfpgen.GetFingerprint(MolFromSmiles("CC(=O)O")))
k = TanimotoBit()
print(k(benzene, naphthalene)) # low — structurally similar
print(k(benzene, acetic_acid)) # high — structurally dissimilar
```
#### call(a: [utils.BitFingerprint](utils.md#relag.utils.BitFingerprint), b: [utils.BitFingerprint](utils.md#relag.utils.BitFingerprint)) → float
Compute the Tanimoto distance between two `BitFingerprint` objects.
* **Parameters:**
* **a** – First fingerprint.
* **b** – Second fingerprint.
* **Returns:**
Distance in `[0.0, 1.0]`. `0.0` means identical feature sets,
`1.0` means fully disjoint.
Example:
```default
from relag.utils import BitFingerprint
from relag.kernels.molecules import TanimotoBit
fp1 = BitFingerprint.from_list([True, False, True, True])
fp2 = BitFingerprint.from_list([True, True, True, False])
k = TanimotoBit()
assert k.call(fp1, fp2) == k(fp1, fp2) # both forms are equivalent
# intersection={0,2}=2, union={0,1,2,3}=4 → distance = 0.5
```
### *class* relag.kernels.molecules.TanimotoReal
Bases: `object`
Tanimoto distance kernel for real-valued (count) molecular fingerprints.
Generalises the binary Tanimoto to continuous feature vectors using the
dot-product formulation. A distance of 0 means the two fingerprints are
proportional; values approach 1 as the vectors become orthogonal.
Formula: `1 - dot(a, b) / (||a||² + ||b||² - dot(a, b))`
This kernel is the standard choice when working with count fingerprints
such as those returned by `GetCountFingerprint` (Morgan counts, etc.).
Example:
```default
from rdkit.Chem import MolFromSmiles, rdFingerprintGenerator
from relag.utils import RealFingerprint
from relag.kernels.molecules import TanimotoReal
mfpgen = rdFingerprintGenerator.GetMorganGenerator(fpSize=1024, radius=2)
benzene = RealFingerprint(mfpgen.GetCountFingerprint(MolFromSmiles("c1ccccc1")))
naphthalene = RealFingerprint(mfpgen.GetCountFingerprint(MolFromSmiles("c1ccc2ccccc2c1")))
acetic_acid = RealFingerprint(mfpgen.GetCountFingerprint(MolFromSmiles("CC(=O)O")))
k = TanimotoReal()
print(k(benzene, naphthalene)) # low — structurally similar
print(k(benzene, acetic_acid)) # high — structurally dissimilar
```
#### call(a: [utils.RealFingerprint](utils.md#relag.utils.RealFingerprint), b: [utils.RealFingerprint](utils.md#relag.utils.RealFingerprint)) → float
Compute the Tanimoto distance between two `RealFingerprint` objects.
* **Parameters:**
* **a** – First fingerprint.
* **b** – Second fingerprint.
* **Returns:**
Distance in `[0.0, 1.0]`. `0.0` means identical (proportional)
feature vectors, `1.0` means fully orthogonal.
Example:
```default
from relag.utils import RealFingerprint
from relag.kernels.molecules import TanimotoReal
fp1 = RealFingerprint.from_list([1.0, 0.0, 1.0])
fp2 = RealFingerprint.from_list([0.0, 1.0, 1.0])
k = TanimotoReal()
assert k.call(fp1, fp2) == k(fp1, fp2) # both forms are equivalent
# dot=1, ||fp1||²=2, ||fp2||²=2 → distance = 1 - 1/(2+2-1) ≈ 0.667
```
## relag.kernels.structures
Protein structure kernels (USalign TM-score).
### *class* relag.kernels.structures.USAlignKernel(norm_mode: [NormMode](#relag.kernels.structures.NormMode) = NormMode.Min, fast: bool = True)
Bases: `object`
Protein structure comparison kernel using USalign TM-score.
Operates on pre-loaded `PdbStructure` objects (from `relag.utils`).
Returns `1.0 - TM-score` so that identical structures have distance 0
and unrelated structures have distance approaching 1.
Example:
```default
from relag.utils import PdbStructure
from relag.kernels.structures import USAlignKernel, NormMode
s1 = PdbStructure("1abc.pdb")
s2 = PdbStructure("1xyz.pdb")
k = USAlignKernel() # default: NormMode.Min
d = k(s1, s2) # float in [0, 1]
```
#### call(a: [utils.PdbStructure](utils.md#relag.utils.PdbStructure), b: [utils.PdbStructure](utils.md#relag.utils.PdbStructure)) → float
Compute the structure distance between two `PdbStructure` objects.
* **Parameters:**
* **a** – First structure.
* **b** – Second structure.
* **Returns:**
Distance in `[0.0, 1.0]`. `0.0` means structurally identical,
values near `1.0` indicate unrelated folds.
### *class* relag.kernels.structures.NormMode
Bases: `object`
TM-score normalization strategy.
USalign returns two scores per alignment:
- `TM1`: normalized by the length of the first structure (query).
- `TM2`: normalized by the length of the second structure (target).
- `Min` (default): `min(TM1, TM2)` — conservative, symmetric.
- `Query`: use `TM1` only.
- `Target`: use `TM2` only.
#### Min *= NormMode.Min*
#### Query *= NormMode.Query*
#### Target *= NormMode.Target*
## relag.kernels.protspam
ProtSpaM-style spaced-word kernel for protein sequences.
### *class* relag.kernels.protspam.ProtSpamKernel(patterns: [utils.SWPatternSet](utils.md#relag.utils.SWPatternSet), significance_threshold: int = 0, distance: [ProtSpamDistance](#relag.kernels.protspam.ProtSpamDistance) = ProtSpamDistance.Evolutionary)
Bases: `object`
Distance between two `SWSequence`, ProtSpaM-style: for each pattern in a
shared `SWPatternSet`, match spaced words by key (grouping ties into “blocks”),
score the best-aligned spaced word pair within each matching block against
BLOSUM62, and pool mismatches at don’t-care positions into a mismatch rate.
Reports either that raw rate or a Kimura-corrected evolutionary distance, per
`distance`.
Every `SWSequence` passed to `call` must have been built from the same
`SWPatternSet` as this kernel’s `patterns()` (or an equal copy of it).
Example:
```default
from relag.kernels.protspam import ProtSpamKernel, ProtSpamDistance
from relag.utils import SWPatternSet, SWSequence
sequences = [
"MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEK",
"MKTAYIAKQRQISFVKSHFSRQLEERLGLIEVQAPILSRVGDGTQDNLSGAEA",
]
# A RasBhari-optimized pattern set, shared by every SWSequence and the kernel.
patterns = SWPatternSet(5, 6, 20)
swseqs = [SWSequence(s, patterns) for s in sequences]
kernel = ProtSpamKernel(patterns, distance=ProtSpamDistance.Evolutionary)
distance = kernel.call(swseqs[0], swseqs[1])
distance = kernel(swseqs[0], swseqs[1]) # equivalent -- kernel(a, b) syntax
assert distance >= 0.0
```
#### call(ref_sample: [utils.SWSequence](utils.md#relag.utils.SWSequence), query: [utils.SWSequence](utils.md#relag.utils.SWSequence)) → float
Compute the distance between `ref_sample` and `query`, per this kernel’s
`distance()` mode.
* **Parameters:**
* **ref_sample** – Reference sequence. Must have been built from the same
`SWPatternSet` as this kernel’s `patterns()`.
* **query** – Query sequence. Same requirement.
* **Returns:**
The distance – see `ProtSpamDistance`.
Panics:
: `ref_sample`/`query` must have been built from the same
`SWPatternSet` as this kernel’s `patterns()`. If they weren’t, this
can panic (index out of range, if the mismatched set has fewer patterns)
or – more insidiously – silently compare the wrong patterns’ spaced
words against each other with no panic at all, if the mismatched set
merely has a different pattern at the same index.
#### distance() → [ProtSpamDistance](#relag.kernels.protspam.ProtSpamDistance)
Which value `call` reports.
#### patterns() → [utils.SWPatternSet](utils.md#relag.utils.SWPatternSet)
The shared pattern set spaced words are matched against.
#### significance_threshold() → int
Minimum BLOSUM62 score for a spaced-word match to be considered homologous.
### *class* relag.kernels.protspam.ProtSpamDistance
Bases: `object`
Which distance `ProtSpamKernel.call` reports.
- `MismatchRate`: the raw mismatch rate itself (fraction of don’t-care
positions that mismatch, pooled across every pattern’s accepted spaced-word
matches) – always in `[0, 1]`. `1.0` if no spaced word matched at all, or
if every accepted match’s don’t-care positions mismatched outright.
- `Evolutionary`: the mismatch rate corrected into a distance via the Kimura
two-parameter formula – in `[0, inf]`. `inf` if the mismatch rate is
undefined (no match at all) or high enough (`> ~0.8541`) that the
correction’s log argument is non-positive.
#### Evolutionary *= ProtSpamDistance.Evolutionary*
#### MismatchRate *= ProtSpamDistance.MismatchRate*
## relag.kernels.vectors
Vector distance kernels.
### *class* relag.kernels.vectors.Cosine
Bases: `object`
Cosine distance: `1 - dot(a, b) / (||a|| * ||b||)`. `0.0` if either vector
is the zero vector.
Example:
```default
import numpy as np
from relag.kernels.vectors import Cosine
a = np.array([1.0, 0.0], dtype=np.float32)
b = np.array([0.0, 1.0], dtype=np.float32)
k = Cosine()
assert k.call(a, b) == k(a, b) == 1.0
```
#### call(a: numpy.typing.NDArray[numpy.float32], b: numpy.typing.NDArray[numpy.float32]) → float
Compute the cosine distance between two 1D `float32` numpy arrays.
Reads directly from each array’s buffer – no copy.
* **Parameters:**
* **a** – First vector.
* **b** – Second vector.
* **Returns:**
Distance in `[0.0, 2.0]`. `0.0` means the same direction, `2.0`
means exactly opposite directions.
### *class* relag.kernels.vectors.L1
Bases: `object`
Manhattan (L1) distance: `sum(|a_i - b_i|)`.
Example:
```default
import numpy as np
from relag.kernels.vectors import L1
a = np.array([1.0, 2.0, 3.0], dtype=np.float32)
b = np.array([4.0, 0.0, 3.0], dtype=np.float32)
k = L1()
assert k.call(a, b) == k(a, b) == 5.0
```
#### call(a: numpy.typing.NDArray[numpy.float32], b: numpy.typing.NDArray[numpy.float32]) → float
Compute the L1 distance between two 1D `float32` numpy arrays.
Reads directly from each array’s buffer – no copy.
* **Parameters:**
* **a** – First vector.
* **b** – Second vector.
* **Returns:**
Distance in `[0.0, inf]`. `0.0` means identical vectors.
### *class* relag.kernels.vectors.L2
Bases: `object`
Euclidean (L2) distance: `sqrt(sum((a_i - b_i)²))`.
Example:
```default
import numpy as np
from relag.kernels.vectors import L2
a = np.array([0.0, 0.0], dtype=np.float32)
b = np.array([3.0, 4.0], dtype=np.float32)
k = L2()
assert k.call(a, b) == k(a, b) == 5.0
```
#### call(a: numpy.typing.NDArray[numpy.float32], b: numpy.typing.NDArray[numpy.float32]) → float
Compute the L2 distance between two 1D `float32` numpy arrays.
Reads directly from each array’s buffer – no copy.
* **Parameters:**
* **a** – First vector.
* **b** – Second vector.
* **Returns:**
Distance in `[0.0, inf]`. `0.0` means identical vectors.
# utils.html.md
# relag.utils
### *class* relag.utils.BitFingerprint(fp: ExplicitBitVect)
Bases: `object`
Dense binary fingerprint backed by a dense bitset.
Each bit represents the presence or absence of a structural feature.
The primary source is an RDKit `ExplicitBitVect` (from `GetFingerprint`),
but plain Python lists and numpy arrays are also accepted.
`count` caches the popcount so Tanimoto computation avoids re-counting.
Example:
```default
from rdkit.Chem import rdFingerprintGenerator, MolFromSmiles
from relag.utils import BitFingerprint
mfpgen = rdFingerprintGenerator.GetMorganGenerator(fpSize=1024, radius=2)
mol = MolFromSmiles("c1ccccc1")
fp = BitFingerprint(mfpgen.GetFingerprint(mol))
print(fp.count(), len(fp)) # set bits, total bits
```
#### count() → int
Number of on bits (popcount).
#### *static* from_list(values: Sequence[bool]) → [BitFingerprint](#relag.utils.BitFingerprint)
Construct from a list of booleans (or 0/1 ints).
#### *static* from_np(arr: numpy.typing.NDArray[numpy.bool_]) → [BitFingerprint](#relag.utils.BitFingerprint)
Construct from a numpy boolean or uint8 array.
For uint8/bool dtypes, reads the array’s raw buffer directly – a zero-copy
view into memory numpy already owns – and sets bits from it in one pass, with no
per-element Python object boxing. Any other dtype falls back to .tolist() +
from_list, which does box each element as an individual Python object along the
way; this covers arbitrary array-likes at the cost of that boxing.
#### *static* random(len: int, count: int) → [BitFingerprint](#relag.utils.BitFingerprint)
Create a fingerprint of `len` bits with exactly `count` bits set at random.
Useful for randomized testing.
* **Parameters:**
* **len** – Total number of bits.
* **count** – Number of bits to turn on. Must be `<= len`.
* **Raises:**
**ValueError** – If `count > len`.
#### to_list() → list[bool]
Export as a list of booleans.
#### to_np() → numpy.typing.NDArray[numpy.bool_]
Export as a numpy bool array.
#### to_rdkit() → ExplicitBitVect
Export as an RDKit `ExplicitBitVect`.
#### fp *: ExplicitBitVect*
#### return *: [BitFingerprint](#relag.utils.BitFingerprint)*
### *class* relag.utils.RealFingerprint(fp: UIntSparseIntVect)
Bases: `object`
Dense real-valued fingerprint backed by a `Vec`.
Each element represents a feature count or continuous value. The primary
source is an RDKit `UIntSparseIntVect` (from `GetCountFingerprint`),
but plain Python lists and numpy arrays are also accepted.
`norm_sq` caches `||x||²` so Tanimoto computation avoids recomputing it.
Example:
```default
from rdkit.Chem import rdFingerprintGenerator, MolFromSmiles
from relag.utils import RealFingerprint
from relag.kernels.molecules import TanimotoReal
mfpgen = rdFingerprintGenerator.GetMorganGenerator(fpSize=1024, radius=2)
mol = MolFromSmiles("c1ccccc1")
fp = RealFingerprint(mfpgen.GetCountFingerprint(mol))
print(fp.norm_sq(), len(fp))
```
#### *static* from_list(values: Sequence[float]) → [RealFingerprint](#relag.utils.RealFingerprint)
Construct from a list of floats.
#### *static* from_np(arr: numpy.typing.NDArray[numpy.float32]) → [RealFingerprint](#relag.utils.RealFingerprint)
Construct from a numpy float32 or float64 array.
#### norm_sq() → float
Squared Euclidean norm of the feature vector (`||x||²`).
#### to_list() → list[float]
Export as a list of floats.
#### to_np() → numpy.typing.NDArray[numpy.float32]
Export as a numpy float32 array.
#### to_rdkit() → UIntSparseIntVect
Export as an RDKit `UIntSparseIntVect`, compatible with `GetCountFingerprint`.
Only non-zero elements are stored; the length equals `len(self)`.
#### fp *: UIntSparseIntVect*
#### return *: [RealFingerprint](#relag.utils.RealFingerprint)*
### *class* relag.utils.Vector(values: Any)
Bases: `object`
A dense `f32` vector, used by the `relag.kernels.vectors` kernels (`Cosine`,
`EluDot`, `L1`, `L2`). Backed by a plain `Vec` – unlike
`RealFingerprint`, there’s no precomputed cache.
Example:
```default
import numpy as np
from relag.utils import Vector
v = Vector(np.array([1.0, 2.0, 3.0], dtype=np.float32))
print(len(v))
```
#### *static* from_list(values: Sequence[float]) → [Vector](#relag.utils.Vector)
Construct from a list of floats.
#### *static* from_np(arr: Any) → [Vector](#relag.utils.Vector)
Construct from a numpy float32 array, reading its buffer directly. Falls back
to `.tolist()` for other dtypes.
#### to_list() → list[float]
Export as a list of floats.
#### to_np() → numpy.typing.NDArray[numpy.float32]
Export as a numpy float32 array.
#### values *: Any*
#### return *: [Vector](#relag.utils.Vector)*
### *class* relag.utils.PdbStructure(path: str)
Bases: `object`
A protein structure loaded from a PDB file and held in memory.
The structure is parsed once on construction and the coordinate arrays are
kept alive for the lifetime of the object. Cloning is cheap (reference-counted).
Example:
```default
from relag.utils import PdbStructure
s = PdbStructure("1abc.pdb")
```
#### path *: str*
#### return *: [PdbStructure](#relag.utils.PdbStructure)*
### relag.utils.read_fasta(path: str) → list[tuple[str, str]]
Parse a FASTA file and return all records as a list of `(header, sequence)` pairs.
The header string is the full description line without the leading `>`.
The sequence is the concatenation of all continuation lines for that record,
with whitespace stripped.
* **Parameters:**
**path** – Path to the FASTA file.
* **Returns:**
A list of `(header, sequence)` tuples, one per FASTA record.
* **Raises:**
**IOError** – If the file cannot be opened or is not valid UTF-8.
Example:
```default
from relag.utils import read_fasta
records = read_fasta("proteins.fasta")
header, seq = records[0]
print(header) # "sp|P12345|MYPR_HUMAN ..."
print(seq) # "MKTAYIAKQRQISFVKSHFSRQ..."
```
### relag.utils.largest_cluster(clusters: Sequence[int]) → tuple[int, int]
Return the ID and size of the largest cluster.
Convenience helper — iterates over a cluster-label vector and finds the
most populous label.
* **Parameters:**
**clusters** – A list of cluster IDs (e.g. from `connected_components` or
`find_communities`).
* **Returns:**
A tuple `(cluster_id, size)` for the largest cluster.
### *class* relag.utils.SWPattern(weight: int, dont_care: int)
Bases: `object`
A spaced-word pattern: a binary mask over length positions where a match position
(“1”) contributes a residue to the spaced word’s key and a don’t-care position (“0”)
is skipped when hashing but still compared for mismatches. Position 0 and the last
position are always match positions.
Example:
```default
from relag.utils import SWPattern
pat = SWPattern(6, 20)
assert len(pat) == 26
assert pat.weight() == 6
assert pat.dontcare() == 20
assert pat.is_match(0)
assert pat.is_match(25)
# Parse/render as a string of '1's (match) and '0's (don't-care)
pat2 = SWPattern.parse("10100101")
assert pat2.weight() == 4
assert str(pat2) == "10100101"
```
#### dontcare() → int
Number of don’t-care (“0”) positions.
#### is_match(pos: int) → bool
Whether `pos` is a match position. Never raises: an out-of-range `pos`
simply reads as `False`.
#### match_positions() → list[int]
Ascending indices of match (“1”) positions.
#### *static* parse(s: str) → [SWPattern](#relag.utils.SWPattern)
Parse a pattern from a string of `'1'``s (match) and ``'0'``s (don't-care),
the same format ``str()` produces.
* **Raises:**
**ValueError** – If the string contains a character other than `'0'`/`'1'`,
or doesn’t start and end with `'1'`.
#### weight() → int
Number of match (“1”) positions.
#### dont_care *: int*
#### return *: [SWPattern](#relag.utils.SWPattern)*
### *class* relag.utils.SWPatternSet(n: int, weight: int, dont_care: int)
Bases: `object`
A set of
```
``
```
SWPattern\`\`s used together to compute spaced words for a sequence.
The default constructor builds a RasBhari-optimized set (recommended for real
use); use `SWPatternSet.random` for a cheap, unoptimized set, or
`SWPatternSet.from_patterns` to build one from hand-picked patterns.
Example:
```default
from relag.utils import SWPatternSet
# RasBhari-optimized (recommended)
patterns = SWPatternSet(5, 6, 20)
assert len(patterns) == 5
# Cheap, unoptimized baseline, refined by hand
random_patterns = SWPatternSet.random(5, 6, 20)
score_before = random_patterns.optimize(0) # limit=0: score only, no changes
score_after = random_patterns.optimize(2000)
assert score_after <= score_before
```
#### *static* from_patterns(patterns: Sequence[[SWPattern](#relag.utils.SWPattern)]) → [SWPatternSet](#relag.utils.SWPatternSet)
Build a set from already-constructed patterns. Unlike `random`, there’s no uniqueness check – duplicate
patterns are allowed, though they add nothing (two identical patterns always
find exactly the same matches).
#### *static* load(path: str) → [SWPatternSet](#relag.utils.SWPatternSet)
Deserialize a set previously written by `save`.
* **Raises:**
**IOError** – If `path` can’t be read, or its contents aren’t a valid pattern set.
#### optimize(limit: int) → float
Optimize this pattern set in place with RasBhari’s overlap-complexity hill
climbing: repeatedly picks a pattern round-robin, swaps one of its interior
match positions for a don’t-care position, and keeps the change only if it
strictly lowers the set’s total pairwise overlap-complexity score.
* **Parameters:**
**limit** – Number of hill-climbing steps to run. Pass `0` to just compute
and return the current score without changing anything.
* **Returns:**
The achieved overlap-complexity score after optimizing (lower is better).
#### patterns() → list[[SWPattern](#relag.utils.SWPattern)]
The patterns in this set, in construction order.
#### *static* random(n: int, weight: int, dont_care: int) → [SWPatternSet](#relag.utils.SWPatternSet)
Build `n` distinct unoptimized random patterns of the given
`weight`/`dont_care`. Useful as a cheap baseline, or as the unoptimized
starting point `optimize` refines.
#### WARNING
Patterns are generated by rejection sampling on uniqueness, so this hangs
(never returns) if `n` isn’t well below the number of distinct patterns
possible for `weight`/`dont_care` (`C(weight + dont_care - 2, weight - 2)`).
This is sharpest at `weight == 2`: there are no interior positions to
vary at all, so every generated pattern is identical and any `n > 1`
hangs immediately.
#### save(path: str) → None
Serialize this set to `path`. Inverse of `load`.
* **Raises:**
**IOError** – If `path` can’t be written.
#### *static* with_limit(n: int, weight: int, dont_care: int, limit: int) → [SWPatternSet](#relag.utils.SWPatternSet)
Same as the default constructor, but with an explicit hill-climbing step
budget instead of ProtSpaM’s default of 25,000.
#### n *: int*
#### weight *: int*
#### dont_care *: int*
#### return *: [SWPatternSet](#relag.utils.SWPatternSet)*
### *class* relag.utils.SWSequence(seq: str, patterns: [SWPatternSet](#relag.utils.SWPatternSet))
Bases: `object`
A sequence prepared for spaced-word comparison (ProtSpaM-style). Its residues are
pre-encoded as 5-bit amino-acid codes, and it holds the sorted spaced words for
every pattern in the `SWPatternSet` it was built with.
Two `SWSequence``s must be built from the same ``SWPatternSet` (or two equal
copies of it) to be compared meaningfully – see
`relag.kernels.protspam.ProtSpamKernel`, which is what actually compares two of
these.
Picklable: `pickle.dumps`/`pickle.loads` round-trip an `SWSequence` without
needing the original pattern set again (its encoded residues and sorted spaced
words are serialized directly).
Example:
```default
import pickle
from relag.utils import SWPatternSet, SWSequence
patterns = SWPatternSet(5, 6, 20)
seq = SWSequence("MKTAYIAKQRQISFVKSHFSRQ", patterns)
assert len(seq) == 22
restored = pickle.loads(pickle.dumps(seq))
assert restored.seq() == seq.seq()
```
#### pattern_count() → int
Number of patterns this sequence has spaced words for – the `len()` of the
`SWPatternSet` it was built with. Valid indices for `sorted_words` are
`0 .. pattern_count()`.
#### seq() → list[int]
Residues as 5-bit amino-acid codes, not raw ASCII – e.g. `'A'` reads back
as `0`, not `65`.
#### sorted_words(pattern_idx: int) → list[[SWWord](#relag.utils.SWWord)]
Sorted spaced words for `patterns.patterns()[pattern_idx]`, where
`patterns` is the set this sequence was built with. Empty if this sequence
is shorter than that pattern (no window fits).
* **Raises:**
**IndexError** – If `pattern_idx` is out of range for the pattern set this
sequence was built with.
#### patterns *: [SWPatternSet](#relag.utils.SWPatternSet)*
#### return *: [SWSequence](#relag.utils.SWSequence)*
### *class* relag.utils.SWWord
Bases: `object`
One complete spaced word: the residues at a pattern’s match positions.
Example:
```default
from relag.utils import SWPatternSet, SWSequence
patterns = SWPatternSet.random(1, 2, 1) # single pattern, weight 2, dc 1 -> "101"
seq = SWSequence("ACDE", patterns)
words = seq.sorted_words(0)
assert all(words[i].key() <= words[i + 1].key() for i in range(len(words) - 1))
```
#### key() → int
The packed key: the pattern’s match-position residues, 5 bits each,
most-significant residue first.
#### pos() → int
Start position of this word’s window in the sequence it came from.