usearch 2.11.1 → 2.13.1

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 +100 -30
  2. package/binding.gyp +11 -12
  3. package/c/README.md +3 -0
  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 +34 -13
  9. package/cpp/bench.cpp +99 -47
  10. package/cpp/test.cpp +947 -132
  11. package/include/usearch/index.hpp +894 -272
  12. package/include/usearch/index_dense.hpp +418 -156
  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
 
@@ -118,7 +119,7 @@ USearch is compact and broadly compatible without sacrificing performance, prima
118
119
  Base functionality is identical to FAISS, and the interface must be familiar if you have ever investigated Approximate Nearest Neighbors search:
119
120
 
120
121
  ```py
121
- $ pip install numpy usearch
122
+ # pip install numpy usearch
122
123
 
123
124
  import numpy as np
124
125
  from usearch.index import Index
@@ -136,12 +137,13 @@ assert np.allclose(index[42], vector, atol=0.1) # Ensure high tolerance in mixed
136
137
  ```
137
138
 
138
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.
139
141
 
140
142
  ```py
141
143
  index = Index(
142
144
  ndim=3, # Define the number of dimensions in input vectors
143
- metric='cos', # Choose 'l2sq', 'haversine' or other metric, default = 'ip'
144
- 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
145
147
  connectivity=16, # Optional: Limit number of neighbors per graph node
146
148
  expansion_add=128, # Optional: Control the recall of indexing
147
149
  expansion_search=64, # Optional: Control the quality of the search
@@ -360,41 +362,58 @@ In some cases, like Batch operations, feature parity is meaningless, as the host
360
362
 
361
363
  ## Application Examples
362
364
 
363
- ### USearch + AI = Multi-Modal Semantic Search
364
-
365
- [![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
366
366
 
367
367
  AI has a growing number of applications, but one of the coolest classic ideas is to use it for Semantic Search.
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, 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.
369
369
 
370
370
  ```python
371
- import ucall
372
- import uform
373
- import usearch
371
+ from ucall import Server
372
+ from uform import get_model, Modality
373
+ from usearch.index import Index
374
374
 
375
375
  import numpy as np
376
376
  import PIL as pil
377
377
 
378
- server = ucall.Server()
379
- model = uform.get_model('unum-cloud/uform-vl-multilingual')
380
- 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)
381
386
 
382
387
  @server
383
388
  def add(key: int, photo: pil.Image.Image):
384
- image = model.preprocess_image(photo)
385
- vector = model.encode_image(image).detach().numpy()
389
+ image = processor_image(photo)
390
+ vector = model_image(image)
386
391
  index.add(key, vector.flatten(), copy=True)
387
392
 
388
393
  @server
389
394
  def search(query: str) -> np.ndarray:
390
- tokens = model.preprocess_text(query)
391
- vector = model.encode_text(tokens).detach().numpy()
395
+ tokens = processor_text(query)
396
+ vector = model_text(tokens)
392
397
  matches = index.search(vector.flatten(), 3)
393
398
  return matches.keys
394
399
 
395
400
  server.run()
396
401
  ```
397
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
+
398
417
  A more complete [demo with Streamlit is available on GitHub](https://github.com/ashvardanian/usearch-images).
399
418
  We have pre-processed some commonly used datasets, cleaned the images, produced the vectors, and pre-built the index.
400
419
 
@@ -446,32 +465,83 @@ That method was used to build the ["USearch Molecules"](https://github.com/ashva
446
465
  [smiles]: https://en.wikipedia.org/wiki/Simplified_molecular-input_line-entry_system
447
466
  [rdkit-fingerprints]: https://www.rdkit.org/docs/RDKit_Book.html#additional-information-about-the-fingerprints
448
467
 
449
- ### USearch + POI Coordinates = GIS Applications... on iOS?
468
+ ### USearch + POI Coordinates = GIS Applications
450
469
 
451
- [![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.
452
472
 
453
- With Objective-C and Swift iOS bindings, USearch can be easily used in mobile applications.
454
- The [SwiftVectorSearch](https://github.com/ashvardanian/SwiftVectorSearch) project illustrates how to build a dynamic, real-time search system on iOS.
455
- In this example, we use 2-dimensional vectors—encoded as latitude and longitude—to find the closest Points of Interest (POIs) on a map.
456
- 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
+ ```
457
523
 
458
- ## Integrations
524
+ ## Integrations & Users
459
525
 
460
- - [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).
461
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).
462
- - [x] ClickHouse: [C++](https://github.com/ClickHouse/ClickHouse/pull/53447).
463
531
  - [x] Microsoft Semantic Kernel: [Python](https://github.com/microsoft/semantic-kernel/releases/tag/python-0.3.9.dev) and C#.
464
- - [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).
465
535
 
466
536
  ## Citations
467
537
 
468
- ```txt
538
+ ```bibtex
469
539
  @software{Vardanian_USearch_2023,
470
540
  doi = {10.5281/zenodo.7949416},
471
541
  author = {Vardanian, Ash},
472
542
  title = {{USearch by Unum Cloud}},
473
543
  url = {https://github.com/unum-cloud/usearch},
474
- version = {2.11.1},
544
+ version = {2.13.1},
475
545
  year = {2023},
476
546
  month = oct,
477
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
@@ -207,6 +207,9 @@ By default, as many of these structures are created as there are logical cores o
207
207
  Then, one can construct the index in parallel using many cores using third-party thread-pools or tools like OpenACC and OpenMP.
208
208
 
209
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
+
210
213
  #pragma omp parallel for
211
214
  for (size_t i = 0; i < 1000; i++) {
212
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