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 arenafound, fetched = usearch.Arena(), usearch.Arena() # two live results, two arenasfor 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 callarenas = [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.
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 10matches.distances # shape (queries, columns), format 'd'matches.counts # shape (queries,)matches[0] # a tuple of `Match` struct sequences, one per hit for query zeronp.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.