usearch 2.11.0 → 2.13.0

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.
Files changed (50) hide show
  1. package/README.md +126 -37
  2. package/binding.gyp +11 -12
  3. package/c/README.md +9 -3
  4. package/c/lib.cpp +50 -13
  5. package/c/test.c +60 -55
  6. package/c/usearch.h +16 -0
  7. package/cpp/CMakeLists.txt +6 -0
  8. package/cpp/README.md +46 -14
  9. package/cpp/bench.cpp +99 -47
  10. package/cpp/test.cpp +948 -132
  11. package/include/usearch/index.hpp +894 -272
  12. package/include/usearch/index_dense.hpp +419 -152
  13. package/include/usearch/index_plugins.hpp +99 -49
  14. package/javascript/dist/cjs/usearch.d.ts +39 -1
  15. package/javascript/dist/cjs/usearch.js +11 -1
  16. package/javascript/dist/esm/usearch.d.ts +39 -1
  17. package/javascript/dist/esm/usearch.js +11 -1
  18. package/javascript/tsconfig-esm.json +3 -2
  19. package/javascript/usearch.ts +18 -7
  20. package/package.json +2 -2
  21. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  22. package/prebuilds/linux-arm64/usearch.node +0 -0
  23. package/prebuilds/linux-x64/usearch.node +0 -0
  24. package/simsimd/.cmake-format.py +19 -0
  25. package/simsimd/CONTRIBUTING.md +49 -8
  26. package/simsimd/Cargo.lock +1 -1
  27. package/simsimd/Cargo.toml +10 -5
  28. package/simsimd/Package.swift +16 -0
  29. package/simsimd/README.md +69 -15
  30. package/simsimd/binding.gyp +14 -19
  31. package/simsimd/build.rs +2 -0
  32. package/simsimd/c/lib.c +18 -0
  33. package/simsimd/include/module.modulemap +4 -0
  34. package/simsimd/include/simsimd/binary.h +2 -0
  35. package/simsimd/include/simsimd/dot.h +466 -32
  36. package/simsimd/include/simsimd/probability.h +20 -10
  37. package/simsimd/include/simsimd/simsimd.h +444 -98
  38. package/simsimd/include/simsimd/spatial.h +480 -44
  39. package/simsimd/include/simsimd/types.h +161 -39
  40. package/simsimd/javascript/simsimd.ts +26 -2
  41. package/simsimd/package.json +1 -1
  42. package/simsimd/rust/benches/cosine.rs +8 -2
  43. package/simsimd/rust/benches/native.rs +240 -2
  44. package/simsimd/rust/benches/sqeuclidean.rs +8 -2
  45. package/simsimd/rust/lib.rs +50 -13
  46. package/simsimd/swift/source/SimSIMD.swift +123 -0
  47. package/simsimd/swift/tests/SimSIMDTests.swift +92 -0
  48. package/prebuilds/win32-ia32/usearch.node +0 -0
  49. package/prebuilds/win32-x64/usearch.node +0 -0
  50. package/simsimd/.releaserc +0 -87
package/README.md CHANGED
@@ -49,7 +49,7 @@ Linux • MacOS • Windows • iOS • WebAssembly •
49
49
 
50
50
  - ✅ __[10x faster][faster-than-faiss]__ [HNSW][hnsw-algorithm] implementation than [FAISS][faiss].
51
51
  - ✅ Simple and extensible [single C++11 header][usearch-header] __library__.
52
- - ✅ [Trusted](#integrations) by giants like Google and databases like [ClickHouse][clickhouse-docs].
52
+ - ✅ [Trusted](#integrations) by giants like Google and DBs like [ClickHouse][clickhouse-docs] & [DuckDB][duckdb-docs].
53
53
  - ✅ [SIMD][simd]-optimized and [user-defined metrics](#user-defined-functions) with JIT compilation.
54
54
  - ✅ Hardware-agnostic `f16` & `i8` - [half-precision & quarter-precision support](#memory-efficiency-downcasting-and-quantization).
55
55
  - ✅ [View large indexes from disk](#serving-index-from-disk) without loading into RAM.
@@ -67,6 +67,7 @@ Linux • MacOS • Windows • iOS • WebAssembly •
67
67
  [simd]: https://en.wikipedia.org/wiki/Single_instruction,_multiple_data
68
68
  [faster-than-faiss]: https://www.unum.cloud/blog/2023-11-07-scaling-vector-search-with-intel
69
69
  [clickhouse-docs]: https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/annindexes#usearch
70
+ [duckdb-docs]: https://duckdb.org/2024/05/03/vector-similarity-search-vss.html
70
71
 
71
72
  __Technical Insights__ and related articles:
72
73
 
@@ -94,9 +95,10 @@ USearch is compact and broadly compatible without sacrificing performance, prima
94
95
  | Supported metrics ² | 9 fixed metrics | any metric | extendible |
95
96
  | Supported languages ³ | C++, Python | 10 languages | portable |
96
97
  | Supported ID types ⁴ | 32-bit, 64-bit | 32-bit, 40-bit, 64-bit | efficient |
97
- | Required dependencies ⁵ | BLAS, OpenMP | - | light-weight |
98
- | Bindings ⁶ | SWIG | Native | low-latency |
99
- | Python binding size ⁷ | [~ 10 MB][faiss-weight] | [< 1 MB][usearch-weight] | deployable |
98
+ | Filtering ⁵ | ban-lists | any predicates | composable |
99
+ | Required dependencies ⁶ | BLAS, OpenMP | - | light-weight |
100
+ | Bindings ⁷ | SWIG | Native | low-latency |
101
+ | Python binding size ⁸ | [~ 10 MB][faiss-weight] | [< 1 MB][usearch-weight] | deployable |
100
102
 
101
103
  [sloc]: https://en.wikipedia.org/wiki/Source_lines_of_code
102
104
  [faiss-weight]: https://pypi.org/project/faiss-cpu/#files
@@ -107,16 +109,17 @@ USearch is compact and broadly compatible without sacrificing performance, prima
107
109
  > ² User-defined metrics allow you to customize your search for various applications, from GIS to creating custom metrics for composite embeddings from multiple AI models or hybrid full-text and semantic search.
108
110
  > ³ With USearch, you can reuse the same preconstructed index in various programming languages.
109
111
  > ⁴ The 40-bit integer allows you to store 4B+ vectors without allocating 8 bytes for every neighbor reference in the proximity graph.
110
- > ⁵ Lack of obligatory dependencies makes USearch much more portable.
111
- > ⁶ Native bindings introduce lower call latencies than more straightforward approaches.
112
- > ⁷ Lighter bindings make downloads and deployments faster.
112
+ > ⁵ With USearch the index can be combined with arbitrary external containers, like Bloom filters or third-party databases, to filter out irrelevant keys during index traversal.
113
+ > ⁶ Lack of obligatory dependencies makes USearch much more portable.
114
+ > ⁷ Native bindings introduce lower call latencies than more straightforward approaches.
115
+ > ⁸ Lighter bindings make downloads and deployments faster.
113
116
 
114
117
  [intel-benchmarks]: https://www.unum.cloud/blog/2023-11-07-scaling-vector-search-with-intel
115
118
 
116
119
  Base functionality is identical to FAISS, and the interface must be familiar if you have ever investigated Approximate Nearest Neighbors search:
117
120
 
118
121
  ```py
119
- $ pip install numpy usearch
122
+ # pip install numpy usearch
120
123
 
121
124
  import numpy as np
122
125
  from usearch.index import Index
@@ -134,12 +137,13 @@ assert np.allclose(index[42], vector, atol=0.1) # Ensure high tolerance in mixed
134
137
  ```
135
138
 
136
139
  More settings are always available, and the API is designed to be as flexible as possible.
140
+ The default storage/quantization level is hardware-dependant for efficiency, but `f16` is recommended for most modern CPUs.
137
141
 
138
142
  ```py
139
143
  index = Index(
140
144
  ndim=3, # Define the number of dimensions in input vectors
141
- metric='cos', # Choose 'l2sq', 'haversine' or other metric, default = 'ip'
142
- dtype='f32', # Quantize to 'f16' or 'i8' if needed, default = 'f32'
145
+ metric='cos', # Choose 'l2sq', 'ip', 'haversine' or other metric, default = 'cos'
146
+ dtype='f16', # Store as 'f64', 'f32', 'f16', 'i8', 'b1'..., default = None
143
147
  connectivity=16, # Optional: Limit number of neighbors per graph node
144
148
  expansion_add=128, # Optional: Control the recall of indexing
145
149
  expansion_search=64, # Optional: Control the quality of the search
@@ -192,7 +196,7 @@ When compared to FAISS's `IndexFlatL2` in Google Colab, __[USearch may offer up
192
196
  - `faiss.IndexFlatL2`: __55.3 ms__.
193
197
  - `usearch.index.search`: __2.54 ms__.
194
198
 
195
- ## User-Defined Functions
199
+ ## User-Defined Metrics
196
200
 
197
201
  While most vector search packages concentrate on just two metrics, "Inner Product distance" and "Euclidean distance", USearch allows arbitrary user-defined metrics.
198
202
  This flexibility allows you to customize your search for various applications, from computing geospatial coordinates with the rare [Haversine][haversine] distance to creating custom metrics for composite embeddings from multiple AI models, like joint image-text embeddings.
@@ -228,6 +232,23 @@ So you can apply it in [obscure][obscure] applications, like searching for simil
228
232
  [cppyy]: https://cppyy.readthedocs.io/en/latest/
229
233
  [peachpy]: https://github.com/Maratyszcza/PeachPy
230
234
 
235
+ ## Filtering and Predicate Functions
236
+
237
+ Sometimes you may want to cross-reference search-results against some external database or filter them based on some criteria.
238
+ In most engines, you'd have to manually perform paging requests, successively filtering the results.
239
+ In USearch you can simply pass a predicate function to the search method, which will be applied directly during graph traversal.
240
+ In Rust that would look like this:
241
+
242
+ ```rust
243
+ let is_odd = |key: Key| key % 2 == 1;
244
+ let query = vec![0.2, 0.1, 0.2, 0.1, 0.3];
245
+ let results = index.filtered_search(&query, 10, is_odd).unwrap();
246
+ assert!(
247
+ results.keys.iter().all(|&key| key % 2 == 1),
248
+ "All keys must be odd"
249
+ );
250
+ ```
251
+
231
252
  ## Memory Efficiency, Downcasting, and Quantization
232
253
 
233
254
  Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
@@ -341,41 +362,58 @@ In some cases, like Batch operations, feature parity is meaningless, as the host
341
362
 
342
363
  ## Application Examples
343
364
 
344
- ### USearch + AI = Multi-Modal Semantic Search
345
-
346
- [![USearch Semantic Image Search](https://github.com/ashvardanian/usearch-images/raw/main/assets/usearch-images-slow.gif)](https://github.com/ashvardanian/usearch-images)
365
+ ### USearch + UForm + UCall = Multimodal Semantic Search
347
366
 
348
367
  AI has a growing number of applications, but one of the coolest classic ideas is to use it for Semantic Search.
349
- One can take an encoder model, like the multi-modal [UForm](https://github.com/unum-cloud/uform), and a web-programming framework, like UCall, and build a text-to-image search platform in just 20 lines of Python.
368
+ One can take an encoder model, like the multi-modal [UForm](https://github.com/unum-cloud/uform), and a web-programming framework, like [UCall](https://github.com/unum-cloud/ucall), and build a text-to-image search platform in just 20 lines of Python.
350
369
 
351
370
  ```python
352
- import ucall
353
- import uform
354
- import usearch
371
+ from ucall import Server
372
+ from uform import get_model, Modality
373
+ from usearch.index import Index
355
374
 
356
375
  import numpy as np
357
376
  import PIL as pil
358
377
 
359
- server = ucall.Server()
360
- model = uform.get_model('unum-cloud/uform-vl-multilingual')
361
- index = usearch.index.Index(ndim=256)
378
+ processors, models = get_model('unum-cloud/uform3-image-text-english-small')
379
+ model_text = models[Modality.TEXT_ENCODER]
380
+ model_image = models[Modality.IMAGE_ENCODER]
381
+ processor_text = processors[Modality.TEXT_ENCODER]
382
+ processor_image = processors[Modality.IMAGE_ENCODER]
383
+
384
+ server = Server()
385
+ index = Index(ndim=256)
362
386
 
363
387
  @server
364
388
  def add(key: int, photo: pil.Image.Image):
365
- image = model.preprocess_image(photo)
366
- vector = model.encode_image(image).detach().numpy()
389
+ image = processor_image(photo)
390
+ vector = model_image(image)
367
391
  index.add(key, vector.flatten(), copy=True)
368
392
 
369
393
  @server
370
394
  def search(query: str) -> np.ndarray:
371
- tokens = model.preprocess_text(query)
372
- vector = model.encode_text(tokens).detach().numpy()
395
+ tokens = processor_text(query)
396
+ vector = model_text(tokens)
373
397
  matches = index.search(vector.flatten(), 3)
374
398
  return matches.keys
375
399
 
376
400
  server.run()
377
401
  ```
378
402
 
403
+ Similar experiences can also be imlemented in other languages and on the client side, removing the network latency.
404
+ For Swift and iOS, check out the [`ashvardanian/SwiftSemanticSearch`](https://github.com/ashvardanian/SwiftSemanticSearch) repository.
405
+
406
+ <table>
407
+ <tr>
408
+ <td>
409
+ <img src="https://github.com/ashvardanian/ashvardanian/blob/master/demos/SwiftSemanticSearch-Dog.gif?raw=true" alt="SwiftSemanticSearch demo Dog">
410
+ </td>
411
+ <td>
412
+ <img src="https://github.com/ashvardanian/ashvardanian/blob/master/demos/SwiftSemanticSearch-Flowers.gif?raw=true" alt="SwiftSemanticSearch demo with Flowers">
413
+ </td>
414
+ </tr>
415
+ </table>
416
+
379
417
  A more complete [demo with Streamlit is available on GitHub](https://github.com/ashvardanian/usearch-images).
380
418
  We have pre-processed some commonly used datasets, cleaned the images, produced the vectors, and pre-built the index.
381
419
 
@@ -427,32 +465,83 @@ That method was used to build the ["USearch Molecules"](https://github.com/ashva
427
465
  [smiles]: https://en.wikipedia.org/wiki/Simplified_molecular-input_line-entry_system
428
466
  [rdkit-fingerprints]: https://www.rdkit.org/docs/RDKit_Book.html#additional-information-about-the-fingerprints
429
467
 
430
- ### USearch + POI Coordinates = GIS Applications... on iOS?
468
+ ### USearch + POI Coordinates = GIS Applications
431
469
 
432
- [![USearch Maps with SwiftUI](https://github.com/ashvardanian/SwiftVectorSearch/raw/main/USearch+SwiftUI.gif)](https://github.com/ashvardanian/SwiftVectorSearch)
470
+ Similar to Vector and Molecule search, USearch can be used for Geospatial Information Systems.
471
+ The Haversine distance is available out of the box, but you can also define more complex relationships, like the Vincenty formula, that accounts for the Earth's oblateness.
433
472
 
434
- With Objective-C and Swift iOS bindings, USearch can be easily used in mobile applications.
435
- The [SwiftVectorSearch](https://github.com/ashvardanian/SwiftVectorSearch) project illustrates how to build a dynamic, real-time search system on iOS.
436
- In this example, we use 2-dimensional vectors—encoded as latitude and longitude—to find the closest Points of Interest (POIs) on a map.
437
- The search is based on the Haversine distance metric but can easily be extended to support high-dimensional vectors.
473
+ ```py
474
+ from numba import cfunc, types, carray
475
+ import math
476
+
477
+ # Define the dimension as 2 for latitude and longitude
478
+ ndim = 2
479
+
480
+ # Signature for the custom metric
481
+ signature = types.float32(
482
+ types.CPointer(types.float32),
483
+ types.CPointer(types.float32))
484
+
485
+ # WGS-84 ellipsoid parameters
486
+ a = 6378137.0 # major axis in meters
487
+ f = 1 / 298.257223563 # flattening
488
+ b = (1 - f) * a # minor axis
489
+
490
+ def vincenty_distance(a_ptr, b_ptr):
491
+ a_array = carray(a_ptr, ndim)
492
+ b_array = carray(b_ptr, ndim)
493
+ lat1, lon1, lat2, lon2 = a_array[0], a_array[1], b_array[0], b_array[1]
494
+ L, U1, U2 = lon2 - lon1, math.atan((1 - f) * math.tan(lat1)), math.atan((1 - f) * math.tan(lat2))
495
+ sinU1, cosU1, sinU2, cosU2 = math.sin(U1), math.cos(U1), math.sin(U2), math.cos(U2)
496
+ lambda_, iterLimit = L, 100
497
+ while iterLimit > 0:
498
+ iterLimit -= 1
499
+ sinLambda, cosLambda = math.sin(lambda_), math.cos(lambda_)
500
+ sinSigma = math.sqrt((cosU2 * sinLambda) ** 2 + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ** 2)
501
+ if sinSigma == 0: return 0.0 # Co-incident points
502
+ cosSigma, sigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda, math.atan2(sinSigma, cosSigma)
503
+ sinAlpha, cos2Alpha = cosU1 * cosU2 * sinLambda / sinSigma, 1 - (cosU1 * cosU2 * sinLambda / sinSigma) ** 2
504
+ cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cos2Alpha if not math.isnan(cosSigma - 2 * sinU1 * sinU2 / cos2Alpha) else 0 # Equatorial line
505
+ C = f / 16 * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha))
506
+ lambda_, lambdaP = L + (1 - C) * f * (sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM ** 2)))), lambda_
507
+ if abs(lambda_ - lambdaP) <= 1e-12: break
508
+ if iterLimit == 0: return float('nan') # formula failed to converge
509
+ u2 = cos2Alpha * (a ** 2 - b ** 2) / (b ** 2)
510
+ A = 1 + u2 / 16384 * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)))
511
+ B = u2 / 1024 * (256 + u2 * (-128 + u2 * (74 - 47 * u2)))
512
+ deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM ** 2) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma ** 2) * (-3 + 4 * cos2SigmaM ** 2)))
513
+ s = b * A * (sigma - deltaSigma)
514
+ return s / 1000.0 # Distance in kilometers
515
+
516
+ # Example usage:
517
+ index = Index(ndim=ndim, metric=CompiledMetric(
518
+ pointer=vincenty_distance.address,
519
+ kind=MetricKind.Haversine,
520
+ signature=MetricSignature.ArrayArray,
521
+ ))
522
+ ```
438
523
 
439
- ## Integrations
524
+ ## Integrations & Users
440
525
 
441
- - [x] GPTCache: [Python](https://github.com/zilliztech/GPTCache/releases/tag/0.1.29).
526
+ - [x] ClickHouse: [C++](https://github.com/ClickHouse/ClickHouse/pull/53447), [docs](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/annindexes#usearch).
527
+ - [x] DuckDB: [post](https://duckdb.org/2024/05/03/vector-similarity-search-vss.html).
528
+ - [x] Google: [UniSim](https://github.com/google/unisim), [RetSim](https://arxiv.org/abs/2311.17264) paper.
529
+ - [x] LanternDB: [C++](https://github.com/lanterndata/lantern), [Rust](https://github.com/lanterndata/lantern_extras), [docs](https://lantern.dev/blog/hnsw-index-creation).
442
530
  - [x] LangChain: [Python](https://github.com/langchain-ai/langchain/releases/tag/v0.0.257) and [JavaScript](https://github.com/hwchase17/langchainjs/releases/tag/0.0.125).
443
- - [x] ClickHouse: [C++](https://github.com/ClickHouse/ClickHouse/pull/53447).
444
531
  - [x] Microsoft Semantic Kernel: [Python](https://github.com/microsoft/semantic-kernel/releases/tag/python-0.3.9.dev) and C#.
445
- - [x] LanternDB: [C++](https://github.com/lanterndata/lantern) and [Rust](https://github.com/lanterndata/lantern_extras).
532
+ - [x] GPTCache: [Python](https://github.com/zilliztech/GPTCache/releases/tag/0.1.29).
533
+ - [x] Sentence-Transformers: Python [docs](https://www.sbert.net/docs/package_reference/quantization.html#sentence_transformers.quantization.semantic_search_usearch).
534
+ - [x] Pathway: [Rust](https://github.com/pathwaycom/pathway).
446
535
 
447
536
  ## Citations
448
537
 
449
- ```txt
538
+ ```bibtex
450
539
  @software{Vardanian_USearch_2023,
451
540
  doi = {10.5281/zenodo.7949416},
452
541
  author = {Vardanian, Ash},
453
542
  title = {{USearch by Unum Cloud}},
454
543
  url = {https://github.com/unum-cloud/usearch},
455
- version = {2.11.0},
544
+ version = {2.13.0},
456
545
  year = {2023},
457
546
  month = oct,
458
547
  }
package/binding.gyp CHANGED
@@ -25,15 +25,9 @@
25
25
  "GCC_ENABLE_CPP_EXCEPTIONS": "YES",
26
26
  "CLANG_CXX_LIBRARY": "libc++",
27
27
  "MACOSX_DEPLOYMENT_TARGET": "11.0",
28
- "OTHER_CFLAGS": [
29
- "-arch arm64",
30
- "-arch x86_64"
31
- ],
32
- "OTHER_LDFLAGS": [
33
- "-arch arm64",
34
- "-arch x86_64"
35
- ]
36
- },
28
+ "OTHER_CFLAGS": ["-arch arm64", "-arch x86_64"],
29
+ "OTHER_LDFLAGS": ["-arch arm64", "-arch x86_64"],
30
+ },
37
31
  "msvs_settings": {
38
32
  "VCCLCompilerTool": {
39
33
  "ExceptionHandling": 1,
@@ -47,18 +41,19 @@
47
41
  "cflags_cc": [
48
42
  '<!(if [ "$USEARCH_USE_OPENMP" = "1" ]; then echo \'-fopenmp\'; fi)',
49
43
  ],
50
- "ldflags": [
51
- '<!(if [ "$USEARCH_USE_OPENMP" = "1" ]; then echo \'-lgomp\'; fi)'
52
- ],
44
+ "ldflags": ['<!(if [ "$USEARCH_USE_OPENMP" = "1" ]; then echo \'-lgomp\'; fi)'],
53
45
  "defines": [
54
46
  "USEARCH_USE_OPENMP=<!(echo ${USEARCH_USE_OPENMP:-0})",
55
47
  "USEARCH_USE_SIMSIMD=<!(echo ${USEARCH_USE_SIMSIMD:-1})",
56
48
  "USEARCH_USE_FP16LIB=<!(echo ${USEARCH_USE_FP16LIB:-1})",
57
49
  "SIMSIMD_TARGET_NEON=<!(echo ${SIMSIMD_TARGET_NEON:-1})",
50
+ "SIMSIMD_TARGET_NEON_BF16=<!(echo ${SIMSIMD_TARGET_NEON_BF16:-0})",
58
51
  "SIMSIMD_TARGET_SVE=<!(echo ${SIMSIMD_TARGET_SVE:-1})",
52
+ "SIMSIMD_TARGET_SVE_BF16=<!(echo ${SIMSIMD_TARGET_SVE_BF16:-0})",
59
53
  "SIMSIMD_TARGET_HASWELL=<!(echo ${SIMSIMD_TARGET_HASWELL:-1})",
60
54
  "SIMSIMD_TARGET_SKYLAKE=<!(echo ${SIMSIMD_TARGET_SKYLAKE:-1})",
61
55
  "SIMSIMD_TARGET_ICE=<!(echo ${SIMSIMD_TARGET_ICE:-1})",
56
+ "SIMSIMD_TARGET_GENOA=<!(echo ${SIMSIMD_TARGET_GENOA:-1})",
62
57
  "SIMSIMD_TARGET_SAPPHIRE=<!(echo ${SIMSIMD_TARGET_SAPPHIRE:-1})",
63
58
  ],
64
59
  },
@@ -71,10 +66,12 @@
71
66
  "USEARCH_USE_SIMSIMD=<!(echo ${USEARCH_USE_SIMSIMD:-0})",
72
67
  "USEARCH_USE_FP16LIB=<!(echo ${USEARCH_USE_FP16LIB:-1})",
73
68
  "SIMSIMD_TARGET_NEON=<!(echo ${SIMSIMD_TARGET_NEON:-1})",
69
+ "SIMSIMD_TARGET_NEON_BF16=<!(echo ${SIMSIMD_TARGET_NEON_BF16:-0})",
74
70
  "SIMSIMD_TARGET_SVE=<!(echo ${SIMSIMD_TARGET_SVE:-0})",
75
71
  "SIMSIMD_TARGET_HASWELL=<!(echo ${SIMSIMD_TARGET_HASWELL:-1})",
76
72
  "SIMSIMD_TARGET_SKYLAKE=<!(echo ${SIMSIMD_TARGET_SKYLAKE:-0})",
77
73
  "SIMSIMD_TARGET_ICE=<!(echo ${SIMSIMD_TARGET_ICE:-0})",
74
+ "SIMSIMD_TARGET_GENOA=<!(echo ${SIMSIMD_TARGET_GENOA:-0})",
78
75
  "SIMSIMD_TARGET_SAPPHIRE=<!(echo ${SIMSIMD_TARGET_SAPPHIRE:-0})",
79
76
  ],
80
77
  },
@@ -89,9 +86,11 @@
89
86
  "SIMSIMD_TARGET_SKYLAKE=0",
90
87
  "SIMSIMD_TARGET_ICE=0",
91
88
  "SIMSIMD_TARGET_SAPPHIRE=0",
89
+ "SIMSIMD_TARGET_GENOA=0",
92
90
  "SIMSIMD_TARGET_SVE=0",
93
91
  "SIMSIMD_TARGET_HASWELL=0",
94
92
  "SIMSIMD_TARGET_NEON=0",
93
+ "SIMSIMD_TARGET_NEON_BF16=0",
95
94
  ],
96
95
  },
97
96
  ],
package/c/README.md CHANGED
@@ -42,7 +42,7 @@ int main() {
42
42
 
43
43
  // Search:
44
44
  usearch_key_t found_keys[10];
45
- float found_distances[10];
45
+ usearch_distance_t found_distances[10];
46
46
  size_t found_count = usearch_search(
47
47
  index, &vector[0], usearch_scalar_f32_k, 10,
48
48
  &found_keys[0], &found_distances[0], &error);
@@ -113,7 +113,8 @@ simsimd_distance_t callback(void const* a, void const* b, void* state) {
113
113
  // Your custom metric implementation here
114
114
  }
115
115
 
116
- usearch_change_metric(index, callback, NULL, usearch_metric_unknown_k, &error);
116
+ void callback_state = NULL;
117
+ usearch_change_metric(index, callback, callback_state, usearch_metric_unknown_k, &error);
117
118
  ```
118
119
 
119
120
  You can always revert back to one of the native metrics by calling:
@@ -133,8 +134,10 @@ int is_odd(usearch_key_t key, void* state) {
133
134
  return key % 2;
134
135
  }
135
136
 
137
+ usearch_key_t found_keys[10];
138
+ usearch_distance_t found_distances[10];
136
139
  usearch_filtered_search(
137
- index, &vector[0], usearch_scalar_f32_k, 10,
140
+ index, &query[0], usearch_scalar_f32_k, 10,
138
141
  &is_odd, NULL, // no state needed for this callback
139
142
  &found_keys[0], &found_distances[0], &error);
140
143
  ```
@@ -204,6 +207,9 @@ By default, as many of these structures are created as there are logical cores o
204
207
  Then, one can construct the index in parallel using many cores using third-party thread-pools or tools like OpenACC and OpenMP.
205
208
 
206
209
  ```c
210
+ usearch_change_threads_add(index, omp_get_max_threads(), &error);
211
+ usearch_change_threads_search(index, omp_get_max_threads(), &error);
212
+
207
213
  #pragma omp parallel for
208
214
  for (size_t i = 0; i < 1000; i++) {
209
215
  usearch_add(index, i, &vector[0], usearch_scalar_f32_k, &error);
package/c/lib.cpp CHANGED
@@ -94,7 +94,7 @@ std::size_t get_(index_dense_t* index, usearch_key_t key, size_t count, void* ve
94
94
  case scalar_kind_t::f16_k: return index->get(key, (f16_t*)vector, count);
95
95
  case scalar_kind_t::i8_k: return index->get(key, (i8_t*)vector, count);
96
96
  case scalar_kind_t::b1x8_k: return index->get(key, (b1x8_t*)vector, count);
97
- default: return search_result_t().failed("Unknown scalar kind!");
97
+ default: return search_result_t(*index).failed("Unknown scalar kind!");
98
98
  }
99
99
  }
100
100
 
@@ -112,7 +112,7 @@ search_result_t search_(index_dense_t* index, void const* vector, scalar_kind_t
112
112
  return index->filtered_search((i8_t const*)vector, n, std::forward<predicate_at>(predicate));
113
113
  case scalar_kind_t::b1x8_k:
114
114
  return index->filtered_search((b1x8_t const*)vector, n, std::forward<predicate_at>(predicate));
115
- default: return search_result_t().failed("Unknown scalar kind!");
115
+ default: return search_result_t(*index).failed("Unknown scalar kind!");
116
116
  }
117
117
  }
118
118
 
@@ -123,19 +123,32 @@ USEARCH_EXPORT char const* usearch_version(void) {
123
123
  int minor = USEARCH_VERSION_MINOR;
124
124
  int patch = USEARCH_VERSION_PATCH;
125
125
  static char version[32];
126
- sprintf(version, "%d.%d.%d", major, minor, patch);
126
+ std::snprintf(version, sizeof(version), "%d.%d.%d", major, minor, patch);
127
127
  return version;
128
128
  }
129
129
 
130
130
  USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, usearch_error_t* error) {
131
131
 
132
- USEARCH_ASSERT(options && error && "Missing arguments");
132
+ USEARCH_ASSERT(error && "Missing arguments");
133
133
 
134
- index_dense_config_t config(options->connectivity, options->expansion_add, options->expansion_search);
134
+ // The user may want to initialize from a file.
135
+ // In that case he may pass NULL options, and we will try to load the metadata from the file.
136
+ if (!options) {
137
+ index_dense_t* result_ptr = new index_dense_t();
138
+ if (!result_ptr)
139
+ *error = "Out of memory!";
140
+ return result_ptr;
141
+ }
142
+
143
+ index_dense_config_t config;
144
+ config.connectivity = options->connectivity;
145
+ config.expansion_add = options->expansion_add;
146
+ config.expansion_search = options->expansion_search;
135
147
  config.multi = options->multi;
148
+ config.enable_key_lookups = 1;
149
+
136
150
  metric_kind_t metric_kind = metric_kind_to_cpp(options->metric_kind);
137
151
  scalar_kind_t scalar_kind = scalar_kind_to_cpp(options->quantization);
138
-
139
152
  metric_punned_t metric = //
140
153
  !options->metric ? metric_punned_t::builtin(options->dimensions, metric_kind, scalar_kind)
141
154
  : metric_punned_t::stateless(options->dimensions, //
@@ -147,10 +160,18 @@ USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, use
147
160
  return NULL;
148
161
  }
149
162
 
150
- index_dense_t index = index_dense_t::make(metric, config);
151
- index_dense_t* result_ptr = new index_dense_t(std::move(index));
152
- if (!result_ptr || !*result_ptr)
163
+ using state_result_t = typename index_dense_t::state_result_t;
164
+ state_result_t state = index_dense_t::make(metric, config);
165
+ if (!state)
166
+ *error = state.error.release();
167
+ index_dense_t* result_ptr = new index_dense_t(std::move(state.index));
168
+ if (!result_ptr)
153
169
  *error = "Out of memory!";
170
+
171
+ // Let's immediately make it usable by reserving enough threads for this machine:
172
+ if (!result_ptr->try_reserve(index_limits_t()))
173
+ *error = "Out of memory when preparing contexts!";
174
+
154
175
  return result_ptr;
155
176
  }
156
177
 
@@ -304,6 +325,22 @@ USEARCH_EXPORT void usearch_change_expansion_search(usearch_index_t index, size_
304
325
  reinterpret_cast<index_dense_t*>(index)->change_expansion_search(expansion);
305
326
  }
306
327
 
328
+ USEARCH_EXPORT void usearch_change_threads_add(usearch_index_t index, size_t threads, usearch_error_t* error) {
329
+ USEARCH_ASSERT(index && error && "Missing arguments");
330
+ auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
331
+ index_limits_t limits = index_dense.limits();
332
+ limits.threads_add = threads;
333
+ index_dense.try_reserve(limits);
334
+ }
335
+
336
+ USEARCH_EXPORT void usearch_change_threads_search(usearch_index_t index, size_t threads, usearch_error_t* error) {
337
+ USEARCH_ASSERT(index && error && "Missing arguments");
338
+ auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
339
+ index_limits_t limits = index_dense.limits();
340
+ limits.threads_search = threads;
341
+ index_dense.try_reserve(limits);
342
+ }
343
+
307
344
  USEARCH_EXPORT void usearch_change_metric_kind(usearch_index_t index, usearch_metric_kind_t kind,
308
345
  usearch_error_t* error) {
309
346
  USEARCH_ASSERT(index && error && "Missing arguments");
@@ -317,9 +354,9 @@ USEARCH_EXPORT void usearch_change_metric(usearch_index_t index, usearch_metric_
317
354
  USEARCH_ASSERT(index && error && "Missing arguments");
318
355
  auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
319
356
  auto metric_punned =
320
- state ? metric_punned_t::statefull(reinterpret_cast<std::uintptr_t>(metric),
321
- reinterpret_cast<std::uintptr_t>(state), metric_kind_to_cpp(kind),
322
- index_dense.scalar_kind())
357
+ state ? metric_punned_t::stateful(reinterpret_cast<std::uintptr_t>(metric),
358
+ reinterpret_cast<std::uintptr_t>(state), metric_kind_to_cpp(kind),
359
+ index_dense.scalar_kind())
323
360
  : metric_punned_t::stateless(index_dense.dimensions(), reinterpret_cast<std::uintptr_t>(metric),
324
361
  metric_punned_signature_t::array_array_k, metric_kind_to_cpp(kind),
325
362
  index_dense.scalar_kind());
@@ -328,7 +365,7 @@ USEARCH_EXPORT void usearch_change_metric(usearch_index_t index, usearch_metric_
328
365
 
329
366
  USEARCH_EXPORT void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t* error) {
330
367
  USEARCH_ASSERT(index && error && "Missing arguments");
331
- if (!reinterpret_cast<index_dense_t*>(index)->reserve(capacity))
368
+ if (!reinterpret_cast<index_dense_t*>(index)->try_reserve(capacity))
332
369
  *error = "Out of memory!";
333
370
  }
334
371