usearch 2.26.3 → 2.26.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
@@ -525,20 +525,39 @@ index = Index(ndim=ndim, metric=CompiledMetric(
525
525
 
526
526
  ## Integrations & Users
527
527
 
528
+ ### Databases & Query Engines
529
+
528
530
  - [x] ClickHouse: [C++](https://github.com/ClickHouse/ClickHouse/pull/53447), [docs](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/annindexes#usearch).
529
531
  - [x] DuckDB: [post](https://duckdb.org/2024/05/03/vector-similarity-search-vss.html).
530
532
  - [x] ScyllaDB: [Rust](https://github.com/scylladb/vector-store), [presentation](https://www.slideshare.net/slideshow/vector-search-with-scylladb-by-szymon-wasik/276571548).
531
533
  - [x] TiDB & TiFlash: [C++](https://github.com/pingcap/tiflash), [announcement](https://www.pingcap.com/article/introduce-vector-search-indexes-in-tidb/).
532
- - [x] YugaByte: [C++](https://github.com/yugabyte/yugabyte-db/blob/366b9f5e3c4df3a1a17d553db41d6dc50146f488/src/yb/vector_index/usearch_wrapper.cc).
534
+ - [x] YugaByte: [C++](https://github.com/yugabyte/yugabyte-db/blob/366b9f5e3c4df3a1a17d553db41d6dc50146f488/src/yb/vector_index/usearch_wrapper.cc), [architecture](https://www.yugabyte.com/blog/yugabytedb-vector-indexing-architecture/), [1B-vector benchmark](https://www.yugabyte.com/blog/benchmarking-1-billion-vectors-in-yugabytedb/).
533
535
  - [x] MemGraph: [C++](https://github.com/memgraph/memgraph/blob/784dd8520f65050d033aea8b29446e84e487d091/src/storage/v2/indices/vector_index.cpp), [announcement](https://memgraph.com/blog/simplify-data-retrieval-memgraph-vector-search).
534
- - [x] Google: [UniSim](https://github.com/google/unisim), [RetSim](https://arxiv.org/abs/2311.17264) paper.
535
- - [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).
536
- - [x] Microsoft Semantic Kernel: [Python](https://github.com/microsoft/semantic-kernel/releases/tag/python-0.3.9.dev) and C#.
536
+ - [x] MatrixOne: [GoLang](https://github.com/matrixorigin/matrixone).
537
+ - [x] GreptimeDB: [Rust](https://github.com/GreptimeTeam/greptimedb/blob/main/src/index/src/vector.rs).
538
+ - [x] KurrentDB: [C#](https://github.com/kurrent-io/KurrentDB/blob/master/src/KurrentDB.Kontext/Indexing/USearch/USearchVectorStore.cs), [credits](https://github.com/kurrent-io/KurrentDB/blob/master/NOTICE.md).
539
+ - [x] Chroma: [Rust](https://github.com/chroma-core/chroma/blob/main/rust/index/src/usearch.rs), behind their RaBitQ-quantized SPANN index.
540
+
541
+ ### AI Frameworks & Memory
542
+
543
+ - [x] Google: [Perch](https://github.com/google-research/perch-hoplite) bioacoustics DB, [UniSim](https://github.com/google/unisim), [RetSim](https://arxiv.org/abs/2311.17264) paper.
544
+ - [x] Microsoft: [Semantic Kernel](https://github.com/microsoft/semantic-kernel/releases/tag/python-0.3.9.dev) and [vcpkg](https://github.com/microsoft/vcpkg/tree/master/ports/usearch).
545
+ - [x] LangChain: [Python](https://github.com/langchain-ai/langchain/releases/tag/v0.0.257) and [JavaScript](https://github.com/langchain-ai/langchainjs/releases/tag/0.0.125).
546
+ - [x] Hugging Face: Sentence-Transformers [docs](https://www.sbert.net/docs/package_reference/quantization.html#sentence_transformers.quantization.semantic_search_usearch), [quantization benchmarks](https://huggingface.co/blog/embedding-quantization).
537
547
  - [x] GPTCache: [Python](https://github.com/zilliztech/GPTCache/releases/tag/0.1.29).
538
- - [x] Sentence-Transformers: Python [docs](https://www.sbert.net/docs/package_reference/quantization.html#sentence_transformers.quantization.semantic_search_usearch).
548
+ - [x] MemMachine: [Python](https://github.com/MemMachine/MemMachine/blob/main/packages/server/src/memmachine_server/common/vector_store/vector_search_engine/usearch_engine.py).
539
549
  - [x] Pathway: [Rust](https://github.com/pathwaycom/pathway).
540
550
  - [x] Vald: [GoLang](https://github.com/vdaas/vald).
541
- - [x] MatrixOne: [GoLang](https://github.com/matrixorigin/matrixone).
551
+
552
+ ### Applications & Products
553
+
554
+ - [x] Arm MCP: [Python](https://github.com/arm/mcp/blob/main/arm_kb_search/loaders.py).
555
+ - [x] GPT4All: [C++](https://github.com/nomic-ai/gpt4all/blob/main/gpt4all-chat/src/database.cpp).
556
+ - [x] Ente: [Rust](https://github.com/ente-io/ente/blob/main/rust/crates/photos/src/ml/vector_db.rs), [post](https://ente.com/blog/vector-db/).
557
+ - [x] Tracy profiler: [C++](https://github.com/wolfpld/tracy/blob/master/profiler/src/profiler/TracyLlmEmbeddings.cpp), behind the embeddings index of its LLM assistant.
558
+ - [x] Copilot for Xcode by Intitni: [Swift](https://github.com/intitni/CopilotForXcode/blob/main/Tool/Sources/USearchIndex/UsearchIndex.swift).
559
+ - [x] LLM for Unity: [C#](https://github.com/undreamai/LLMUnity/tree/main/Runtime/RAG/usearch), [docs](https://undream.ai/LLMUnity/).
560
+ - [x] Music Assistant: [Python](https://github.com/music-assistant/server/blob/dev/music_assistant/providers/sonic_similarity/vectors.py), [docs](https://www.music-assistant.io/plugins/sonic-similarity/).
542
561
 
543
562
 
544
563
  ## Citations
@@ -549,7 +568,7 @@ doi = {10.5281/zenodo.7949415},
549
568
  author = {Vardanian, Ash},
550
569
  title = {{USearch by Unum Cloud}},
551
570
  url = {https://github.com/unum-cloud/USearch},
552
- version = {2.26.3},
571
+ version = {2.26.4},
553
572
  year = {2026},
554
573
  }
555
574
  ```
package/c/README.md CHANGED
@@ -19,7 +19,7 @@ int main() {
19
19
  usearch_error_t error = NULL;
20
20
  usearch_init_options_t opts = {
21
21
  .metric_kind = usearch_metric_cos_k,
22
- .scalar_kind = usearch_scalar_f16_k, // or f32_k, bf16_k, e5m2_k, e4m3_k, e3m2_k, e2m3_k, i8_k, u8_k
22
+ .quantization = usearch_scalar_f16_k, // or f32_k, bf16_k, e5m2_k, e4m3_k, e3m2_k, e2m3_k, i8_k, u8_k, b1_k
23
23
  .dimensions = dimensions,
24
24
  .expansion_add = 0, // for defaults
25
25
  .expansion_search = 0 // for defaults
package/cpp/test.cpp CHANGED
@@ -1319,6 +1319,51 @@ void test_isolate() {
1319
1319
  }
1320
1320
  }
1321
1321
 
1322
+ /**
1323
+ * @brief Regression test: `compact()` permutes the slots of all members, so the
1324
+ * key-to-slot lookup and the free-slot list must follow the permutation.
1325
+ * Otherwise `get` returns other members' vectors and later `add` calls
1326
+ * overwrite live members.
1327
+ */
1328
+ void test_compact() {
1329
+ constexpr std::size_t dataset_count = 256;
1330
+ constexpr std::size_t added_count = 64;
1331
+ constexpr std::size_t dimensions = 16;
1332
+ metric_punned_t metric(dimensions, metric_kind_t::l2sq_k, scalar_kind_t::f32_k);
1333
+
1334
+ std::mt19937 generator(42);
1335
+ std::uniform_real_distribution<float> distribution(0.0, 1.0);
1336
+ std::vector<std::vector<float>> vectors(dataset_count + added_count, std::vector<float>(dimensions));
1337
+ for (auto& vector : vectors)
1338
+ std::generate(vector.begin(), vector.end(), [&] { return distribution(generator); });
1339
+
1340
+ index_dense_t index = index_dense_t::make(metric);
1341
+ index.reserve(dataset_count + added_count);
1342
+ for (std::size_t idx = 0; idx < dataset_count; ++idx)
1343
+ expect(index.add(idx, vectors[idx].data()));
1344
+ for (std::size_t idx = 0; idx < dataset_count; idx += 3)
1345
+ expect(index.remove(idx));
1346
+ std::size_t const removed_count = (dataset_count + 2) / 3;
1347
+
1348
+ expect(index.compact());
1349
+ expect_eq(index.size(), dataset_count - removed_count);
1350
+
1351
+ // Freed slots must be reused without clobbering live members
1352
+ for (std::size_t idx = dataset_count; idx < dataset_count + added_count; ++idx)
1353
+ expect(index.add(idx, vectors[idx].data()));
1354
+ expect_eq(index.size(), dataset_count - removed_count + added_count);
1355
+
1356
+ std::vector<float> reconstructed(dimensions);
1357
+ for (std::size_t idx = 0; idx < dataset_count + added_count; ++idx) {
1358
+ bool const removed = idx < dataset_count && idx % 3 == 0;
1359
+ expect_eq(index.contains(idx), !removed);
1360
+ if (removed)
1361
+ continue;
1362
+ expect_eq(index.get(idx, reconstructed.data()), std::size_t(1));
1363
+ expect(std::equal(reconstructed.begin(), reconstructed.end(), vectors[idx].begin()));
1364
+ }
1365
+ }
1366
+
1322
1367
  static void usearch_write_backtrace(int signal_number) {
1323
1368
  std::fprintf(stderr, "\n[usearch] Fatal signal %d. Back-trace:\n", signal_number);
1324
1369
  #if USEARCH_HAS_STD_STACKTRACE
@@ -1584,6 +1629,7 @@ int main(int, char**) {
1584
1629
 
1585
1630
  test_filtered_search();
1586
1631
  test_isolate();
1632
+ test_compact();
1587
1633
  test_load_after_metric_make();
1588
1634
  test_view_of_truncated_file();
1589
1635
  return 0;
@@ -9,7 +9,7 @@
9
9
 
10
10
  #define USEARCH_VERSION_MAJOR 2
11
11
  #define USEARCH_VERSION_MINOR 26
12
- #define USEARCH_VERSION_PATCH 3
12
+ #define USEARCH_VERSION_PATCH 4
13
13
 
14
14
  // Inferring C++ version
15
15
  // https://stackoverflow.com/a/61552074
@@ -1923,6 +1923,9 @@ class index_dense_gt {
1923
1923
  std::forward<executor_at>(executor), std::forward<progress_at>(progress));
1924
1924
  vectors_lookup_ = std::move(new_vectors_lookup);
1925
1925
  vectors_tape_allocator_ = std::move(new_vectors_allocator);
1926
+
1927
+ // Slots were permuted, so the key-to-slot lookup and the free-slot list are stale
1928
+ reindex_keys_();
1926
1929
  return result;
1927
1930
  }
1928
1931
 
@@ -3,10 +3,11 @@
3
3
  #include <float.h> // `_Float16`
4
4
  #include <stdlib.h> // `aligned_alloc`
5
5
 
6
- #include <atomic> // `std::atomic`
7
- #include <chrono> // `std::chrono`
8
- #include <cstring> // `std::strncmp`
9
- #include <thread> // `std::thread`
6
+ #include <atomic> // `std::atomic`
7
+ #include <chrono> // `std::chrono`
8
+ #include <cstring> // `std::strncmp`
9
+ #include <thread> // `std::thread`
10
+ #include <type_traits> // `std::is_floating_point`
10
11
 
11
12
  #include <usearch/index.hpp> // `expected_gt` and macros
12
13
 
@@ -374,10 +375,10 @@ inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::s
374
375
  parsed.result = scalar_kind_t::i8_k;
375
376
  else if (str_equals(name, len, "u8"))
376
377
  parsed.result = scalar_kind_t::u8_k;
377
- else if (str_equals(name, len, "b1"))
378
+ else if (str_equals(name, len, "b1") || str_equals(name, len, "b1x8"))
378
379
  parsed.result = scalar_kind_t::b1x8_k;
379
380
  else
380
- parsed.failed("Unknown type, choose: f64, f32, bf16, f16, e5m2, e4m3, e3m2, e2m3, i8, u8, b1");
381
+ parsed.failed("Unknown type, choose: f64, f32, bf16, f16, e5m2, e4m3, e3m2, e2m3, i8, u8, b1, b1x8");
381
382
  return parsed;
382
383
  }
383
384
 
@@ -2280,8 +2281,23 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct met
2280
2281
 
2281
2282
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
2282
2283
  result_t ab{}, a2{}, b2{};
2284
+ // OpenMP only accepts `x += expr` as a reduction update, not a comma-expression.
2285
+ // With a function-call cast, GCC leaves `a2` and `b2` at zero and every distance becomes 0.
2286
+ add_products_(a, b, dim, ab, a2, b2, std::is_floating_point<scalar_t>());
2287
+
2288
+ result_t result_if_zero[2][2];
2289
+ result_if_zero[0][0] = 1 - ab / (std::sqrt(a2) * std::sqrt(b2));
2290
+ result_if_zero[0][1] = result_if_zero[1][0] = 1;
2291
+ result_if_zero[1][1] = 0;
2292
+ return result_if_zero[a2 == 0][b2 == 0];
2293
+ }
2294
+
2295
+ private:
2296
+ inline void add_products_(scalar_t const* a, scalar_t const* b, std::size_t dim, result_t& ab, result_t& a2,
2297
+ result_t& b2, std::true_type) const noexcept {
2298
+ result_t product{}, first_squared{}, second_squared{};
2283
2299
  #if USEARCH_USE_OPENMP
2284
- #pragma omp simd reduction(+ : ab, a2, b2)
2300
+ #pragma omp simd reduction(+ : product, first_squared, second_squared)
2285
2301
  #elif defined(USEARCH_DEFINED_CLANG)
2286
2302
  #pragma clang loop vectorize(enable)
2287
2303
  #elif defined(USEARCH_DEFINED_GCC)
@@ -2290,14 +2306,24 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct met
2290
2306
  for (std::size_t i = 0; i != dim; ++i) {
2291
2307
  result_t ai = static_cast<result_t>(a[i]);
2292
2308
  result_t bi = static_cast<result_t>(b[i]);
2293
- ab += ai * bi, a2 += square(ai), b2 += square(bi);
2309
+ product += ai * bi;
2310
+ first_squared += square(ai);
2311
+ second_squared += square(bi);
2294
2312
  }
2313
+ ab = product;
2314
+ a2 = first_squared;
2315
+ b2 = second_squared;
2316
+ }
2295
2317
 
2296
- result_t result_if_zero[2][2];
2297
- result_if_zero[0][0] = 1 - ab / (std::sqrt(a2) * std::sqrt(b2));
2298
- result_if_zero[0][1] = result_if_zero[1][0] = 1;
2299
- result_if_zero[1][1] = 0;
2300
- return result_if_zero[a2 == 0][b2 == 0];
2318
+ inline void add_products_(scalar_t const* a, scalar_t const* b, std::size_t dim, result_t& ab, result_t& a2,
2319
+ result_t& b2, std::false_type) const noexcept {
2320
+ for (std::size_t i = 0; i != dim; ++i) {
2321
+ result_t ai = static_cast<result_t>(a[i]);
2322
+ result_t bi = static_cast<result_t>(b[i]);
2323
+ ab += ai * bi;
2324
+ a2 += square(ai);
2325
+ b2 += square(bi);
2326
+ }
2301
2327
  }
2302
2328
  };
2303
2329
 
@@ -2544,9 +2570,14 @@ struct metric_cos_i8_t {
2544
2570
  a2 += square(ai);
2545
2571
  b2 += square(bi);
2546
2572
  }
2547
- result_t a2f = std::sqrt(static_cast<result_t>(a2));
2548
- result_t b2f = std::sqrt(static_cast<result_t>(b2));
2549
- return (ab != 0) ? (1.f - ab / (a2f * b2f)) : 0;
2573
+ // Same tail as `nk_angular_i8_serial`: a zero dot product is orthogonal, not identical.
2574
+ if (a2 == 0 && b2 == 0)
2575
+ return 0;
2576
+ if (ab == 0)
2577
+ return 1;
2578
+ result_t norms = std::sqrt(static_cast<result_t>(a2)) * std::sqrt(static_cast<result_t>(b2));
2579
+ result_t unclipped = 1 - static_cast<result_t>(ab) / norms;
2580
+ return unclipped < 0 ? 0 : unclipped;
2550
2581
  }
2551
2582
  };
2552
2583
 
@@ -2596,9 +2627,14 @@ struct metric_cos_u8_t {
2596
2627
  a2 += square(ai);
2597
2628
  b2 += square(bi);
2598
2629
  }
2599
- result_t a2f = std::sqrt(static_cast<result_t>(a2));
2600
- result_t b2f = std::sqrt(static_cast<result_t>(b2));
2601
- return (ab != 0) ? (1.f - ab / (a2f * b2f)) : 0;
2630
+ // Same tail as `nk_angular_u8_serial`: a zero dot product is orthogonal, not identical.
2631
+ if (a2 == 0 && b2 == 0)
2632
+ return 0;
2633
+ if (ab == 0)
2634
+ return 1;
2635
+ result_t norms = std::sqrt(static_cast<result_t>(a2)) * std::sqrt(static_cast<result_t>(b2));
2636
+ result_t unclipped = 1 - static_cast<result_t>(ab) / norms;
2637
+ return unclipped < 0 ? 0 : unclipped;
2602
2638
  }
2603
2639
  };
2604
2640
 
@@ -1,6 +1,7 @@
1
1
  # USearch for JavaScript
2
2
 
3
- USearch is a high-performance library for building and querying vector search indexes, optimized for Node.js and WASM environments.
3
+ USearch is a high-performance library for building and querying vector search indexes.
4
+ This page is the Node.js addon. The browser build is the C library compiled with Emscripten, documented in `wasm/README.md`.
4
5
 
5
6
  ## Installation
6
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.26.3",
3
+ "version": "2.26.4",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian (https://ashvardanian.com/)",
6
6
  "license": "Apache 2.0",
Binary file
Binary file
Binary file