ann_router.backends.exact module
Exact brute-force backend — the always-available reference implementation.
Pure numpy: no optional dependency, so ExactIndex is the one backend that
is always usable and doubles as the ground truth the router’s recall tests
score every approximate engine against. For a corpus under a few tens of
thousands of vectors a vectorised full scan is already sub-millisecond and,
being exact, has recall 1.0 by construction — which is exactly why the policy
routes small problems here instead of paying an index-build cost for nothing.
Consumes: ann_router.base (the ANNIndex contract), numpy.
Produces: ExactIndex.
Author: Warith Harchaoui <warith.harchaoui@deraison.ai>
- class ann_router.backends.exact.ExactIndex(dim, metric='cosine', **kwargs)[source]
Bases:
ANNIndexExact k-NN by full matrix multiplication over the stored corpus.
The corpus is kept as a contiguous float32 matrix; for cosine/inner-product metrics search is a single
queries @ corpus.Tfollowed by a top-k partition, and for L2 it is the standard||q||^2 - 2 q·x + ||x||^2expansion. All operations are exact, so this class defines “truth” for the recall benchmarks.- Parameters:
Examples
>>> rng = np.random.default_rng(0) >>> vecs = rng.standard_normal((100, 16)).astype(np.float32) >>> idx = ExactIndex(dim=16).build(vecs) >>> ids, dists = idx.search(vecs[:1], k=1) >>> int(ids[0, 0]) # nearest neighbour of a point is itself 0
- add(vectors)[source]
Append vectors with the next contiguous ids.
- Parameters:
vectors (numpy.ndarray) – Shape
(m, dim).- Return type:
None
Examples
>>> idx = ExactIndex(dim=4).build(np.ones((2, 4), dtype=np.float32)) >>> idx.add(np.ones((3, 4), dtype=np.float32)) >>> idx.size 5
- add_with_ids(vectors, ids)[source]
Append vectors with explicit ids.
- Parameters:
vectors (numpy.ndarray) – Shape
(m, dim).ids (numpy.ndarray) – Shape
(m,).
- Return type:
None
Examples
>>> idx = ExactIndex(dim=4).build(np.ones((2, 4), dtype=np.float32)) >>> idx.add_with_ids(np.zeros((1, 4), dtype=np.float32), np.array([99])) >>> idx.size 3
- build(vectors, ids=None)[source]
Store the initial corpus.
- Parameters:
vectors (numpy.ndarray) – Shape
(n, dim).ids (numpy.ndarray, optional) – Shape
(n,); defaults torange(n).
- Returns:
self.- Return type:
Examples
>>> idx = ExactIndex(dim=4).build(np.ones((5, 4), dtype=np.float32)) >>> idx.search(np.ones((1, 4), dtype=np.float32), k=3)[0].shape (1, 3)
- classmethod capabilities()[source]
Return the exact backend’s capability descriptor.
- Returns:
Exact, mutable, in-memory, no GPU, no metadata filter.
- Return type:
Examples
>>> ExactIndex.capabilities().approximate False
- classmethod is_available()[source]
Return
True— numpy is a core dependency, so exact always works.- Returns:
Always
True.- Return type:
Examples
>>> ExactIndex.is_available() True
- load(path)[source]
Load a corpus previously written by
save().- Parameters:
path (str) – Source
.npzpath.- Returns:
self, populated from disk.- Return type:
- remove(ids)[source]
Delete vectors by id (true deletion — the rows are dropped).
- Parameters:
ids (numpy.ndarray) – Ids to remove.
- Return type:
None
Examples
>>> idx = ExactIndex(dim=4).build(np.ones((3, 4), dtype=np.float32)) >>> idx.remove(np.array([1])) >>> idx.size 2
- save(path)[source]
Persist the corpus and ids to a
.npzfile.- Parameters:
path (str) – Destination path (
.npzappended by numpy if absent).- Return type:
None
Examples
>>> import tempfile, os >>> idx = ExactIndex(dim=4).build(np.ones((3, 4), dtype=np.float32)) >>> p = os.path.join(tempfile.mkdtemp(), "exact.npz") >>> idx.save(p); os.path.exists(p) or os.path.exists(p + ".npz") True
- search(queries, k)[source]
Return exact top-
kneighbours per query.- Parameters:
queries (numpy.ndarray) – Shape
(q, dim).k (int) – Neighbours per query.
- Returns:
ids (numpy.ndarray) – Shape
(q, k)neighbour ids (-1where fewer thankexist).distances (numpy.ndarray) – Shape
(q, k)distances (similarity for cosine/ip, euclidean^2 for l2).
- Return type:
tuple[ndarray, ndarray]
Examples
>>> rng = np.random.default_rng(1) >>> vecs = rng.standard_normal((50, 8)).astype(np.float32) >>> ids, _ = ExactIndex(dim=8).build(vecs).search(vecs[:3], k=5) >>> ids.shape (3, 5)