Python · Executors

Executors

import usearchfrom usearch import Executor, Deferred, On, … 3.0.0-rc.1 · 4c3e5023

An executor owns host threads or device streams; an index owns entries. Keeping them apart is what lets one pool serve several indexes, and what makes the cost of spawning visible at the line that spawns it rather than hidden in a constructor. Its residency must match that of every index it drives.

One factory per residency, since each spawns something different and takes different arguments:

pool = usearch.Executor.for_ram(threads=8)  # 0 derives from the hardware
numa_pool = usearch.Executor.for_ram(threads=8, memory_domains=[0, 1])
device_pool = usearch.Executor.for_vram(devices=[0, 1])  # One stream per CUDA ordinal
disk_pool = usearch.Executor.for_nvme(threads=8, paths=["/mnt/nvme0", "/mnt/nvme1"])
shared_pool = usearch.Executor.on_pool(fu_pool_address)  # Borrows a ForkUnion pool by address
pool.drain(usearch.Mood.SPINNING)  # Waits, then keeps idle workers hot

for_ram creates an executor of host threads, and its memory_domains are the NUMA node IDs those threads stay local to. for_nvme opens one submission ring per worker, striping shards across the paths it is given. for_vram takes no thread count at all: one host thread drives one stream per device, so the width of that pool is the device list rather than a worker count. on_pool borrows a ForkUnion fu_pool_t another library spawned, passed as an integer address the way ctypes and cffi hand pointers over, and the pool must outlive the executor. One call holds an executor at a time, and nothing queues: a second call finishes with Status.CONTENDED and raises ContendedError, a deferred one once awaited. A deferred call on a caller-exclusive pool, which for_ram and for_nvme spawn for more than one thread, runs on the pool’s thread 0, so polling status alone finishes it, and its progress and predicate callbacks run on the pool’s threads under the GIL; a single thread or a VRAM executor runs it at submission. drain waits until no call holds the executor, then lets idle workers sleep with Mood.PARKED, the default, or keeps them hot with Mood.SPINNING. Used as a context manager, an executor waits for its calls and closes on exit, after which a receiver naming it raises UninitializedError. Calling usearch.Executor() raises a TypeError naming the factories, since what a pool spawns cannot be derived from nothing.

§ Concurrency

The index and every receiver derived from it never change after they are made, so threads share them freely. Searches never write to the graph, so any number may overlap with insertions, and saving needs a mutation-free window. The extension declares Py_MOD_GIL_NOT_USED, so free-threaded builds run without the interpreter lock. Critical sections or atomics fence the few fields the binding mutates: an arena’s exports and generation, the handle a cursor or stream lends its running pull, a closed executor’s handle, a latched callback exception, a DenseTraversalStats total and an absorbed donor’s handle.

§ Receivers & Deferred Calls

A receiver carries what every C argument struct puts before its inputs: the executor and the completion. The index runs a verb on the calling thread, index.on(executor) answers a usearch.On whose verbs block on that executor, and index.deferred(executor) answers a usearch.DeferredOn whose verbs submit there and answer a usearch.Deferred. The receiver’s type alone chooses the completion, so all three offer the same verbs with the same arguments, and no verb takes an executor= keyword. In a coroutine, await a deferred call; anywhere else, wait() blocks the thread with its state detached:

import asyncio


async def nearest(pool):
    here, there = usearch.Arena(), usearch.Arena(pool)
    matches = index.search(queries, 10, here)  # the calling thread
    matches = index.on(pool).search(queries, 10, there)  # blocking, holding the pool until it returns
    call = index.deferred(pool).search(queries, 10, there)  # a usearch.Deferred, submitted now
    call.status  # usearch.Status.DEFERRED until final
    return await call  # or: call.wait()


matches = asyncio.run(nearest(pool))

await runs wait() on the running event loop’s default executor, so the thread it blocks belongs to asyncio. A Deferred holds the outcome and every input buffer the library reads, so nothing the call reads ever moves. It holds its arena as a blocking call does, until it is waited on or dropped: meanwhile another call naming that arena raises ContendedError, and its result views the arena like the blocking verb’s. Dropping an unfinished one waits for its call, which is the C contract rather than binding logic. The collector leaves a cycle through an unfinished one for a later collection, never clearing what the library still reads. Constructors address no index yet, so no receiver offers them; the ones the C ABI lets fan out take executor=, and run on the calling thread without one.

API

Types

MoodWhat an executor's idle workers do: sleep, or stay hot.
__init__.py:179 ↗
class Mood(IntEnum)

Values

PARKED1Idle workers sleep; the next call pays a wake-up.
SPINNING2Idle workers stay hot, trading power for the next call's latency.
ClassExecutorThe workers a verb runs on, shared across indexes and named by on and deferred.7
lib.pyi:395 ↗
class Executor
for_ramSpawns host worker threads for a RAM-resident index.
lib.pyi:400 ↗
@classmethod
def for_ram(
    threads: int = 0,
    *,
    memory_domains: Sequence[int] | None = None,
    deferred_limit: int = 0,
    cpu_class: CPUClass | None = None,
) -> Executor
on_poolDrives RAM-resident indexes on a thread pool the caller owns.
lib.pyi:412 ↗
@classmethod
def on_pool(
    pool: int,
    /,
    *,
    memory_domains: Sequence[int] | None = None,
    deferred_limit: int = 0,
    cpu_class: CPUClass | None = None,
) -> Executor
for_vramOpens one stream per device for a VRAM-resident index.
lib.pyi:425 ↗
@classmethod
def for_vram(
    *,
    devices: Sequence[int] | None = None,
    deferred_limit: int = 0,
    gpu_class: GPUClass | None = None,
) -> Executor
for_nvmeOpens one submission ring per worker for an NVMe-resident index.
lib.pyi:432 ↗
@classmethod
def for_nvme(
    threads: int = 0,
    *,
    paths: Sequence[_PathLike] | None = None,
    deferred_limit: int = 0,
    nvme_class: NVMeClass | None = None,
    cpu_class: CPUClass | None = None,
) -> Executor
drainWaits for every call submitted before it, then sets what idle workers do.
lib.pyi:445 ↗
def drain(mood: Mood = ...) -> None
__exit__Closes it once its calls finish, or leaves that to its last live GPU arena.
lib.pyi:455 ↗
def __exit__(
    kind: type[BaseException] | None,
    value: BaseException | None,
    traceback: TracebackType | None,
    /,
) -> None
residencyThe Residency its calls compute in.
lib.pyi:450 ↗
residency: Residency

Python-only.

ClassDeferredA call a DeferredOn submitted: await it, or wait() for it; dropping it waits.4
lib.pyi:585 ↗
class Deferred[AnswerT]
__class_getitem__Names what wait() answers, as Deferred[Matches].
lib.pyi:590 ↗
def __class_getitem__(item: Any, /) -> GenericAlias
waitBlocks until the call is final, then answers or raises.
lib.pyi:599 ↗
def wait() -> AnswerT
__await__Runs wait() on the running event loop's default executor, suspending the task meanwhile.
lib.pyi:603 ↗
def __await__() -> Generator[Any, None, AnswerT]
statusStatus.DEFERRED until the call is final, then the final one.
lib.pyi:595 ↗
status: Status
ClassOnAn index bound to the executor its verbs block on, as index.on(executor) answers it.20
lib.pyi:609 ↗
class On[IndexT]
__class_getitem__Names the index the receiver addresses, as On[DenseIndex].
lib.pyi:614 ↗
def __class_getitem__(item: Any, /) -> GenericAlias
*× 2The frozen metadata and the current counters, read in one call.
lib.pyi:618 ↗
def describe() -> DenseIndexDescription

Each counter is read once, so under concurrent insertions they may come from slightly different moments.

Examples

>>> usearch.DenseIndex.init_ram(256).describe().metadata.dimensions
256
*× 2Grows the index to host at least capacity entries, a hint, since insertions grow the index on demand.
lib.pyi:628 ↗
def reserve(capacity: int, /) -> None

Parameters

capacityintEntries the index must accept without growing again.
*× 2Drops every entry, keeping the capacity and the frozen geometry.
lib.pyi:637 ↗
def clear() -> None
*× 2Inserts a batch of vectors under a batch of keys; one vector is a one-row matrix.
lib.pyi:642 ↗
def insert(
    keys: Sequence[_KeyLike] | _Tensor,
    vectors: _Tensor,
    /,
    *,
    expansion_factor: int = 0,
    dtype: Scalar | None = None,
    progress: _Progress | None = None,
    progress_steps: int = 0,
    stats: DenseTraversalStats | None = None,
) -> None

Keys need not be unique: inserting one twice stores two entries, and count reports both. A batch is all-or-nothing and becomes visible at once, so a rejected batch leaves the index untouched and a concurrent search sees all of it or none.

Parameters

keysSequence[_KeyLike] | _TensorOne key per row, or an (N, 16) buffer.
vectors_TensorAn (N, dimensions) matrix; only the row stride may vary.
expansion_factorintBreadth of the candidates; 0 derives it.
dtypeScalar | NoneNames the input scalar type when the buffer cannot; it must be as wide as the buffer's items.
progress_Progress | NoneCalled with (completed, total) entries. It reports and cannot stop the batch, every entry being claimed before the first report; what it raises surfaces once the batch has finished. Reports are serialized.
progress_stepsintReports the call makes at most; 0 derives to ten.
statsDenseTraversalStats | NoneOverwritten with this call's telemetry.

Examples

>>> from array import array
>>> index = usearch.DenseIndex.init_ram(dimensions=4)
>>> index.reserve(1)
>>> index.insert([1], array('d', [0.1, 0.2, 0.3, 0.4]))
>>> len(index)
1
*× 2Re-selects the neighbors of every stored entry against the finished graph, improving edges that were chosen when the graph around them was still incomplete.
lib.pyi:681 ↗
def refine(
    *,
    expansion_factor: int = 0,
    progress: _Progress | None = None,
    progress_steps: int = 0,
    stats: DenseTraversalStats | None = None,
) -> RefineReport

A build step rather than a repair: a cheap insertion pass followed by one refinement reaches a better graph, in less total time, than one expensive insertion pass - so a wide expansion_factor belongs here rather than in insert. Searches may run throughout on a RAM handle but not on a VRAM one, and a caller loops until the report's changed falls below whatever change rate they consider settled.

Stopping is safe: re-selecting edges claims no identity, so a stopped sweep still re-links its islands, leaves the graph correct and merely less improved, and raises CancelledError. A handler is therefore how a sweep is put under a time budget.

Parameters

expansion_factorintBreadth of the candidates; 0 derives it.
progress_Progress | NoneCalled with (completed, total) nodes; returning False ends the sweep, None carries on.
progress_stepsintReports the call makes at most; 0 derives to ten.
statsDenseTraversalStats | NoneOverwritten with this call's telemetry.

Returns

Entries re-selected and changed, the islands found and reconnected, and the audit closing the heal; changed at zero means the graph has settled.

Examples

>>> from array import array
>>> index = usearch.DenseIndex.init_ram(dimensions=4)
>>> index.reserve(1)
>>> index.insert([1], array('d', [0.1, 0.2, 0.3, 0.4]))
>>> index.refine().changed
0
*× 2Audits how much of the graph a search can reach, sweeping forward reachability from as many landmarks as one search draws.
lib.pyi:723 ↗
def health() -> DenseReachability

Read-only.

Examples

>>> from array import array
>>> index = usearch.DenseIndex.init_ram(dimensions=4)
>>> index.reserve(1)
>>> index.insert([1], array('d', [0.1, 0.2, 0.3, 0.4]))
>>> health = index.health()
>>> health.reachable == health.audited == 1
True
*× 2How many entries share key; zero means the index holds none.
lib.pyi:788 ↗
def count(key: _KeyLike, /) -> int

Parameters

key_KeyLikeThe identity to count.

Examples

>>> from array import array
>>> index = usearch.DenseIndex.init_ram(dimensions=2)
>>> index.reserve(2)
>>> row = array('d', [0.1, 0.2])
>>> index.insert([7], row)
>>> index.insert([7], row)
>>> index.count(7)
2
*× 2Every vector stored under key, in insertion order, and none when the key is absent.
lib.pyi:806 ↗
def get(
    key: _KeyLike,
    arena: Arena,
    /,
    *,
    outputs_dtype: Scalar = ...,
) -> DenseVectors

Parameters

key_KeyLikeThe identity to retrieve.
arenaArenaWhere the rows land, rewound by the call.
outputs_dtypeScalarThe scalar type to export as; converted from storage.

Returns

A writable (count, dimensions) matrix in the arena, for the kinds memoryview can format - f64, f32, f16, i8, u8. Sub-byte and mini-float kinds, bf16 among them, leave as a (count, row_bytes) run of 'B' instead. Empty, so falsy, when the index holds no entry under key.
*× 2Approximate sparse product of a matrix against the indexed vectors - the linear algebra sibling of search, reporting every pair the traversal scored rather than only the nearest.
lib.pyi:822 ↗
def multiply(
    rows: _Tensor,
    wanted: int,
    arena: Arena,
    /,
    *,
    expansion_factor: int = 0,
    dtype: Scalar | None = None,
    progress: _Progress | None = None,
    progress_steps: int = 0,
    stats: DenseTraversalStats | None = None,
) -> DenseProducts

Pairs arrive in traversal order rather than sorted, and carry no duplicates. How many a walk scores is known only once it ends, so the pairs come back ragged.

Parameters

rows_TensorAn (R, dimensions) matrix of left-hand rows.
wantedintUpper bound on pairs kept per row, the first ones its walk scored; 0 keeps every pair.
arenaArenaWhere the pairs and their offsets land, rewound by the call.
expansion_factorintBreadth of the candidates; 0 derives it. A wider factor widens the emitted pattern, being what the walk's breadth decides.
dtypeScalar | NoneNames the left-hand scalar type when the buffer cannot.
progress_Progress | NoneCalled with (completed, total) rows; returning False stops the batch and raises CancelledError, None carries on.
progress_stepsintReports the call makes at most; 0 derives to ten.
statsDenseTraversalStats | NoneOverwritten with this call's telemetry.

Returns

offsets, (R + 1,) Apache Arrow offsets, left row i spanning pairs offsets[i] up to offsets[i + 1]; keys, a (pairs, 16) run naming each pair's indexed entry; scores, the (pairs,) float64 products; and required, the pairs scored before wanted cut any. All of them live in the arena.
*× 2Softmax attention of every query row over every indexed key its walk scores, folded online as Flash Attention folds key tiles, with no top-k cut.
lib.pyi:861 ↗
def attend(
    queries: _Tensor,
    values: _Tensor,
    row_of: Callable[[Key], int | None],
    arena: Arena,
    /,
    *,
    head_count: int = 1,
    expansion_factor: int = 0,
    score_scale: float = 0.0,
    dtype: Scalar | None = None,
    values_dtype: Scalar | None = None,
    outputs_dtype: Scalar | None = None,
    carry: _Tensor | None = None,
    progress: _Progress | None = None,
    progress_steps: int = 0,
    stats: DenseTraversalStats | None = None,
) -> DenseVectors

Never writes to the graph, so a growing context may stream through this while insertions continue.

The index holds one layer's keys for one key-value head and no values: row_of names the row of values each scored key reads, so only keys a walk scores are ever resolved.

Parameters

queries_TensorA (Q, dimensions) matrix; row t * head_count + h is head h of token t.
values_TensorAn (N, dimensions) matrix of value rows, as wide as the keys and stored in the index's quantization, borrowed until the call is final.
row_ofCallable[[Key], int | None]Called with each scored Key, answers its row of values. None, or a row outside them, refuses the call with ShapeMismatchError, as do values of another width or scalar type. It re-enters Python from the library's threads, so it runs under the GIL and serializes the walk. A VRAM walk resolves values on the device, where no Python callable runs, so a VRAM arena raises DeviceError.
arenaArenaWhere the attended rows land, rewound by the call.
head_countintThe query heads sharing the index's key-value head, and with it one walk per token; it must divide Q.
expansion_factorintBreadth of the candidates; 0 derives it. Wider candidates walk further, folding more of the tail.
score_scalefloatThe logit scale, conventionally the inverse square root of the head dimensionality; 0.0 derives it from the frozen geometry.
dtypeScalar | NoneNames the query scalar type when the buffer cannot.
values_dtypeScalar | NoneNames the values' scalar type when the buffer cannot.
outputs_dtypeScalar | NoneThe scalar type to write the outputs as; defaults to the queries' own.
carry_Tensor | NoneA writable (Q, 2) buffer of float32, holding each query's largest logit and the softmax denominator taken against it. The pair recombines this branch with another over the same queries - attention sinks, a dense recent window, or a second index of one context - by partial-softmax rescaling; a maximum without its partition is a number nothing can rescale, which is why they travel as one row.
progress_Progress | NoneCalled with (completed, total) tokens; returning False stops the batch and raises CancelledError, None carries on.
progress_stepsintReports the call makes at most; 0 derives to ten.
statsDenseTraversalStats | NoneOverwritten with this call's telemetry.

Returns

The (Q, dimensions) attended rows in the arena, already divided by the partition; kinds memoryview cannot format leave as a (Q, row_bytes) run of 'B'.
*× 2Late-interaction scoring of multi-row queries against multi-vector documents: every query row takes its best match within a key, and those maxima sum to that key's score.
lib.pyi:925 ↗
def maxsim(
    queries: _Tensor,
    rows_per_query: int,
    wanted: int,
    arena: Arena,
    /,
    *,
    expansion_factor: int = 0,
    traversal: Traversal = ...,
    dtype: Scalar | None = None,
    predicate: Callable[[Key], bool] | None = None,
    progress: _Progress | None = None,
    progress_steps: int = 0,
    stats: DenseTraversalStats | None = None,
) -> DenseProducts

A document is one key holding its token rows, which is what values_per_key above one exists for, and the walk stays over individual tokens under a plain inner product - so a document's vectors need no ordering and two documents need not share a vector count. The result is sized over documents rather than over rows.

Parameters

queries_TensorThe token rows of every query laid end to end, an (Q * R, dimensions) matrix.
rows_per_queryintRows every query occupies; uniform, so ColBERT's padding applies as is. The query row count must be a whole multiple of it.
wantedintUpper bound on matches per query - the 'k' over documents, not rows; 0 keeps every document the query scored.
arenaArenaWhere the scored documents land, rewound by the call.
expansion_factorintBreadth of the candidates; 0 derives it. Widening it nominates more documents, each still scored exactly.
traversalTraversalTraversal.WALK, the default, nominates documents by walking the graph; Traversal.SCAN nominates every document, on RAM and NVMe.
dtypeScalar | NoneNames the query scalar type when the buffer cannot.
predicateCallable[[Key], bool] | NoneAdmits a Key when it returns truthy, asked before any of its vectors are scored, so a rejected document never scores.
progress_Progress | NoneCalled with (completed, total) queries; returning False stops the batch and raises CancelledError, None carries on.
progress_stepsintReports the call makes at most; 0 derives to ten.
statsDenseTraversalStats | NoneOverwritten with this call's telemetry.

Returns

offsets, (Q + 1,) Apache Arrow offsets, query i spanning documents offsets[i] up to offsets[i + 1], largest first; keys, a (documents, 16) run of identities; scores, their (documents,) summed maxima; and required, the documents scored before wanted cut any. All of them live in the arena.
*× 2Absorbs every donor into this index, one after another, keeping their graphs rather than re-inserting their vectors, and linking each in by walks across the appended part.
lib.pyi:977 ↗
def absorb(
    *donors: DenseIndex = (),
    progress: _Progress | None = None,
    progress_steps: int = 0,
) -> RefineReport

Each donor handle is consumed and every verb on it afterwards raises. Readers of a RAM index may keep searching throughout, while a VRAM one admits no overlapping search.

Cancellation stops the closing refinement alone: every donor is still consumed, the islands still re-linked, and the call raises CancelledError. A donor whose absorption fails, and every later one, stays a live handle its caller still owns, though the entries of the one it struck may already have moved into this index.

Parameters

donorsDenseIndexThe indexes to absorb.
progress_Progress | NoneCalled with (completed, total) nodes the closing refinement re-selected; returning False stops it, None carries on.
progress_stepsintReports the call makes at most; 0 derives to ten.

Returns

What the closing refinement did to the graph.
*× 2Splits this index into the empty receivers, the inverse of absorb: the entries are partitioned along the graph, each part lands in one receiver with its edges kept, and a closing refinement re-links every receiver.
lib.pyi:1002 ↗
def split(
    *receivers: DenseIndex = (),
    expansion_factor: int = 0,
    seed: int = 0,
    progress: _Progress | None = None,
    progress_steps: int = 0,
    stats: DenseTraversalStats | None = None,
) -> RefineReport

This index is left empty and live. Receivers match its residency, scalar, metric, geometry and shard count.

Parameters

receiversDenseIndexAt least two empty indexes, one per part.
expansion_factorintBreadth of the closing walks; 0 derives it.
seedintPicks the part roots; equal seeds split equally.
progress_Progress | NoneCalled with (completed, total) nodes the closing refinements re-selected; returning False stops them, None carries on.
progress_stepsintReports the call makes at most; 0 derives to ten.
statsDenseTraversalStats | NoneOverwritten with this call's telemetry.

Returns

What the closing refinements did, across the receivers.
*× 2Draws keys around focus, or across the whole index, each hanging from a shown or earlier draw and placed in three dimensions beside the shown ones - so a view grows one connected sample as it zooms, never scanning the index.
lib.pyi:1030 ↗
def sample(
    wanted: int,
    arena: Arena,
    /,
    *,
    shown: Sequence[_KeyLike] | _Tensor | None = None,
    shown_coordinates: _Tensor | None = None,
    focus: Sequence[_KeyLike] | _Tensor | None = None,
    seed: int = 0,
    expansion_factor: int = 0,
    progress: _Progress | None = None,
    progress_steps: int = 0,
    stats: DenseTraversalStats | None = None,
) -> Sample

Equal seeds, shown keys and focus over an unchanged index draw equally, and a larger wanted draws a superset. Only a RAM index samples yet.

Parameters

wantedintThe upper bound on draws.
arenaArenaWhere the draws are written, rewound by the call.
shownSequence[_KeyLike] | _Tensor | NoneThe keys already shown, never drawn again.
shown_coordinates_Tensor | None(len(shown), 3) float32 places of the shown keys, the points later draws are placed against.
focusSequence[_KeyLike] | _Tensor | NoneThe keys to look around; None looks across the index.
seedintThe draw; equal seeds draw equally.
expansion_factorintBreadth of the walks behind the draws; 0 derives.
progress_Progress | NoneCalled with (written, wanted) draws.
progress_stepsintReports the call makes at most; 0 derives to ten.
statsDenseTraversalStats | NoneOverwritten with this call's telemetry.

Returns

The keys, entry ordinals, parents and coordinates of the draws, fewer than wanted only once the region is exhausted.
*× 2Writes the index under path, in the layout its extension picks: one .safegraphs monolith, or a .safegraphs.json manifest beside one part per shard.
lib.pyi:1070 ↗
def save(
    path: _PathLike,
    /,
    *,
    progress: _Progress | None = None,
    progress_steps: int = 0,
) -> None

Demands a mutation-free window; concurrent readers keep searching throughout.

Parameters

path_PathLikeWhere the checkpoint goes.
progress_Progress | NoneCalled with (completed, total) shards; returning False stops the write and raises CancelledError, leaving a checkpoint no reader accepts and whatever parts it already wrote for the caller to remove.
progress_stepsintReports the call makes at most; 0 derives to ten.
save_*_buffer× 2Serializes the index as one monolith of every shard, the very bytes save writes to a .safegraphs path.
lib.pyi:1086 ↗
def save__buffer(
    arena: Arena,
    /,
    *,
    progress: _Progress | None = None,
    progress_steps: int = 0,
) -> Monolith

Parameters

arenaArenaWhere the monolith lands, rewound by the call.
progress_Progress | NoneCalled with (completed, total) bytes; returning False stops the write and raises CancelledError, None carries on.
progress_stepsintReports the call makes at most; 0 derives to ten.

Returns

The monolith's bytes, in place in the arena.

Examples

>>> index = usearch.DenseIndex.init_ram(dimensions=4)
>>> index.reserve(1)
>>> checkpoint = index.save_buffer(usearch.Arena())
>>> usearch.DenseIndex.metadata_read_buffer(checkpoint).dimensions
4
cursorOpens a walk over the stored entries that DenseCursor.next pulls in batches, from the starts keys in their order when given, or across every entry as stored otherwise.
lib.pyi:1110 ↗
def cursor(
    starts: Sequence[_KeyLike] | _Tensor | None = None,
) -> DenseCursor
search_streamOpens a resumable nearest-neighbor stream for one query, which next pulls for as long as the caller wants more, each pull into the arena it names.
lib.pyi:1116 ↗
def search_stream(
    query: _Tensor,
    /,
    *,
    expansion_factor: int = 0,
    dtype: Scalar | None = None,
) -> DenseSearchStream

Every pull blocks, on the receiver's executor, which the stream keeps alive, or on the calling thread.

Parameters

query_TensorOne vector of dimensions scalars.
expansion_factorintBreadth of the candidates; 0 derives it.
dtypeScalar | NoneNames the query scalar type when the buffer cannot.

Examples

>>> from array import array
>>> index = usearch.DenseIndex.init_ram(dimensions=2)
>>> index.reserve(1)
>>> index.insert([5], array('d', [0.1, 0.2]))
>>> stream, arena = index.search_stream(array('d', [0.1, 0.2])), usearch.Arena()
>>> [int(match.key) for match in stream.next(4, arena)[0]]
[5]
>>> len(stream.next(4, arena)[0])
0
ClassDeferredOnAn index bound to the executor its verbs submit to, each answering a Deferred at once.1
lib.pyi:1143 ↗
class DeferredOn[IndexT]
__class_getitem__Names the index the receiver addresses, as DeferredOn[DenseIndex].
lib.pyi:1148 ↗
def __class_getitem__(item: Any, /) -> GenericAlias