Python · Inputs and Outputs

Inputs and Outputs

import usearchfrom usearch import Arena, Match, Matches, … 3.0.0-rc.1 · 4c3e5023

§ Arenas

Every output whose length only the call knows, such as matches, rows, products, monoliths and cursor or stream batches, lands in a usearch.Arena you pass, and the verb answers a view over it without copying. usearch.Arena(executor) places it where that executor computes, decided once, and usearch.Arena() makes host pages for the calling thread. Each call rewinds its arena, so the previous call’s views die and a steady loop allocates once. To keep a result past the next call, or to read one while making the next, pass that call another arena. An arena serves one call at a time, so each deferred call needs its own. A NumPy array or memoryview over an output pins its arena as a memoryview pins a bytearray, so the next call naming that arena raises ContendedError until the export is released. A stale output raises UninitializedError from memoryview, from indexing a Matches and from DenseProducts.query, but np.asarray swallows that error into a 0-d object array, so read through those or copy with np.array(...) while the output is fresh. An arena from a VRAM executor holds device memory, which host accessors refuse with DeviceError. It serves only calls on that executor, any other or none raising DeviceError, and holds the executor until it is freed, so an executor closed first keeps its streams until then. Its views export __cuda_array_interface__ instead, for CuPy, PyTorch or Numba, and arena.describe() tells the two apart. Every view also exports __dlpack__ wherever it lives, so torch.from_dlpack(vectors) takes it in place.

hits, arena = 0, usearch.Arena(pool)
for batch in batches:
    matches = index.on(pool).search(batch, 10, arena)  # rewinds: last batch's matches are gone
    hits += int(np.asarray(matches.counts).sum())  # an array kept past here would pin the arena
found, fetched = usearch.Arena(), usearch.Arena()  # two live results, two arenas
for key, _ in index.search(query, 10, found)[0]:
    rerank(key, index.get(key, fetched))
pools = [usearch.Executor.for_ram(threads=8) for _ in shards]  # one executor and one arena per submitted call
arenas = [usearch.Arena(own) for own in pools]
calls = [shard.deferred(own).search(queries, 10, arena) for shard, own, arena in zip(shards, pools, arenas)]
results = [call.wait() for call in calls]  # or, in a coroutine: await call

search answers a usearch.Matches, get and attend answer usearch.DenseVectors, a cursor’s batches usearch.DenseBatch, multiply and maxsim answer usearch.DenseProducts, and save_buffer answers usearch.Monolith. Their len() counts queries, entries or rows, or bytes of a monolith.

§ Rows & Scalars

Inputs arrive through DLPack first, then PEP 3118 and __array_interface__, so NumPy is never required and a PyTorch, JAX or CuPy tensor needs no round-trip through it, on the host or on its GPU. Only the row stride may vary; a strided column would force a gather the SIMD kernels have no form for. A dtype= names the scalar type a buffer cannot spell, such as Scalar.BF16 over raw bytes, and must be as wide as the buffer’s items. In get and attend, an outputs_dtype= names the scalar type the verb exports as, converted from storage.

A ranked verb answers a Matches that reads through to three outputs in the arena:

arena = usearch.Arena(pool)
matches = index.on(pool).search(queries, 10, arena)
matches.keys  # shape (queries, columns, 16), format 'B', columns at most 10
matches.distances  # shape (queries, columns),     format 'd'
matches.counts  # shape (queries,)
matches[0]  # a tuple of `Match` struct sequences, one per hit for query zero
np.asarray(matches.distances)  # zero-copy; NumPy never entered the binding

A count bounds its own row, and the cells past it hold nothing a call wrote.

The breadth of the candidates is a per-call setting rather than a property of the handle, so a recall sweep runs against one immutable index with no mutation between points. Traversal telemetry rides a DenseTraversalStats the call fully overwrites, so one reading of visited_members, computed_distances and restarts always belongs to one call and never to the sweep around it:

for expansion_factor in (16, 32, 64, 128, 256):
    stats = usearch.DenseTraversalStats()
    index.on(pool).search(queries, 10, arena, expansion_factor=expansion_factor, stats=stats)
    print(expansion_factor, stats.computed_distances)

traversal=usearch.Traversal.SCAN skips the walk and scores every entry exactly, on RAM and NVMe handles, which gives such a sweep its ground truth. += adds one reading into another when the totals across a sweep are what is wanted:

total = usearch.DenseTraversalStats()
for batch in batches:
    index.on(pool).search(batch, 10, arena, stats=stats)
    total += stats

API

Types

ScalarA stored or exchanged scalar type, widest dynamic range first.
__init__.py:187 ↗
class Scalar(IntEnum)

Values

F641IEEE-754 binary64: ±1.80e308, epsilon 2.22e-16, with infinities and NaNs.
F322IEEE-754 binary32: ±3.40e38, epsilon 1.19e-7, with infinities and NaNs.
BF163Truncated f32 with a 7-bit mantissa: f32's range and epsilon 7.81e-3.
F164IEEE-754 binary16: ±65504, epsilon 9.77e-4, with infinities and NaNs.
E5M25FP8 with a 5-bit exponent: ±57344, epsilon 0.25, with infinities and NaNs.
E4M36FP8 with a 4-bit exponent: ±448, epsilon 0.125, no infinities, two NaN codes.
E3M27FP6 with a 3-bit exponent in one byte: ±28, every one of its 64 codes a number.
E2M38FP6 with a 2-bit exponent in one byte: ±7.5, every one of its 64 codes a number.
I8108-bit signed integer spanning -128 to 127.
I4X211Two signed nibbles per byte spanning -8 to 7, even dimensions in the low nibble.
I321232-bit signed integer spanning ±2.15e9.
U8208-bit unsigned integer spanning 0 to 255.
U4X221Two unsigned nibbles per byte spanning 0 to 15, even dimensions in the low nibble.
U1X822Eight single-bit dimensions per byte, the form Hamming and Jaccard score.
TraversalHow a search reaches the entries it scores.
__init__.py:229 ↗
class Traversal(IntEnum)

Values

WALK1Walks the graph, scoring the entries the expansion reaches; approximate, the default.
SCAN2Scores every entry published when the scan starts; exact, the ground truth a walk approximates.
ClassArenaThe memory every output whose length scales with a call lands in, rewound by each call handed it.2
lib.pyi:463 ↗
class Arena
ArenaMakes one where executor computes, or host pages for the calling thread.
lib.pyi:467 ↗
Arena(executor: Executor | None = None, /)
residencyThe Residency its outputs live in.
lib.pyi:472 ↗
residency: Residency

Python-only.

ClassMatchOne scored entry: the identity found and its distance, unpacking as key, distance = match.2
lib.pyi:541 ↗
class Match(structseq[Any], tuple[Key, float])
keyThe identity of the matched entry.
lib.pyi:548 ↗
key: Key
distanceThe distance to the query, in the index's own metric.
lib.pyi:553 ↗
distance: float
ClassMatchesA ranked run search wrote into an Arena, reading through to three outputs.6
lib.pyi:557 ↗
class Matches

Exports Arrow in place as a LargeListView of Struct<key, distance>, or compacted into a LargeList when requested_schema asks one.

keys(queries, columns, 16) identities, most significant byte first.
lib.pyi:567 ↗
keys: _ArenaBuffer
distances(queries, columns) distances in the index's own metric, columns at most the call's wanted.
lib.pyi:572 ↗
distances: _ArenaBuffer
counts(queries,) match counts, each bounding its own row.
lib.pyi:577 ↗
counts: _ArenaBuffer
__arrow_c_array__Exports host rows as an Arrow schema and array capsule.
lib.pyi:117 ↗

Inherited from _ArrowOutput

def __arrow_c_array__(
    requested_schema: _Capsule | None = None,
) -> tuple[_Capsule, _Capsule]
__arrow_c_device_array__Exports rows wherever they live as an Arrow schema and device array capsule.
lib.pyi:121 ↗

Inherited from _ArrowOutput

def __arrow_c_device_array__(
    requested_schema: _Capsule | None = None,
) -> tuple[_Capsule, _Capsule]
__arrow_c_stream__Exports host rows as an Arrow stream of one chunk.
lib.pyi:125 ↗

Inherited from _ArrowOutput

def __arrow_c_stream__(
    requested_schema: _Capsule | None = None,
) -> _Capsule
ClassDenseTraversalStatsTraversal telemetry, fully overwritten by every call it is passed to; += sums a sweep.4
lib.pyi:1824 ↗
class DenseTraversalStats
__iadd__Python-only.
lib.pyi:1844 ↗
def __iadd__(other: DenseTraversalStats, /) -> Self
visited_membersEntries whose neighborhoods the walk expanded.
lib.pyi:1830 ↗
visited_members: int
computed_distancesDistance evaluations the walk paid for.
lib.pyi:1835 ↗
computed_distances: int
restartsTimes the walk fell back to a fresh entry point.
lib.pyi:1840 ↗
restarts: int