torann
TORoidal Approximate Nearest Neighbours
torann is exact + approximate k-NN and range search on the unit torus $[0,1)^d$ under toroidal L1 — a metric mainstream ANN libraries do not offer, chosen deliberately: L1 degrades more gracefully than L2/cosine in high dimensions, and on the torus the LSH guarantee is exact. Built for ESS-style epoch workloads: static anchors, a moving candidate tier, selective updates, batch promotion.
Features
- The metric is the contract: toroidal L1, exact distances everywhere — the LSH only filters candidates, never approximates a distance.
- An LSH family that is exactly L1-sensitive on the torus: randomly rotated integer grids with a closed-form, seam-free collision law (see How it works).
- ESS-shaped lifecycle: two tiers (anchors + candidates), selective
update()that re-places only points whose hash cell changed,promote()as a linear merge — never a re-sort, exact after every step. - No brute-force fallback: under-filled queries widen buckets by
prefix relaxation (contiguous sorted-key ranges), so
kresults are structurally guaranteed. - Self-tuning:
fit(..., k=...)orradius=...derives the hash parameters (B, K, L) from the workload; explicit arguments always win. - Three interchangeable implementations of one interface
(
torann/base.py): exact NumPy brute force, a pure-Python LSH reference, and a Rust core (PyO3 + rayon) that produces byte-identical hash tables at 60–120× the speed. Without the compiled module — or on a CPU below the AVX2 floor — the package still runs, on the reference implementation. - No
unsafe: the SIMD kernel iswide, a safe stable-Rust wrapper. Hand-writtencore::archintrinsics were measured at 21% faster and declined; that trade is deliberate and stays open.
Installation
From PyPI
pip install torann
From source
The project is a maturin mixed Rust/Python package — a Rust toolchain is required to build the native core:
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install .
Requirements: Python ≥ 3.12, numpy.
Rust ≥ 1.98 is required to build the native core: the distance kernel uses the algebraic floating-point methods stabilized in that release.
CPU: published x86-64 wheels are built with an AVX2 + FMA floor — Intel Haswell (2013) and AMD Excavator (2015) onward. That is not gratuitous: the distance kernel is a plain scalar loop that the compiler auto-vectorizes, and without AVX it has no 256-bit register to lower to, costing 17% (340 ms → 408 ms on the reference shape). AVX-512 is not used: it is a further 4% at d = 32 but nearly 2× worse at d = 8. On a CPU without AVX2 the compiled backend is not loaded and the pure-Python implementation is used instead, with a warning; installing from the sdist builds a native module for whatever the machine has. arm64 needs no floor — NEON is baseline there, and the kernel names no vector type, so it follows the target rather than pinning a width.
Quick Start
import numpy as np
from torann import ToroidalNN
d = 16
static = np.random.rand(15_000, d) # anchors: never move
batch = np.random.rand(3_000, d) # candidates: move each epoch
nn = ToroidalNN(seed=42)
nn.fit(static, batch, k=2*d) # build + tune from zero
for epoch in range(32):
idx, dist = nn.query() # each candidate vs everything
new = force_step(nn.candidates, idx, dist) # your physics here
nn.update(new) # selective refresh
nn.promote(next_batch) # candidates freeze into anchors
nn.query_radius(0.25) # range query as a post-filter
nn.query(k=8, queries=Q) # arbitrary external queries
Knobs (all optional — tuning fills them in): num_tables, resolution,
dims_per_table, target_bucket_size, probes, brute_threshold,
backend ("auto" prefers the fastest installed of rust, python).
How it works
The metric
On the torus, distance wraps: per dimension it is $\min(|a_i-b_i|,, 1-|a_i-b_i|)$, and the metric is the sum. Near an edge the nearest region of a query is not where a seam-blind index looks — it wraps around every boundary it touches. The teal points are the true 12-NN of the star:
The region follows the query around the torus:
At d=16, distance concentration makes wrapping the common case: almost
every true neighbour pair wraps in at least one dimension, which is why a
seam-blind exact search misses ~74 % of the true toroidal neighbours
(examples/compare_faiss.py).
The hash
One hashed dimension, with integer resolution $B \ge 2$ and a random offset $u \sim U[0,1)$:
$$c(x) = \lfloor B,((x+u) \bmod 1) \rfloor \qquad P[c(x){=}c(y)] = \max(0,, 1 - B\delta)$$
Because $B$ is an integer, the $B$ arcs tile the circle exactly — the grid has no seam, and the collision law is exact, not approximate (dots are measured frequencies):
A table concatenates $K$ sampled dimensions into a base-$B$ key, so
collisions decay as $\prod_j \max(0, 1-B\delta_j) \approx e^{-B \cdot L1}$
— inherently an L1 guarantee, which is why L1 is the public contract
and no other metric is offered. A uniformly random pair collides per
dimension with probability exactly $1/B$ (closed-form bucket load
$n/B^K$), and a point that moves by $s$ changes its cell with probability
$B s$ — churn is proportional to movement, which is what makes selective
updates cheap. The rejected alternatives (p-stable projections cannot
wrap; integer projections alias far points onto near ones) are measured
in exploration/.
The index
Sorted key arrays make a bucket a contiguous range (an $O(1)$
direct-address offset table serves the static tier), keys are digit
concatenations so prefix relaxation — dropping low-order digits —
widens a bucket into a wider contiguous range without any distance scan,
and every gathered candidate is refined with the exact toroidal L1 before
the top-k. Full details: torann/lsh.py — the reference implementation,
normative for the L1 hash.
Benchmarks
Measured on an AMD Ryzen AI 7 PRO 350 (16 threads), d=16, k=32, on a build
carrying the AVX2 floor described under Installation —
which is what the published wheels now ship, so these are numbers you get
rather than numbers only the developer got. Regenerate the full grids,
crossover and complexity tables with python examples/benchmark.py:
These figures predate the Rust 1.98 distance kernel and are now conservative. That rewrite is worth 8–50% on the query path depending on dimensionality, and d = 16 — the dimension charted here — is near the top of that range at ~27%. The plots are regenerated by the command above; the numbers below have not been re-measured since.
On the workload this library exists for — the ESS main loop, simulated
end to end (examples/ess_sim.py) — torann is 1.7× faster than FAISS
Flat rebuilt per epoch and correct, where FAISS's exact seam-blind L1
delivers 0.25–0.28 recall against the true toroidal neighbours:
Queries run 11–148 µs at 16 threads across n ∈ [20k, 1M] on torus data — 60–120× the NumPy pipeline — with 0.9–2.1 ms selective updates and ~1 s builds at n = 1M (HNSW: 24–32 s). On wrap-free data (FAISS's best case) torann sits within 1.06–1.56× of FAISS's exact SIMD Flat scan at equal ≈ 1.0 recall. Python and Rust implementations produce byte-identical hash tables, so their recall is identical by construction.
Running Unit Tests
The conformance suite runs once per installed backend and checks byte-identical tables plus equivalent query results across the whole lifecycle, using the standard unittest framework:
python -m unittest discover -s test
Documentation
The library uses Google-style docstrings; the API documentation is generated with pdoc by a GitHub Action and published here. To preview locally:
pip install pdoc
pdoc --math -d google torann torann.wrapper torann.base torann.brute torann.lsh torann.rust
Development
The checks in CI also run at commit time:
pip install pre-commit
pre-commit install
That gates each commit on ruff, basedpyright, vulture, the unit tests, and --
because the native core is held to the same standard -- cargo fmt --check
and cargo clippy --release -- -D warnings. pre-commit run --all-files
checks the tree without committing.
python examples/ess_sim.py # the ESS main loop end to end, vs FAISS
python examples/bench_backends.py # per-op grid over (n, d, backend)
python examples/crossover.py # brute vs LSH crossover n*(d, backend)
python examples/compare_faiss_flat.py # non-toroidal throughput vs FAISS
python examples/figures.py # regenerate the README method figures
python examples/plot_benchmarks.py # regenerate the benchmark figures
python exploration/exp_1d.py # regenerate the concept experiments
maturin build --release produces the complete wheel (Cargo.toml +
src/lib.rs are the native core; torann/ is the Python package). The C
contender from the phase-6 bake-off is preserved at tag
archive/backend-c.
Authors
- Mário Antunes - mariolpantunes
License
This project is licensed under the MIT License - see the LICENSE file for details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file torann-0.3.0.tar.gz.
File metadata
- Download URL: torann-0.3.0.tar.gz
- Upload date:
- Size: 2.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f64929ed71632e59258bd9683d3dac5bf01c934621e50f2096f6e192719ba2ae
|
|
| MD5 |
d4249815957a684d88131c5f75a92c04
|
|
| BLAKE2b-256 |
2c387fe46b9a4f8de870c94ee4957c544f4f09facf4d0af6dc774b3299eaac60
|
File details
Details for the file torann-0.3.0-cp314-cp314-win_amd64.whl.
File metadata
- Download URL: torann-0.3.0-cp314-cp314-win_amd64.whl
- Upload date:
- Size: 267.7 kB
- Tags: CPython 3.14, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4afe65dfb98e78a7e3a840c1c52583958c46c68557e6afb34db815476e096ab
|
|
| MD5 |
19965ce8d6d326ca4ba5dc964dcc66dc
|
|
| BLAKE2b-256 |
ac8de8ab4329a67d366716bdfe14c1e09ef4daa1663b4e0d80251b1194cab220
|
File details
Details for the file torann-0.3.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: torann-0.3.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 390.4 kB
- Tags: CPython 3.14, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54eea8a931f37e7749bf7e3307746ddbde2a20044b88ba221f8b5d48702721d8
|
|
| MD5 |
003789dde58c56006305c05a1cb85814
|
|
| BLAKE2b-256 |
155887c80fa3a6cdbad87263fd34a8384690ec42906585c7da3785f50725af3b
|
File details
Details for the file torann-0.3.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: torann-0.3.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 382.7 kB
- Tags: CPython 3.14, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c4c0710faf53407cea88e3c79a43c7290fd6c92e9221e084473fd215c33e449a
|
|
| MD5 |
e048ef47cdd6ccf11e5f57fb3ded540d
|
|
| BLAKE2b-256 |
9174b38edc9713f6b3f5a2bd9f73f24c609ca2c6c1771419f9d5994e631cbafd
|
File details
Details for the file torann-0.3.0-cp314-cp314-macosx_11_0_arm64.whl.
File metadata
- Download URL: torann-0.3.0-cp314-cp314-macosx_11_0_arm64.whl
- Upload date:
- Size: 347.8 kB
- Tags: CPython 3.14, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d89aec9a9a7034d09ff2a41b8601aa613caab896f61e3e2fa5ab793479af155
|
|
| MD5 |
de61e9cd804a9108baaf646a5774af01
|
|
| BLAKE2b-256 |
3b33563c8f539587f47ae2f65e5dc5cc013f0d0c0ecdd960a9d3b44e85c1169d
|
File details
Details for the file torann-0.3.0-cp314-cp314-macosx_10_12_x86_64.whl.
File metadata
- Download URL: torann-0.3.0-cp314-cp314-macosx_10_12_x86_64.whl
- Upload date:
- Size: 359.9 kB
- Tags: CPython 3.14, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
489f186705fcf23328e054933ee846188147b271cb8180e97a6520d09c71f0e5
|
|
| MD5 |
f8d4dc51f259b4295540b6ef3f380a1b
|
|
| BLAKE2b-256 |
acbd9641c0ef4ee9d79a388ce1d508f2b9bdc98535f2b6db929f519177a61148
|
File details
Details for the file torann-0.3.0-cp313-cp313-win_amd64.whl.
File metadata
- Download URL: torann-0.3.0-cp313-cp313-win_amd64.whl
- Upload date:
- Size: 268.0 kB
- Tags: CPython 3.13, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d97639aa4e207ffa19536991893f778cfd1cc741ae468dcb8bf6d8d5770fb544
|
|
| MD5 |
a8bb26856fbf48648029974b97d372e7
|
|
| BLAKE2b-256 |
217895ab48ade322c9d38c8fb99af47702a6fbe46126e54055b0310c65930aca
|
File details
Details for the file torann-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: torann-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 390.2 kB
- Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4276c43e03f485d0d5695bc6018fffe4f8d52fedf2d541ed78e4593009560e16
|
|
| MD5 |
12132764b1e42428f73a4f7c435bcb8b
|
|
| BLAKE2b-256 |
45cb7a7fd4eaf1d1eb1154427a6b22d584b576dfb3582f4ce640677e67b9e986
|
File details
Details for the file torann-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: torann-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 382.3 kB
- Tags: CPython 3.13, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec5d6315d67269ab33b654a67831ab022eb81353b7deb44866feab1d437beb04
|
|
| MD5 |
f70519edf5271d0b34a0c931ed9a4833
|
|
| BLAKE2b-256 |
cc740956e98461bbb7dbc212f8760ac879112956cb03b7257e02208de86fe806
|
File details
Details for the file torann-0.3.0-cp313-cp313-macosx_11_0_arm64.whl.
File metadata
- Download URL: torann-0.3.0-cp313-cp313-macosx_11_0_arm64.whl
- Upload date:
- Size: 347.8 kB
- Tags: CPython 3.13, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
755467e7a10d39d792c667a60a288e29a08767b39eb7b20dcf0ac569948f2464
|
|
| MD5 |
45e51f3136c880d4dd2cfb442b9f6313
|
|
| BLAKE2b-256 |
ee9ec071dd5a363d51343f6ebda346ef018851f7499d7dff5e3a01d30153f30e
|
File details
Details for the file torann-0.3.0-cp313-cp313-macosx_10_12_x86_64.whl.
File metadata
- Download URL: torann-0.3.0-cp313-cp313-macosx_10_12_x86_64.whl
- Upload date:
- Size: 359.8 kB
- Tags: CPython 3.13, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d8d7746ed863685cc16952cb252523932d6bbab7c88f59fb73ed26246a033b2
|
|
| MD5 |
04a8922776a48ab1cd38fa9126668466
|
|
| BLAKE2b-256 |
0efd9c7f891204fe0c03a418accfc6dc978b28dcca3d1ea2f15492dc1adc7da5
|
File details
Details for the file torann-0.3.0-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: torann-0.3.0-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 267.7 kB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dae4c16613e664564dd6273eff7d73f7e1ef050bfe9884130a590af413800a80
|
|
| MD5 |
7792ba24b1e016251047f5b91892b626
|
|
| BLAKE2b-256 |
dbf95c7c65c4f4a5bd6e9d2473c2236d489294ecbe05b6c2661cbef6c890c6fa
|
File details
Details for the file torann-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: torann-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 389.9 kB
- Tags: CPython 3.12, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
374c515ae899f564c075c0aad2d30dc4cac59b09a885901e2c3b9567439a7363
|
|
| MD5 |
e53e23b41af177b46021c75247ebf665
|
|
| BLAKE2b-256 |
52db4e918c7427db5db5a3d731d7ca3506a75aefebceeacc380bccf4548ea9e5
|
File details
Details for the file torann-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: torann-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 382.0 kB
- Tags: CPython 3.12, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4dda78189fd4892a072751ac5279ab2e0afc3e35766a8d89421311ffadfe7a2
|
|
| MD5 |
0928676fa1d3871cd334a068209be98f
|
|
| BLAKE2b-256 |
6d80f95e73f6a58bcf9187103ae12d2d7100ddc881e278a3b730fe32d36db675
|
File details
Details for the file torann-0.3.0-cp312-cp312-macosx_11_0_arm64.whl.
File metadata
- Download URL: torann-0.3.0-cp312-cp312-macosx_11_0_arm64.whl
- Upload date:
- Size: 347.6 kB
- Tags: CPython 3.12, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34fd8c6d16b565d566f078f5f9fe3b6f68068e8e973588ce0d19cf144ec8a647
|
|
| MD5 |
9a1034d08ffcfec5226096f6b849cf36
|
|
| BLAKE2b-256 |
c79824ad2b28d72666d1972414397753f2b6ca2fb29d929d4a22115a8377205d
|
File details
Details for the file torann-0.3.0-cp312-cp312-macosx_10_12_x86_64.whl.
File metadata
- Download URL: torann-0.3.0-cp312-cp312-macosx_10_12_x86_64.whl
- Upload date:
- Size: 359.6 kB
- Tags: CPython 3.12, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8bbbe388cb12312b6a3b14a008f105021a97ad4707dc25df2ccdf47e4cffc30a
|
|
| MD5 |
53bb7fa1738feeb1d914d22d45efc8b8
|
|
| BLAKE2b-256 |
580e7ee76f07c60fe10962873308b54384b7a0b93bdf633705053de25c646c32
|