usearch 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,136 +17,126 @@ Vector Search Engine<br/>
17
17
  <a href="https://github.com/unum-cloud/usearch"><img height="25" src="https://github.com/unum-cloud/.github/raw/main/assets/github.svg" alt="GitHub"></a>
18
18
  </p>
19
19
 
20
- - [x] [Single C++11 header][usearch-header] implementation, easily extendible.
21
- - [x] [4B+](#go-beyond-4b-entries) sized space efficient point-clouds with `uint40_t`.
22
- - [x] [Half-precision](#quantize-on-the-fly) support with [`maratyszcza/fp16`](https://github.com/maratyszcza/fp16).
20
+ <p align="center">
21
+ Euclidean • Angular • Jaccard • Hamming • Haversine • User-Defined Metrics
22
+ <br/>
23
+ <a href="#c++">C++11</a> •
24
+ <a href="#python">Python</a> •
25
+ <a href="#javascript">JavaScript</a> •
26
+ <a href="#java">Java</a> •
27
+ <a href="#rust">Rust</a> •
28
+ <a href="#objective-c">Objective-C</a> •
29
+ <a href="#swift">Swift</a> •
30
+ <a href="#golang">GoLang</a> •
31
+ <a href="#wolfram">Wolfram</a>
32
+ <br/>
33
+ Linux • MacOS • Windows
34
+ </p>
35
+
36
+ ---
37
+
38
+ - [x] Industry-leading [performance](#performance).
39
+ - [x] Easily-extendible [single C++11 header][usearch-header] implementation.
40
+ - [x] [User-defined](#define-custom-metrics) and pre-packaged SIMD-accelerated metrics.
41
+ - [x] [Half-precision `f16` and Quarter-precision `f8`](#quantize-on-the-fly) support on any hardware.
23
42
  - [x] [View from disk](#view-larger-indexes-from-disk), without loading into RAM.
24
- - [x] [Any metric](#define-custom-metrics), includes:
25
- - [x] Euclidean, Dot-product, Cosine,
26
- - [x] Jaccard, Hamming, Haversine.
27
- - [x] Hardware-accelerated [`ashvardanian/simsimd`](https://github.com/ashvardanian/simsimd).
28
- - [x] Variable dimensionality vectors.
29
- - [x] Don't copy vectors if not needed.
43
+ - [x] [4B+](#go-beyond-4b-entries) sized space efficient point-clouds with `uint40_t`.
44
+ - [x] Variable dimensionality vectors - for [obscure use-cases][obscure-use-cases].
30
45
  - [x] [Bring your threads](#bring-your-threads), like OpenMP.
31
46
  - [x] Multiple vectors per label.
32
- - [x] [Python](#python) bindings: `pip install usearch`.
33
- - [x] [JavaScript](#javascript) bindings: `npm install usearch`.
34
- - [x] [Rust](#rust) bindings: `cargo add usearch`.
35
- - [x] [Java](#java) bindings: `cloud.unum:usearch` on GitHub.
36
- - [ ] GoLang bindings.
37
- - [x] Wolfram language bindings.
38
- - [x] For Linux: GCC, Clang.
39
- - [x] For MacOS: Apple Clang.
40
- - [ ] For Windows.
41
- - [ ] Multi-index lookups in Python.
42
47
  - [ ] Thread-safe `reserve`.
43
- - [ ] Distributed construction with MPI.
44
- - [x] AI + Vector Search = Semantic Search.
48
+ - [x] AI + Vector Search = [Semantic Search](#ai--vector-search--semantic-search).
45
49
 
46
50
  [usearch-header]: https://github.com/unum-cloud/usearch/blob/main/include/usearch/usearch.hpp
47
-
48
- ## Features
49
-
50
- ### Define Custom Metrics
51
+ [obscure-use-cases]: https://ashvardanian.com/posts/abusing-vector-search
52
+
53
+ ---
54
+
55
+ | | FAISS | USearch |
56
+ | :----------------- | :---------------------------- | :--------------------------------- |
57
+ | Implementation | 84 K [SLOC][sloc] in `faiss/` | 1 K [SLOC][sloc] in `usearch/` |
58
+ | Supported metrics | 9 fixed metrics | Any User-Defined metrics |
59
+ | Supported ID types | `uint32_t`, `uint64_t` | `uint32_t`, `uint40_t`, `uint64_t` |
60
+ | Dependencies | BLAS, OpenMP | None |
61
+ | Bindings | SWIG | Native |
62
+ | Acceleration | Learned Quantization | Downcasting |
63
+
64
+ FAISS is the industry standard for a high-performance batteries-included vector search engine.
65
+ Both USearch and FAISS implement the same HNSW algorithm.
66
+ But they differ in a lot of design decisions.
67
+ USearch is designed to be compact and broadly compatible without sacrificing performance.
68
+
69
+ | | FAISS, `f32` | USearch, `f32` | USearch, `f16` | USearch, `f8` |
70
+ | :--------- | -----------: | -------------: | -------------: | ------------: |
71
+ | Insertions | 76 K/s | 89 K/s | 73 K/s | **137 K/s** |
72
+ | Queries | 118 K/s | 167 K/s | 140 K/s | **281 K/s** |
73
+ | Recall @1 | 99% | 99.2% | 98% | **99.2%** |
74
+
75
+ > Dataset: 1M vectors sample of the Deep1B dataset.
76
+ > Hardware: `c7g.metal` AWS instance with 64 cores and DDR5 memory.
77
+ > HNSW was configured with identical hyper-parameters:
78
+ > connectivity `M=16`,
79
+ > expansion @ construction `efConstruction=128`,
80
+ > and expansion @ search `ef=64`.
81
+ > Both libraries were compiled for the target architecture.
82
+ > Jump to the [Performance Tuning][benchmarking] section to read about the effects of those hyper-parameters.
83
+
84
+ [sloc]: https://en.wikipedia.org/wiki/Source_lines_of_code
85
+ [benchmarking]: https://github.com/unum-cloud/usearch
86
+
87
+ ## User-Defined Functions
51
88
 
52
89
  Most vector-search packages focus on just 2 metrics - "Inner Product distance" and "Euclidean distance".
53
- The lack of dedicated "Cosine distance" can be justified with the simplicity of normalizing such vectors on the fly.
54
- But that hardly exhausts the list of possible metrics.
90
+ That only partially exhausts the list of possible metrics.
91
+ A good example would be the rare [Haversine][haversine] distance, used to compute the distance between geo-spatial coordinates, extending Vector Search into the GIS domain.
92
+ Another example would be designing a custom metric for **composite embeddings** concatenated from multiple AI models in real-world applications.
93
+ USearch supports that: [Python](#user-defined-functions-in-python) and [C++](#user-defined-functions-in-c) examples.
55
94
 
56
95
  ![USearch: Vector Search Approaches](assets/usearch-approaches-white.png)
57
96
 
58
- Older approaches indexing high-dimensional spaces, like KD-Trees and Locality Sensitive Hashing are hardly extendible to vectors/objects of variable length.
59
- Modern NSW-like approaches, however, only require two objects to be comparable.
60
- This means, the index can be extended to support fuzzy search with Levenstein distance over strings.
61
- USearch implements HNSW, and provides some uncommon-to-vector-search "similarity measures" and "distances" out of the box: Haversine, Jaccard, Hamming, etc.
97
+ Unlike older approaches indexing high-dimensional spaces, like KD-Trees and Locality Sensitive Hashing, HNSW doesn't require vectors to be identical in length.
98
+ They only have to be comparable.
99
+ So you can apply it in [obscure][obscure] applications, like searching for similar sets or fuzzy text matching.
62
100
 
63
- ### Bring your Threads
101
+ [haversine]: https://ashvardanian.com/posts/abusing-vector-search#geo-spatial-indexing
102
+ [obscure]: https://ashvardanian.com/posts/abusing-vector-search
64
103
 
65
- Most AI, HPC, or Big Data packages use some form of a thread pool.
66
- Instead of spawning additional threads within USearch, we focus on thread-safety of the `add` function.
104
+ ## Memory Efficiency, Downcasting, and Quantization
67
105
 
68
- ```cpp
69
- #pragma omp parallel for
70
- for (std::size_t i = 0; i < n; ++i)
71
- native.add(label, span_t{vector, dims}, omp_get_thread_num());
72
- ```
73
-
74
- During initialization we allocate enough temporary memory for all the cores on the machine.
75
- On call, the user can simply supply the identifier of the current thread, making this library easy to integrate with OpenMP and similar tools.
76
-
77
- ### Go Beyond 4B Entries
78
-
79
- Constructing a vector index is a memory-intensive task.
80
- Depending on your configuration parameters, part of the memory will be allocated towards copies of vectors, and another part to the Small World Graph, common to NSW-like algorithms.
81
- Choosing the right data-structures and numeric types can have profound implications on both.
106
+ Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
107
+ Those, however, are only sometimes reliable, can significantly affect the statistical properties of your data, and require regular adjustments if your distribution shifts.
82
108
 
83
109
  ![USearch uint40_t support](assets/usearch-neighbor-types.png)
84
110
 
85
- Internally, some point "label" is mapped into an integer identifier for every stored vector.
86
- Most implementations hard-code it to `uint32_t`, as people rarely store more than 4 Billion vectors.
87
- But when they do, software becomes unusable.
88
- Others hard-code it to `uint64_t`, which hardly space-efficient.
89
- We added a tiny class - `uint40_t`, that should be enough for collections up to 1 Trillion vectors.
90
-
91
- ### View Larger Indexes from Disk
92
-
93
- To construct a large index one may use a beefy RAM-optimized machine, like the AWS `u-24tb1.metal` instances with 24 TB of RAM.
94
- Those are, however, pricy, and would cost you over $200/hour.
95
-
96
- | | To Build | To Serve |
97
- | :------- | :-----------: | :------------: |
98
- | Instance | u-24tb1.metal | is4gen.8xlarge |
99
- | Price | ~ $200/h | ~$4.5/h |
100
- | RAM | 24 TB | 192 GB |
101
- | SSD | EBS | 30 TB |
102
-
103
- You wouldn't want to have them running all the time.
104
- So, if you are working on large datasets, but don't need the in-RAM throughput, you can simply view an existing dataset from disk, without ever loading it fully into memory.
105
-
106
- > Construct on large machines, deploy on the cheap ones.
111
+ Instead, we have focused on high-precision arithmetic over low-precision downcasted vectors.
112
+ The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f32_t`, `f16_t`, `f64_t`, and `f8_t` representations, even if the hardware doesn't natively support it.
113
+ Continuing the topic of memory-efficiency, we provide a `uint40_t` to allow collection with over 4B+ vectors without allocating 8 bytes for every neighbor reference in the proximity graph.
107
114
 
108
- ### Quantize on the Fly
115
+ ## View Larger Indexes from Disk
109
116
 
110
- Most modern CPU have at least partial support for `half`-precision `f16_t` arithmetic.
111
- USearch supports automatic down-casting and up-casting between `f32_t`, `f16_t`, `f64_t`, and `f8_t` representations.
117
+ Modern search systems often suggest using different servers to maximize indexing speed and minimize serving costs.
118
+ Memory-optimized for the first task, and storage-optimized for the second, if the index can be served from external memory, which USearch can.
112
119
 
113
- > Making vectors smaller will help pack more of them in-memory, while also increasing performance on CPUs implementing native support for target type.
120
+ | | To Build | To Serve |
121
+ | :------- | :-------------: | :--------------------: |
122
+ | Instance | u-24tb1.metal | is4gen.8xlarge |
123
+ | Price | ~ $200/h | ~$4.5/h |
124
+ | Memory | 24 TB RAM + EBS | 192 GB RAM + 30 TB SSD |
114
125
 
115
- ## Performance
116
-
117
- Below are the performance numbers for a benchmark running on the 64 cores of AWS `c7g.metal` "Graviton 3"-based instances.
118
- We fix the default configuration in the top line and show the affects of various parameters by changing one parameter at a time.
119
-
120
- | Vectors | Connectivity | EF @ A | EF @ S | **Add**, QPS | **Search**, QPS | **Recall @1** |
121
- | :--------: | :----------: | :----: | :----: | :----------: | :-------------: | ------------: |
122
- | `f32` x256 | 16 | 128 | 64 | 75'640 | 131'654 | 99.3% |
123
- | | | | | | | |
124
- | `f32` x256 | 12 | 128 | 64 | 81'747 | 149'728 | 99.0% |
125
- | `f32` x256 | 32 | 128 | 64 | 64'368 | 104'050 | 99.4% |
126
- | | | | | | | |
127
- | `f32` x256 | 16 | 64 | 32 | 128'644 | 228'422 | 97.2% |
128
- | `f32` x256 | 16 | 256 | 128 | 39'981 | 69'065 | 99.2% |
129
- | | | | | | | |
130
- | `f16` x256 | 16 | 64 | 32 | 128'644 | 228'422 | 97.2% |
131
- | `f32` x256 | 16 | 256 | 128 | 39'981 | 69'065 | 99.2% |
132
-
133
- The main columns are:
134
-
135
- - Add: Number of insertion Queries Per Second.
136
- - Search: Number search Queries Per Second.
137
- - Recall @1: How often does approximate search yield the exact best match?
138
-
139
- To read more and reproduce, jump to [benchmarking section](#benchmarking).
126
+ There is a 50x difference between the cost of such instances for identical capacity.
127
+ Of course, the latency of external memory access will be higher, but it is in part compensated with an excellent prefetching mechanism.
140
128
 
141
129
  ## Usage
142
130
 
143
131
  There are two usage patters:
144
132
 
145
- - Bare-bones with `usearch/usearch.hpp`, only available in C++.
146
- - Full-fat version with it's own threads, mutexes, type-punning, quantization, that is available both in C++ and is wrapped for higher-level bindings.
133
+ 1. Bare-bones with `usearch/usearch.hpp`, only available in C++.
134
+ 2. Full-fat version with it's own threads, mutexes, type-punning, quantization, that is available both in C++ and is wrapped for higher-level bindings.
147
135
 
148
136
  ### C++
149
137
 
138
+ #### Installation
139
+
150
140
  To use in a C++ project simply copy the `include/usearch/usearch.hpp` header into your project.
151
141
  Alternatively fetch it with CMake:
152
142
 
@@ -155,19 +145,10 @@ FetchContent_Declare(usearch GIT_REPOSITORY https://github.com/unum-cloud/usearc
155
145
  FetchContent_MakeAvailable(usearch)
156
146
  ```
157
147
 
158
- The simple usage example would require including the `unum::usearch` namespace and choosing the right "distance" function.
159
- That can be one of the following templates:
160
-
161
- - `cos_gt<float>` for "Cosine" or "Angular" distance.
162
- - `ip_gt<float>` for "Inner Product" or "Dot Product" distance.
163
- - `l2sq_gt<float>` for the squared "L2" or "Euclidean" distance.
164
- - `jaccard_gt<int>` for "Jaccard" distance between two ordered sets of unique elements.
165
- - `bit_hamming_gt<uint>` for "Hamming" distance, as the number of shared bits in hashes.
166
- - `pearson_correlation_gt<float>` for "Pearson" correlation between probability distributions.
167
- - `haversine_gt<float>` for "Haversine" or "Great Circle" distance between coordinates.
148
+ #### Quickstart
168
149
 
169
- That list is easily extendible, and can include similarity measures for vectors that have a different number of elements/dimensions.
170
- The minimal example would be.
150
+ Once included, the low-level C++11 interface is as simple as it gets: `reserve()`, `add()`, `search()`, `size()`, `capacity()`, `save()`, `load()`, `view()`.
151
+ This covers 90% of use-cases.
171
152
 
172
153
  ```c++
173
154
  using namespace unum::usearch;
@@ -180,14 +161,21 @@ index.add(/* label: */ 42, /* vector: */ {&vec, 3});
180
161
  index.search(
181
162
  /* query: */ {&vec, 3}, /* top */ 5 /* results */,
182
163
  /* with callback: */ [](std::size_t label, float distance) { });
164
+ ```
165
+
166
+ The `add` is thread-safe for concurrent index construction.
167
+
168
+ #### Serialization
183
169
 
184
- index.save("index.usearch"); // Serializing to disk
185
- index.load("index.usearch"); // Reconstructing from disk
170
+ ```c++
171
+ index.save("index.usearch");
172
+ index.load("index.usearch"); // Copying from disk
186
173
  index.view("index.usearch"); // Memory-mapping from disk
187
174
  ```
188
175
 
189
- The `add` is thread-safe for concurrent index construction.
190
- For advanced users, more compile-time abstraction are available.
176
+ #### User-Defined Metrics in C++
177
+
178
+ For advanced users, more compile-time abstractions are available.
191
179
 
192
180
  ```cpp
193
181
  template <typename metric_at = ip_gt<float>, //
@@ -198,8 +186,8 @@ template <typename metric_at = ip_gt<float>, //
198
186
  class index_gt;
199
187
  ```
200
188
 
201
- One may also define a custom metric, such as Damerau–Levenshtein distance, to compute the similarity between variable length strings.
202
- The only constraint is the function signature:
189
+ You may want to use a custom memory allocator or a rare scalar type, but most often, you would start by defining a custom similarity measure.
190
+ The function object should have the following signature to support different-length vectors.
203
191
 
204
192
  ```cpp
205
193
  struct custom_metric_t {
@@ -207,82 +195,184 @@ struct custom_metric_t {
207
195
  };
208
196
  ```
209
197
 
198
+ The following distances are pre-packaged:
199
+
200
+ - `cos_gt<scalar_t>` for "Cosine" or "Angular" distance.
201
+ - `ip_gt<scalar_t>` for "Inner Product" or "Dot Product" distance.
202
+ - `l2sq_gt<scalar_t>` for the squared "L2" or "Euclidean" distance.
203
+ - `jaccard_gt<scalar_t>` for "Jaccard" distance between two ordered sets of unique elements.
204
+ - `bit_hamming_gt<scalar_t>` for "Hamming" distance, as the number of shared bits in hashes.
205
+ - `pearson_correlation_gt<scalar_t>` for "Pearson" correlation between probability distributions.
206
+ - `haversine_gt<scalar_t>` for "Haversine" or "Great Circle" distance between coordinates.
207
+
208
+ #### Bring your Threads
209
+
210
+ Most AI, HPC, or Big Data packages use some form of a thread pool.
211
+ Instead of spawning additional threads within USearch, we focus on the thread safety of `add()` function, simplifying resource management.
212
+
213
+ ```cpp
214
+ #pragma omp parallel for
215
+ for (std::size_t i = 0; i < n; ++i)
216
+ native.add(label, span_t{vector, dims}, omp_get_thread_num());
217
+ ```
218
+
219
+ During initialization, we allocate enough temporary memory for all the cores on the machine.
220
+ On the call, the user can supply the identifier of the current thread, making this library easy to integrate with OpenMP and similar tools.
221
+
210
222
  ### Python
211
223
 
212
- Python bindings are implemented with [`pybind/pybind11`](https://github.com/pybind/pybind11).
213
- Assuming the presence of Global Interpreter Lock in Python, on large insertions we spawn threads in the C++ layer.
224
+ #### Installation
214
225
 
215
- ```python
216
- $ pip install usearch
226
+ ```sh
227
+ pip install usearch
228
+ ```
217
229
 
230
+ #### Quickstart
231
+
232
+ ```python
218
233
  import numpy as np
219
234
  from usearch.index import Index
220
235
 
221
236
  index = Index(
222
- ndim=256, # Define the number of dimensions in input vectors
223
- metric='cos', # Choose the "metric" or "distance", default = 'ip', optional
224
- dtype='f32', # Quantize to 'f16' or 'f8' if needed, default = 'f32', optional
237
+ ndim=3, # Define the number of dimensions in input vectors
238
+ metric='cos', # Choose 'l2', 'haversine' or other metric, default = 'ip'
239
+ dtype='f32', # Quantize to 'f16' or 'f8' if needed, default = 'f32'
225
240
  connectivity=16, # How frequent should the connections in the graph be, optional
226
241
  expansion_add=128, # Control the recall of indexing, optional
227
242
  expansion_search=64, # Control the quality of search, optional
228
243
  )
229
244
 
230
- vector = np.random.uniform(0, 0.3, (index.ndim)).astype(np.float32)
245
+ vector = np.array([0.2, 0.6, 0.4], dtype=np.float32)
231
246
  index.add(42, vector)
232
247
  matches, distances, count = index.search(vector, 10)
248
+
249
+ assert len(index) == 1
250
+ assert count == 1
251
+ assert matches[0] == 42
252
+ assert distances[0] <= 0.001
253
+ ```
254
+
255
+ Python bindings are implemented with [`pybind/pybind11`](https://github.com/pybind/pybind11).
256
+ Assuming the presence of Global Interpreter Lock in Python, we spawn threads in the C++ layer on large insertions.
257
+
258
+ #### Serialization
259
+
260
+ ```py
261
+ index.save('index.usearch')
262
+ index.load('index.usearch') # Copy the whole index into memory
263
+ index.view('index.usearch') # View from disk without loading in memory
233
264
  ```
234
265
 
235
- Same can be done with batches, rather than single entries.
236
- Under the hood, worker threads will be spawned, to parallelize the procedure.
266
+ #### Batch Operations
267
+
268
+ Adding or querying a batch of entries is identical to adding a single vector.
269
+ The difference would be in the shape of the tensors.
237
270
 
238
271
  ```py
239
272
  n = 100
240
273
  labels = np.array(range(n), dtype=np.longlong)
241
274
  vectors = np.random.uniform(0, 0.3, (n, index.ndim)).astype(np.float32)
242
275
 
243
- # You can avoid copying the data
244
- # Handy when build 1B+ indexes of memory-mapped files
245
- index.add(labels, vectors, copy=True)
246
- matches, distances, counts = index.search(vectors, 10)
276
+ index.add(labels, vectors, threads=..., copy=...)
277
+ matches, distances, counts = index.search(vectors, 10, threads=...)
278
+
279
+ assert matches.shape[0] == vectors.shape[0]
280
+ assert counts[0] <= 10
281
+ ```
282
+
283
+ You can also override the default `threads` and `copy` arguments in bulk workloads.
284
+ The first controls the number of threads spawned for the task.
285
+ The second controls whether the vector itself will be persisted inside the index.
286
+ If you can preserve the lifetime of the vector somewhere else, you can avoid the copy.
287
+
288
+ #### User-Defined Metrics in Python
289
+
290
+ Assuming the language boundary exists between Python user code and C++ implementation, there are more efficient solutions than passing a Python callable to the engine.
291
+ Luckily, with the help of [Numba][numba], we can JIT compile a function with a matching signature and pass it down to the engine.
292
+
293
+ ```py
294
+ from numba import cfunc, types, carray
295
+
296
+ signature = types.float32(
297
+ types.CPointer(types.float32),
298
+ types.CPointer(types.float32),
299
+ types.size_t, types.size_t)
300
+
301
+ @cfunc(signature)
302
+ def python_dot(a, b, n, m):
303
+ a_array = carray(a, n)
304
+ b_array = carray(b, n)
305
+ c = 0.0
306
+ for i in range(n):
307
+ c += a_array[i] * b_array[i]
308
+ return c
309
+
310
+ index = Index(ndim=ndim, metric_pointer=python_dot.address)
311
+ ```
312
+
313
+ [numba]: https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
314
+
315
+ #### Tooling
316
+
317
+ ```py
318
+ from usearch.index import Index
319
+ from usearch.io import load_matrix, save_matrix
320
+
321
+ vectors = load_matrix('deep1B.fbin')
322
+ index = Index(ndim=vectors.shape[1])
323
+ index.add(labels, vectors)
247
324
  ```
248
325
 
249
326
  ### JavaScript
250
327
 
328
+ #### Installation
329
+
330
+ ```sh
331
+ npm install usearch
332
+ ```
333
+
334
+ #### Quickstart
335
+
251
336
  ```js
252
- // npm install usearch
337
+ var index = new usearch.Index({ metric: 'cos', connectivity: 16, dimensions: 3 })
338
+ index.add(42, new Float32Array([0.2, 0.6, 0.4]))
339
+ var results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10)
253
340
 
254
- var index = new usearch.Index({ metric: 'cos', connectivity: 16, dimensions: 2 })
255
- assert.equal(index.connectivity(), 16)
256
- assert.equal(index.dimensions(), 2)
257
- assert.equal(index.size(), 0)
341
+ assert.equal(index.size(), 1)
342
+ assert.deepEqual(results.labels, new Uint32Array([42]))
343
+ assert.deepEqual(results.distances, new Float32Array([0]))
344
+ ```
258
345
 
259
- index.add(15, new Float32Array([10, 20]))
260
- assert.equal(index.size(), 2)
346
+ #### Serialization
261
347
 
262
- var results = index.search(new Float32Array([13, 14]), 2)
263
- assert.deepEqual(results.labels, new Uint32Array([15, 16]))
264
- assert.deepEqual(results.distances, new Float32Array([45, 130]))
348
+ ```js
349
+ index.save('index.usearch')
350
+ index.load('index.usearch')
351
+ index.view('index.usearch')
265
352
  ```
266
353
 
267
354
  ### Rust
268
355
 
269
- Being a systems-programming language, Rust has better control over memory management and concurrency, but lacks function overloading.
270
- Aside from the `add` and `search`, it also provides `add_in_thread` and `search_in_thread` which let users identify the calling thread to use underlying temporary memory more efficiently.
356
+ #### Installation
271
357
 
272
- ```rust
273
- // cargo add usearch
358
+ ```sh
359
+ cargo add usearch
360
+ ```
361
+
362
+ #### Quickstart
274
363
 
364
+ ```rust
275
365
  let quant: &str = "f16";
276
- let index = new_ip(5, &quant, 0, 0, 0).unwrap();
366
+ let index = new_ip(3, &quant, 0, 0, 0).unwrap();
277
367
 
278
368
  assert!(index.reserve(10).is_ok());
279
369
  assert!(index.capacity() >= 10);
280
370
  assert!(index.connectivity() != 0);
281
- assert_eq!(index.dimensions(), 5);
371
+ assert_eq!(index.dimensions(), 3);
282
372
  assert_eq!(index.size(), 0);
283
373
 
284
- let first: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
285
- let second: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
374
+ let first: [f32; 3] = [0.2, 0.1, 0.2];
375
+ let second: [f32; 3] = [0.2, 0.1, 0.2];
286
376
 
287
377
  assert!(index.add(42, &first).is_ok());
288
378
  assert!(index.add(43, &second).is_ok());
@@ -291,20 +381,43 @@ assert_eq!(index.size(), 2);
291
381
  // Read back the tags
292
382
  let results = index.search(&first, 10).unwrap();
293
383
  assert_eq!(results.count, 2);
384
+ ```
385
+
386
+ Being a systems-programming language, Rust has better control over memory management and concurrency but lacks function overloading.
387
+ Aside from the `add` and `search`, USearch Rust binding also provides `add_in_thread` and `search_in_thread`, which let users identify the calling thread to use underlying temporary memory more efficiently.
294
388
 
295
- // Validate serialization
389
+ #### Serialization
390
+
391
+ ```rust
296
392
  assert!(index.save("index.usearch").is_ok());
297
393
  assert!(index.load("index.usearch").is_ok());
298
394
  assert!(index.view("index.usearch").is_ok());
395
+ ```
396
+
397
+ #### Metrics
299
398
 
300
- // There are more "metrics" available
301
- assert!(new_l2(5, &quant, 0, 0, 0).is_ok());
302
- assert!(new_cos(5, &quant, 0, 0, 0).is_ok());
399
+ ```rust
400
+ assert!(new_l2(3, &quant, 0, 0, 0).is_ok());
401
+ assert!(new_cos(3, &quant, 0, 0, 0).is_ok());
303
402
  assert!(new_haversine(&quant, 0, 0, 0).is_ok());
304
403
  ```
305
404
 
306
405
  ### Java
307
406
 
407
+ #### Installation
408
+
409
+ ```xml
410
+ <dependency>
411
+ <groupId>cloud.unum.usearch</groupId>
412
+ <artifactId>usearch</artifactId>
413
+ <version>0.2.3</version>
414
+ </dependency>
415
+ ```
416
+
417
+ Add that snippet to your `pom.xml` and hit `mvn install`.
418
+
419
+ #### Quickstart
420
+
308
421
  ```java
309
422
  Index index = new Index.Config().metric("cos").dimensions(2).build();
310
423
  float vec[] = {10, 20};
@@ -312,12 +425,20 @@ index.add(42, vec);
312
425
  int[] labels = index.search(vec, 5);
313
426
  ```
314
427
 
315
- ### Objective-C and Swift
428
+ ### Swift
429
+
430
+ #### Installation
431
+
432
+ ```txt
433
+ https://github.com/unum-cloud/usearch
434
+ ```
435
+
436
+ #### Quickstart
316
437
 
317
438
  ```swift
318
- let index = Index.l2(dimensions: 4, connectivity: 8)
319
- let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4]
320
- let vectorB: [Float32] = [0.4, 0.2, 1.2, 1.1]
439
+ let index = Index.l2(dimensions: 3, connectivity: 8)
440
+ let vectorA: [Float32] = [0.3, 0.5, 1.2]
441
+ let vectorB: [Float32] = [0.4, 0.2, 1.2]
321
442
  index.add(label: 42, vector: vectorA[...])
322
443
  index.add(label: 43, vector: vectorB[...])
323
444
 
@@ -329,81 +450,11 @@ assert(results.0[0] == 42)
329
450
 
330
451
  ### Wolfram
331
452
 
332
- ## Benchmarking
333
-
334
- All major HNSW implementation share an identical list of hyper-parameters:
335
-
336
- - connectivity (often called `M`),
337
- - expansion on additions (often called `efConstruction`),
338
- - expansion on search (often called `ef`).
339
-
340
- The default values vary drastically.
341
-
342
- | Library | Connectivity | EF @ A | EF @ S |
343
- | :-------: | :----------: | :----: | :----: |
344
- | `hnswlib` | 16 | 200 | 10 |
345
- | `FAISS` | 32 | 40 | 16 |
346
- | `USearch` | 16 | 128 | 64 |
347
-
348
-
349
- To achieve best results, please compile locally and check out various configuration options.
350
-
351
- ```sh
352
- cmake -B ./build_release \
353
- -DCMAKE_BUILD_TYPE=Release \
354
- -DUSEARCH_USE_OPENMP=1 \
355
- -DUSEARCH_USE_JEMALLOC=1 && \
356
- make -C ./build_release -j
357
-
358
- ./build_release/bench --help
359
- ```
360
-
361
- Which would print the following instructions:
362
-
363
- ```txt
364
- SYNOPSIS
365
- ./build_release/bench [--vectors <path>] [--queries <path>] [--neighbors <path>] [-b] [-j
366
- <integer>] [-c <integer>] [--expansion-add <integer>]
367
- [--expansion-search <integer>] [--native|--f16quant|--i8quant]
368
- [--ip|--l2|--cos|--haversine] [-h]
369
-
370
- OPTIONS
371
- --vectors <path>
372
- .fbin file path to construct the index
373
-
374
- --queries <path>
375
- .fbin file path to query the index
376
-
377
- --neighbors <path>
378
- .ibin file path with ground truth
379
-
380
- -b, --big Will switch to uint40_t for neighbors lists with over 4B entries
381
- -j, --threads <integer>
382
- Uses all available cores by default
383
-
384
- -c, --connectivity <integer>
385
- Index granularity
386
-
387
- --expansion-add <integer>
388
- Affects indexing depth
389
-
390
- --expansion-search <integer>
391
- Affects search depth
392
-
393
- --native Use raw templates instead of type-punned classes
394
- --f16quant Enable `f16_t` quantization
395
- --i8quant Enable `int8_t` quantization
396
- --ip Choose Inner Product metric
397
- --l2 Choose L2 Euclidean metric
398
- --cos Choose Angular metric
399
- --haversine Choose Haversine metric
400
- -h, --help Print this help information on this tool and exit
401
- ```
402
-
403
453
  ## TODO
404
454
 
405
455
  - JavaScript: Allow calling from "worker threads".
406
456
  - Rust: Allow passing a custom thread ID.
457
+ - C# .NET bindings.
407
458
 
408
459
  ## AI + Vector Search = Semantic Search
409
460
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 0.2.4