Zig · Metrics

Metrics

const usearch = @import("usearch");const DenseMetric = @import("usearch").DenseMetric;const DenseIndex = @import("usearch").DenseIndex; 3.0.0-rc.1 · 4c3e5023

API

Types

DistanceDistance or similarity score between two entries, in the metric of the index that produced it.
pub const Distance = f64
DenseOptionsThe geometry and metric an empty index freezes - what DenseIndex.initRam and initVram read beside their placement.
pub const DenseOptions = struct

Binding-only.

Stored vectors round up to whole steps of the distance kit the build picked, so the memory is slightly larger than the logical vectors, and that is not a knob.

Fields

metricDenseMetricThe metric kind used for distance calculations between vectors.
quantizationScalarThe scalar type vectors are stored and quantized into.
dimensionsusizeDimensions of the indexed vectors.
connectivityusizeThe optional connections-per-node bound; zero derives.
values_per_keyusizeUpper bound on entries sharing one key; zero is unbounded.
EnumDenseMetricThe dense metric kinds shipped out of the box, all derived from one inner-product kernel with pre-computed magnitudes.2
dense.zig:105 ↗
pub const DenseMetric = enum(c_int)

The bit-level pair reads its rows as Scalar.u1x8 and refuses any other quantization; the first three refuse that one. Mirrors usearch_dense_metric_t.

Non-exhaustive, so a kind a newer engine reports arrives intact rather than coerced.

Values

unknown0No kind named - an unset field, refused by every constructor.
inner1Sum of elementwise products, unnormalized - the only kind the linear-algebra verbs accept.
angular2Cosine distance in [0, 2], blind to magnitude.
euclidean3L2 distance, zero only for identical vectors.
hamming4Count of differing bits, so its range is the dimensionality.
jaccard5Differing bits over union bits in [0, 1], ignoring the zeros both vectors share.
nameThe canonical spelling, as .inner spells "inner".
dense.zig:121 ↗
pub fn name(self: DenseMetric) [:0]const u8

Calls usearch_dense_metric_name.

fromNameReads a spelling back, or null when no kind spells it.
dense.zig:126 ↗
pub fn fromName(text: []const u8) ?DenseMetric

Calls usearch_dense_metric_from_name.

StructDenseIndexVector Search Index over equidimensional dense representations.36
dense.zig:910 ↗
pub const DenseIndex = extern struct

A handle is shareable across threads: searches never write to the graph, and insertions synchronize through per-node locks and publish in order. It is the Executor that orders, never the index.

const usearch = @import("usearch");

var index = try usearch.DenseIndex.initRam(.{ .dimensions = 4, .metric = .angular, .quantization = .i8 }, &.{});
defer index.deinit();
try index.reserve(1_000);

const keys = [_]usearch.Key{ .fromInteger(0), .fromInteger(1) };
const slab = [_]f32{ 0.2, 0.1, 0.2, 0.1, 0.1, 0.2, 0.1, 0.2 };
try index.insert(&keys, try .of(f32, &slab, 4), .{});

const query = [_]f32{ 0.2, 0.1, 0.2, 0.1 };
var arena = try usearch.Arena.init(null);
defer arena.deinit();
const matches = try index.search(try .ofRow(f32, &query), 10, &arena, .{});
const nearest = (try matches.query(0)).keys[0];

Fields

handle?*anyopaqueThe opaque usearch_dense_index_t the ABI hands back.
OnEvery verb of the index, blocking on an executor after every call submitted there before it.
dense.zig:915 ↗
pub const On = Receiver(.blocking, void)
DeferredOnEvery verb of the index, submitted to an executor into a Deferred(Call) the caller holds, borrowing the arena a verb lands in as a blocking call does; only the verb Call describes compiles.
dense.zig:919 ↗
pub fn DeferredOn(comptime Call: type) type
onRuns every verb on executor, whose kind must match the index's residency.
dense.zig:924 ↗
pub fn on(self: DenseIndex, executor: Executor) On

Binding-only.

deferredSubmits a verb to executor, holding it in call - a *Deferred(Call) the caller places anywhere, which stays put until its deinit.
dense.zig:930 ↗
pub fn deferred(
    self: DenseIndex,
    executor: Executor,
    call: anytype,
) DeferredOn(@FieldType(@TypeOf(call.*), "call"))

Binding-only.

diagnosedRuns every verb on the calling thread, copying each call's final outcome into outcome.
dense.zig:935 ↗
pub fn diagnosed(self: DenseIndex, outcome: *Outcome) On

Binding-only.

initRamCreates an empty, mutable index in host memory, one shard per memory domain; empty derives every node the process may allocate on.
dense.zig:946 ↗
pub fn initRam(
    options: DenseOptions,
    memory_domains: []const i32,
) Error!DenseIndex

Calls usearch_dense_init_ram.

initVramCreates an empty, mutable index in device memory, one shard per device and never a replica; empty derives one free device.
dense.zig:965 ↗
pub fn initVram(
    options: DenseOptions,
    devices: []const i32,
) Error!DenseIndex

Calls usearch_dense_init_vram.

loadRamCopies a checkpoint into a new, mutable index in host memory, shard by shard.
dense.zig:986 ↗
pub fn loadRam(
    path: []const u8,
    config: DenseLoadRamConfig,
) Error!DenseIndex

Point path at the .safegraphs.json manifest, never at one of the parts it enumerates. The geometry and metric come from the checkpoint's own headers. Calls usearch_dense_load_ram.

loadVramCopies a checkpoint into a new, mutable index in device memory, with the placement rules of loadRam.
dense.zig:1006 ↗
pub fn loadVram(
    path: []const u8,
    config: DenseLoadVramConfig,
) Error!DenseIndex

Calls usearch_dense_load_vram.

loadRamBufferCopies a monolith from a memory buffer into a new index in host memory, as loadRam does a path.
dense.zig:1025 ↗
pub fn loadRamBuffer(
    buffer: []const u8,
    config: DenseLoadRamBufferConfig,
) Error!DenseIndex

Calls usearch_dense_load_ram_buffer.

loadVramBufferCopies a monolith from a host or device buffer into a new index in device memory.
dense.zig:1045 ↗
pub fn loadVramBuffer(
    buffer: []const u8,
    config: DenseLoadVramBufferConfig,
) Error!DenseIndex

Calls usearch_dense_load_vram_buffer.

viewRamServes a checkpoint in place through a memory mapping, as a read-only index whose mutating verbs report Error.ReadOnly; the files must stay unmodified while it lives.
dense.zig:1065 ↗
pub fn viewRam(path: []const u8) Error!DenseIndex

Calls usearch_dense_view_ram.

viewNvmeServes a checkpoint in place from NVMe drives, read-only and sized past RAM; until NVMe checkpoints carry keys it answers Error.Unsupported.
dense.zig:1075 ↗
pub fn viewNvme(
    path: []const u8,
    config: DenseViewNvmeConfig,
) Error!DenseIndex

Calls usearch_dense_view_nvme.

metadataReadReads a checkpoint's geometry and metric, without loading it.
dense.zig:1091 ↗
pub fn metadataRead(
    path: []const u8,
    config: DenseMetadataReadConfig,
) Error!DenseMetadata

Calls usearch_dense_metadata_read.

metadataReadBufferReads geometry and metric out of a serialized monolith buffer, without loading it.
dense.zig:1108 ↗
pub fn metadataReadBuffer(
    buffer: []const u8,
    config: DenseMetadataReadBufferConfig,
) Error!DenseMetadata

Calls usearch_dense_metadata_read_buffer.

deinitFrees the resources associated with the index, waiting for every deferred call naming it; the handle is invalid afterwards.
dense.zig:1131 ↗
pub fn deinit(self: *DenseIndex) void

Calls usearch_dense_free.

reserveReserves room for a total number of entries, the current size included.
dense.zig:1138 ↗
pub fn reserve(self: DenseIndex, capacity: usize) Error!void

Calls usearch_dense_reserve.

clearErases all entries, keeping the reserved capacity and frozen geometry.
dense.zig:1144 ↗
pub fn clear(self: DenseIndex) Error!void

Never runs concurrently with any other verb on the same handle. Calls usearch_dense_clear.

describeReports the frozen metadata and the current counters in one call.
dense.zig:1149 ↗
pub fn describe(self: DenseIndex) Error!DenseIndexDescription

Calls usearch_dense_describe.

countThe entries stored under key; zero means the index holds none.
dense.zig:1154 ↗
pub fn count(self: DenseIndex, key: Key) Error!usize

Calls usearch_dense_count.

containsWhether the index holds at least one entry under key.
dense.zig:1159 ↗
pub fn contains(self: DenseIndex, key: Key) Error!bool

Binding-only; calls usearch_dense_count.

getEvery vector stored under key as scalar, one packed row per entry in insertion order, written in arena; an absent key yields no rows.
dense.zig:1165 ↗
pub fn get(
    self: DenseIndex,
    key: Key,
    scalar: Scalar,
    arena: *Arena,
) Error!DenseVectors

Calls usearch_dense_get.

insertAdds a run of vectors under their keys - the native batch operation, one key per row.
dense.zig:1175 ↗
pub fn insert(
    self: DenseIndex,
    keys: []const Key,
    vectors: RowsView,
    config: DenseInsertConfig,
) Error!void

The batch is all-or-nothing: every fallible step runs before the first entry identity is claimed, so a handler reports progress but cannot stop it. Overlaps safely with concurrent searches on the same handle, and the input scalar type is independent of the stored quantization. Calls usearch_dense_insert.

refineRe-selects the neighbors of every stored entry against the finished graph, reporting the entries re-selected and changed and the islands healed; zero changed means settled.
dense.zig:1186 ↗
pub fn refine(
    self: DenseIndex,
    config: DenseRefineConfig,
) Error!DenseRefineReport

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. A stopped sweep leaves the graph correct and merely less improved, and reports no Error.Cancelled. Calls usearch_dense_refine.

healthAudits how much of the graph a search reaches from its landmarks; the entries it misses stay unfound until a refine re-links them.
dense.zig:1192 ↗
pub fn health(self: DenseIndex) Error!DenseReachability

Calls usearch_dense_health.

absorbAbsorbs every donor into the index, one after another, keeping their graphs rather than re-inserting their vectors: each donor's entries append shard by shard, then a refinement links them across the appended part.
dense.zig:1214 ↗
pub fn absorb(
    self: DenseIndex,
    donors: []DenseIndex,
    config: DenseAbsorbConfig,
) Error!DenseRefineReport

Donors must share the index's residency, scalar, metric and geometry, or the call answers Error.ConfigMismatch.

Each donor it absorbs is consumed and nulled in place, so deinit on every donor afterwards frees only those a stop between donors or a failure left live. Readers of a RAM index keep searching throughout; a VRAM one admits no overlapping search. Calls usearch_dense_absorb.

splitSplits the index into receivers, the inverse of absorb: graph-local parts keeping their edges, one per receiver, leaving this index empty but live.
dense.zig:1222 ↗
pub fn split(
    self: DenseIndex,
    receivers: []const DenseIndex,
    config: DenseSplitConfig,
) Error!DenseRefineReport

Receivers must be empty and share the index's residency, scalar, class, metric, geometry and shard count, or the call answers Error.ConfigMismatch, and a refusal moves nothing. Calls usearch_dense_split.

sampleDraws up to wanted keys around config.focus, or across the whole index, each hanging from a shown or earlier draw and placed in three dimensions beside the shown ones, written in arena; a larger wanted draws a superset.
dense.zig:1229 ↗
pub fn sample(
    self: DenseIndex,
    wanted: usize,
    arena: *Arena,
    config: DenseSampleConfig,
) Error!DenseDraws

Only RAM samples yet. Calls usearch_dense_sample.

cursorNextCopies up to wanted more entries out of cursor into arena - their keys, and the keys each links to when links asks - answering an empty batch once the walk is exhausted.
dense.zig:1236 ↗
pub fn cursorNext(
    self: DenseIndex,
    cursor: DenseCursor,
    wanted: usize,
    links: bool,
    arena: *Arena,
) Error!DenseCursorBatch

The vectors themselves come from get. Calls usearch_dense_cursor_next.

searchStreamNextPulls up to wanted more matches out of stream into arena, as one query's matches, answering an empty query once the walk is drained.
dense.zig:1246 ↗
pub fn searchStreamNext(
    self: DenseIndex,
    stream: DenseSearchStream,
    wanted: usize,
    arena: *Arena,
) Error!Matches

Ordering beyond the configured expansion horizon is best-effort nearest rather than globally exact, so pulling far past that breadth degrades the ranking rather than ending the stream. Calls usearch_dense_search_stream_next.

saveSaves the index under path in the layout its extension picks - a .safegraphs monolith, or a .safegraphs.json manifest beside its shards.
dense.zig:1256 ↗
pub fn save(
    self: DenseIndex,
    path: []const u8,
    config: DenseSaveConfig,
) Error!void

Demands a mutation-free window; concurrent readers keep searching throughout. A failed save leaves a file without its header, which every reader rejects, and that leftover is the caller's to remove. Calls usearch_dense_save.

saveBufferSaves the index as one monolith - the same bytes save writes to a .safegraphs path - written in arena.
dense.zig:1262 ↗
pub fn saveBuffer(
    self: DenseIndex,
    arena: *Arena,
    config: DenseSaveBufferConfig,
) Error!Monolith

Calls usearch_dense_save_buffer.

multiplyApproximate sparse product of the left-hand rows against the indexed vectors, reporting every pair the traversal scored instead of only the nearest, as a ragged output written in arena with Apache Arrow offsets.
dense.zig:1274 ↗
pub fn multiply(
    self: DenseIndex,
    left: RowsView,
    arena: *Arena,
    config: DenseMultiplyConfig,
) Error!DenseProducts

Pairs arrive in traversal order rather than sorted, and carry no duplicates. Cost is roughly rows × expansion × connectivity × dimensions, sublinear in the indexed entries. Runs on the inner-product kind only, refusing others with Error.ConfigMismatch. Calls usearch_dense_multiply.

attendSoftmax attention of a run of query heads over every indexed key their graph walks score, folded as Flash Attention folds key tiles - no top-k cut, so the long tail the walk scores brings the result toward exact dense attention.
dense.zig:1287 ↗
pub fn attend(
    self: DenseIndex,
    queries: RowsView,
    value_lookup: ValueLookup,
    arena: *Arena,
    config: DenseAttendConfig,
) Error!DenseVectors

One walk per token serves its config.head_count heads, rows t · head_count + h. The index stores no values: value_lookup resolves the keys of every scored batch to their rows, a host function on RAM and a device one on VRAM. The attended rows land in arena as wide as the keys and divided by the partition, and carry is worth asking for only to fold this branch into another over the same queries. Runs on the inner-product kind only. Calls usearch_dense_attend.

maxsimLate-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.
dense.zig:1298 ↗
pub fn maxsim(
    self: DenseIndex,
    queries: RowsView,
    rows_per_query: usize,
    wanted: usize,
    arena: *Arena,
    config: DenseMaxsimConfig,
) Error!DenseProducts

queries carries the queries laid end to end, rows_per_query rows apiece and uniform, so ColBERT's padding applies as is, and each query's keys land in arena largest first, up to wanted of them, or every key it scored when wanted is zero. Approximate downward only, and runs on the inner-product kind only. Calls usearch_dense_maxsim.