usearch 2.10.5 → 2.11.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.
@@ -604,11 +604,17 @@ class index_dense_gt {
604
604
  add_result_t add(vector_key_t key, f32_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_f32); }
605
605
  add_result_t add(vector_key_t key, f64_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_f64); }
606
606
 
607
- search_result_t search(b1x8_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_b1x8); }
608
- search_result_t search(i8_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_i8); }
609
- search_result_t search(f16_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_f16); }
610
- search_result_t search(f32_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_f32); }
611
- search_result_t search(f64_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_f64); }
607
+ search_result_t search(b1x8_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from_b1x8); }
608
+ search_result_t search(i8_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from_i8); }
609
+ search_result_t search(f16_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from_f16); }
610
+ search_result_t search(f32_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from_f32); }
611
+ search_result_t search(f64_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from_f64); }
612
+
613
+ template <typename predicate_at> search_result_t filtered_search(b1x8_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_b1x8); }
614
+ template <typename predicate_at> search_result_t filtered_search(i8_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_i8); }
615
+ template <typename predicate_at> search_result_t filtered_search(f16_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_f16); }
616
+ template <typename predicate_at> search_result_t filtered_search(f32_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_f32); }
617
+ template <typename predicate_at> search_result_t filtered_search(f64_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from_f64); }
612
618
 
613
619
  std::size_t get(vector_key_t key, b1x8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_b1x8); }
614
620
  std::size_t get(vector_key_t key, i8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_i8); }
@@ -708,7 +714,9 @@ class index_dense_gt {
708
714
  cluster_config.thread = lock.thread_id;
709
715
  cluster_config.expansion = config_.expansion_search;
710
716
  metric_proxy_t metric{*this};
711
- auto allow = [=](member_cref_t const& member) noexcept { return member.key != free_key_; };
717
+ auto allow = [free_key_ = this->free_key_](member_cref_t const& member) noexcept {
718
+ return member.key != free_key_;
719
+ };
712
720
 
713
721
  // Find the closest cluster for any vector under that key.
714
722
  while (key_range.first != key_range.second) {
@@ -734,6 +742,11 @@ class index_dense_gt {
734
742
  unique_lock_t lock(slot_lookup_mutex_);
735
743
  slot_lookup_.reserve(limits.members);
736
744
  vectors_lookup_.resize(limits.members);
745
+
746
+ // During reserve, no insertions may be happening, so we can safely overwrite the whole collection.
747
+ std::unique_lock<std::mutex> available_threads_lock(available_threads_mutex_);
748
+ available_threads_.resize(limits.threads());
749
+ std::iota(available_threads_.begin(), available_threads_.end(), 0ul);
737
750
  }
738
751
  return typed_->reserve(limits);
739
752
  }
@@ -930,7 +943,7 @@ class index_dense_gt {
930
943
  return result.failed("Slot type doesn't match, consider rebuilding");
931
944
 
932
945
  config_.multi = head.multi;
933
- metric_ = metric_t(head.dimensions, head.kind_metric, head.kind_scalar);
946
+ metric_ = metric_t::builtin(head.dimensions, head.kind_metric, head.kind_scalar);
934
947
  cast_buffer_.resize(available_threads_.size() * metric_.bytes_per_vector());
935
948
  casts_ = make_casts_(head.kind_scalar);
936
949
  }
@@ -1016,7 +1029,7 @@ class index_dense_gt {
1016
1029
  return result.failed("Slot type doesn't match, consider rebuilding");
1017
1030
 
1018
1031
  config_.multi = head.multi;
1019
- metric_ = metric_t(head.dimensions, head.kind_metric, head.kind_scalar);
1032
+ metric_ = metric_t::builtin(head.dimensions, head.kind_metric, head.kind_scalar);
1020
1033
  cast_buffer_.resize(available_threads_.size() * metric_.bytes_per_vector());
1021
1034
  casts_ = make_casts_(head.kind_scalar);
1022
1035
  offset += sizeof(buffer);
@@ -1767,10 +1780,9 @@ class index_dense_gt {
1767
1780
  : typed_->add(key, vector_data, metric, update_config, on_success);
1768
1781
  }
1769
1782
 
1770
- template <typename scalar_at>
1771
- search_result_t search_( //
1772
- scalar_at const* vector, std::size_t wanted, //
1773
- std::size_t thread, bool exact, cast_t const& cast) const {
1783
+ template <typename scalar_at, typename predicate_at>
1784
+ search_result_t search_(scalar_at const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread,
1785
+ bool exact, cast_t const& cast) const {
1774
1786
 
1775
1787
  // Cast the vector, if needed for compatibility with `metric_`
1776
1788
  thread_lock_t lock = thread_lock_(thread);
@@ -1787,8 +1799,17 @@ class index_dense_gt {
1787
1799
  search_config.expansion = config_.expansion_search;
1788
1800
  search_config.exact = exact;
1789
1801
 
1790
- auto allow = [=](member_cref_t const& member) noexcept { return member.key != free_key_; };
1791
- return typed_->search(vector_data, wanted, metric_proxy_t{*this}, search_config, allow);
1802
+ if (std::is_same<typename std::decay<predicate_at>::type, dummy_predicate_t>::value) {
1803
+ auto allow = [free_key_ = this->free_key_](member_cref_t const& member) noexcept {
1804
+ return member.key != free_key_;
1805
+ };
1806
+ return typed_->search(vector_data, wanted, metric_proxy_t{*this}, search_config, allow);
1807
+ } else {
1808
+ auto allow = [free_key_ = this->free_key_, &predicate](member_cref_t const& member) noexcept {
1809
+ return member.key != free_key_ && predicate(member.key);
1810
+ };
1811
+ return typed_->search(vector_data, wanted, metric_proxy_t{*this}, search_config, allow);
1812
+ }
1792
1813
  }
1793
1814
 
1794
1815
  template <typename scalar_at>
@@ -1810,7 +1831,9 @@ class index_dense_gt {
1810
1831
  cluster_config.thread = lock.thread_id;
1811
1832
  cluster_config.expansion = config_.expansion_search;
1812
1833
 
1813
- auto allow = [=](member_cref_t const& member) noexcept { return member.key != free_key_; };
1834
+ auto allow = [free_key_ = this->free_key_](member_cref_t const& member) noexcept {
1835
+ return member.key != free_key_;
1836
+ };
1814
1837
  return typed_->cluster(vector_data, level, metric_proxy_t{*this}, cluster_config, allow);
1815
1838
  }
1816
1839
 
@@ -1320,6 +1320,7 @@ using span_punned_t = span_gt<byte_t const>;
1320
1320
  enum class metric_punned_signature_t {
1321
1321
  array_array_k = 0,
1322
1322
  array_array_size_k,
1323
+ array_array_state_k,
1323
1324
  };
1324
1325
 
1325
1326
  /**
@@ -1339,12 +1340,14 @@ class metric_punned_t {
1339
1340
  using metric_array_array_t = result_t (*)(uptr_t, uptr_t);
1340
1341
  /// Distance function that takes two arrays and their length and returns a scalar.
1341
1342
  using metric_array_array_size_t = result_t (*)(uptr_t, uptr_t, uptr_t);
1343
+ /// Distance function that takes two arrays and some callback state and returns a scalar.
1344
+ using metric_array_array_state_t = result_t (*)(uptr_t, uptr_t, uptr_t);
1342
1345
  /// Distance function callback, like `metric_array_array_size_t`, but depends on member variables.
1343
1346
  using metric_rounted_t = result_t (metric_punned_t::*)(uptr_t, uptr_t) const;
1344
1347
 
1345
1348
  metric_rounted_t metric_routed_ = nullptr;
1346
1349
  uptr_t metric_ptr_ = 0;
1347
- uptr_t metric_size_arg_ = 0;
1350
+ uptr_t metric_third_arg_ = 0;
1348
1351
 
1349
1352
  std::size_t dimensions_ = 0;
1350
1353
  metric_kind_t metric_kind_ = metric_kind_t::unknown_k;
@@ -1367,37 +1370,95 @@ class metric_punned_t {
1367
1370
  inline metric_punned_t() noexcept = default;
1368
1371
  inline metric_punned_t(metric_punned_t const&) noexcept = default;
1369
1372
  inline metric_punned_t& operator=(metric_punned_t const&) noexcept = default;
1370
- inline metric_punned_t( //
1371
- std::size_t dimensions, //
1372
- metric_kind_t metric_kind = metric_kind_t::l2sq_k, //
1373
- scalar_kind_t scalar_kind = scalar_kind_t::f32_k) noexcept
1374
- : metric_routed_(&metric_punned_t::invoke_array_array_size), metric_size_arg_(dimensions),
1375
- dimensions_(dimensions), metric_kind_(metric_kind), scalar_kind_(scalar_kind) {
1373
+
1374
+ inline metric_punned_t(std::size_t dimensions, metric_kind_t metric_kind = metric_kind_t::l2sq_k,
1375
+ scalar_kind_t scalar_kind = scalar_kind_t::f32_k) noexcept
1376
+ : metric_punned_t(builtin(dimensions, metric_kind, scalar_kind)) {}
1377
+
1378
+ inline metric_punned_t(std::size_t dimensions, std::uintptr_t metric_uintptr, metric_punned_signature_t signature,
1379
+ metric_kind_t metric_kind, scalar_kind_t scalar_kind) noexcept
1380
+ : metric_punned_t(stateless(dimensions, metric_uintptr, signature, metric_kind, scalar_kind)) {}
1381
+
1382
+ /**
1383
+ * @brief Creates a metric of a natively supported kind, choosing the best
1384
+ * available backend internally or from SimSIMD.
1385
+ *
1386
+ * @param dimensions The number of elements in the input arrays.
1387
+ * @param metric_kind The kind of metric to use.
1388
+ * @param scalar_kind The kind of scalar to use.
1389
+ * @return A metric object that can be used to compute distances between vectors.
1390
+ */
1391
+ inline static metric_punned_t builtin(std::size_t dimensions, metric_kind_t metric_kind = metric_kind_t::l2sq_k,
1392
+ scalar_kind_t scalar_kind = scalar_kind_t::f32_k) noexcept {
1393
+ metric_punned_t metric;
1394
+ metric.metric_routed_ = &metric_punned_t::invoke_array_array_third;
1395
+ metric.metric_ptr_ = 0;
1396
+ metric.metric_third_arg_ =
1397
+ scalar_kind == scalar_kind_t::b1x8_k ? divide_round_up<CHAR_BIT>(dimensions) : dimensions;
1398
+ metric.dimensions_ = dimensions;
1399
+ metric.metric_kind_ = metric_kind;
1400
+ metric.scalar_kind_ = scalar_kind;
1376
1401
 
1377
1402
  #if USEARCH_USE_SIMSIMD
1378
- if (!configure_with_simsimd())
1379
- configure_with_autovec();
1403
+ if (!metric.configure_with_simsimd())
1404
+ metric.configure_with_autovec();
1380
1405
  #else
1381
- configure_with_autovec();
1406
+ metric.configure_with_autovec();
1382
1407
  #endif
1383
1408
 
1384
- if (scalar_kind == scalar_kind_t::b1x8_k)
1385
- metric_size_arg_ = divide_round_up<CHAR_BIT>(dimensions_);
1409
+ return metric;
1386
1410
  }
1387
1411
 
1388
- inline metric_punned_t( //
1389
- std::size_t dimensions, //
1390
- std::uintptr_t metric_uintptr, metric_punned_signature_t signature, //
1391
- metric_kind_t metric_kind, //
1392
- scalar_kind_t scalar_kind) noexcept
1393
- : metric_routed_(signature == metric_punned_signature_t::array_array_k
1394
- ? &metric_punned_t::invoke_array_array
1395
- : &metric_punned_t::invoke_array_array_size),
1396
- metric_ptr_(metric_uintptr), metric_size_arg_(dimensions), dimensions_(dimensions), metric_kind_(metric_kind),
1397
- scalar_kind_(scalar_kind) {
1412
+ /**
1413
+ * @brief Creates a metric using the provided function pointer for a stateless metric.
1414
+ * So the provided ::metric_uintptr is a pointer to a function that takes two arrays
1415
+ * and returns a scalar. If the ::signature is metric_punned_signature_t::array_array_size_k,
1416
+ * then the third argument is the number of scalar words in the input vectors.
1417
+ *
1418
+ * @param dimensions The number of elements in the input arrays.
1419
+ * @param metric_uintptr The function pointer to the metric function.
1420
+ * @param signature The signature of the metric function.
1421
+ * @param metric_kind The kind of metric to use.
1422
+ * @param scalar_kind The kind of scalar to use.
1423
+ * @return A metric object that can be used to compute distances between vectors.
1424
+ */
1425
+ inline static metric_punned_t stateless(std::size_t dimensions, std::uintptr_t metric_uintptr,
1426
+ metric_punned_signature_t signature, metric_kind_t metric_kind,
1427
+ scalar_kind_t scalar_kind) noexcept {
1428
+ metric_punned_t metric;
1429
+ metric.metric_routed_ = signature == metric_punned_signature_t::array_array_k
1430
+ ? &metric_punned_t::invoke_array_array
1431
+ : &metric_punned_t::invoke_array_array_third;
1432
+ metric.metric_ptr_ = metric_uintptr;
1433
+ metric.metric_third_arg_ =
1434
+ scalar_kind == scalar_kind_t::b1x8_k ? divide_round_up<CHAR_BIT>(dimensions) : dimensions;
1435
+ metric.dimensions_ = dimensions;
1436
+ metric.metric_kind_ = metric_kind;
1437
+ metric.scalar_kind_ = scalar_kind;
1438
+ return metric;
1439
+ }
1398
1440
 
1399
- if (scalar_kind == scalar_kind_t::b1x8_k)
1400
- metric_size_arg_ = divide_round_up<CHAR_BIT>(dimensions_);
1441
+ /**
1442
+ * @brief Creates a metric using the provided function pointer for a statefull metric.
1443
+ * The third argument is the state that will be passed to the metric function.
1444
+ *
1445
+ * @param metric_uintptr The function pointer to the metric function.
1446
+ * @param metric_state The state to pass to the metric function.
1447
+ * @param metric_kind The kind of metric to use.
1448
+ * @param scalar_kind The kind of scalar to use.
1449
+ * @return A metric object that can be used to compute distances between vectors.
1450
+ */
1451
+ inline static metric_punned_t statefull(std::uintptr_t metric_uintptr, std::uintptr_t metric_state,
1452
+ metric_kind_t metric_kind = metric_kind_t::unknown_k,
1453
+ scalar_kind_t scalar_kind = scalar_kind_t::unknown_k) noexcept {
1454
+ metric_punned_t metric;
1455
+ metric.metric_routed_ = &metric_punned_t::invoke_array_array_third;
1456
+ metric.metric_ptr_ = metric_uintptr;
1457
+ metric.metric_third_arg_ = metric_state;
1458
+ metric.dimensions_ = 0;
1459
+ metric.metric_kind_ = metric_kind;
1460
+ metric.scalar_kind_ = scalar_kind;
1461
+ return metric;
1401
1462
  }
1402
1463
 
1403
1464
  inline std::size_t dimensions() const noexcept { return dimensions_; }
@@ -1485,16 +1546,17 @@ class metric_punned_t {
1485
1546
  simsimd_distance_t result;
1486
1547
  // Here `reinterpret_cast` raises warning... we know what we are doing!
1487
1548
  auto function_pointer = (simsimd_metric_punned_t)(metric_ptr_);
1488
- function_pointer(reinterpret_cast<void const*>(a), reinterpret_cast<void const*>(b), metric_size_arg_, &result);
1549
+ function_pointer(reinterpret_cast<void const*>(a), reinterpret_cast<void const*>(b), metric_third_arg_,
1550
+ &result);
1489
1551
  return (result_t)result;
1490
1552
  }
1491
1553
  result_t invoke_simsimd_reverse(uptr_t a, uptr_t b) const noexcept { return 1 - invoke_simsimd(a, b); }
1492
1554
  #else
1493
1555
  bool configure_with_simsimd() noexcept { return false; }
1494
1556
  #endif
1495
- result_t invoke_array_array_size(uptr_t a, uptr_t b) const noexcept {
1557
+ result_t invoke_array_array_third(uptr_t a, uptr_t b) const noexcept {
1496
1558
  auto function_pointer = (metric_array_array_size_t)(metric_ptr_);
1497
- result_t result = function_pointer(a, b, metric_size_arg_);
1559
+ result_t result = function_pointer(a, b, metric_third_arg_);
1498
1560
  return result;
1499
1561
  }
1500
1562
  result_t invoke_array_array(uptr_t a, uptr_t b) const noexcept {
@@ -40,8 +40,8 @@ var _Index_compiledIndex;
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.Index = exports.BatchMatches = exports.Matches = exports.ScalarKind = exports.MetricKind = void 0;
42
42
  const node_gyp_build_1 = __importDefault(require("node-gyp-build"));
43
- const path = __importStar(require("node:path"));
44
- const node_fs_1 = require("node:fs");
43
+ const path = __importStar(require("path"));
44
+ const fs_1 = require("fs");
45
45
  const bindings_1 = require("bindings");
46
46
  const compiled = (0, node_gyp_build_1.default)(getBuildDir(getDirName()));
47
47
  /**
@@ -507,14 +507,14 @@ module.exports = {
507
507
  };
508
508
  // utility functions to help find native builds
509
509
  function getBuildDir(dir) {
510
- if ((0, node_fs_1.existsSync)(path.join(dir, "build")))
510
+ if ((0, fs_1.existsSync)(path.join(dir, "build")))
511
511
  return dir;
512
- if ((0, node_fs_1.existsSync)(path.join(dir, "prebuilds")))
512
+ if ((0, fs_1.existsSync)(path.join(dir, "prebuilds")))
513
513
  return dir;
514
514
  if (path.basename(dir) === ".next") {
515
515
  // special case for next.js on custom node (not vercel)
516
516
  const sideways = path.join(dir, "..", "node_modules", "usearch");
517
- if ((0, node_fs_1.existsSync)(sideways))
517
+ if ((0, fs_1.existsSync)(sideways))
518
518
  return getBuildDir(sideways);
519
519
  }
520
520
  if (dir === "/")
@@ -1,6 +1,6 @@
1
1
  import build from "node-gyp-build";
2
- import * as path from "node:path";
3
- import { existsSync } from "node:fs";
2
+ import * as path from "path";
3
+ import { existsSync } from "fs";
4
4
  import { getFileName, getRoot } from "bindings";
5
5
  const compiled = build(getBuildDir(getDirName()));
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import build from "node-gyp-build";
2
- import * as path from "node:path";
3
- import { existsSync } from "node:fs";
2
+ import * as path from "path";
3
+ import { existsSync } from "fs";
4
4
  import { getFileName, getRoot } from "bindings";
5
5
 
6
6
  const compiled: Compiled = build(getBuildDir(getDirName()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.10.5",
3
+ "version": "2.11.1",
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",
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "dependencies": {
31
31
  "bindings": "^1.5.0",
32
- "node-addon-api": "^3.0.0",
32
+ "node-addon-api": "^8.0.0",
33
33
  "node-gyp-build": "^4.8.0"
34
34
  },
35
35
  "scripts": {
@@ -42,10 +42,10 @@
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/bindings": "^1.5.5",
45
- "@types/node": "^20.11.0",
46
- "node-gyp": "^10.0.1",
45
+ "@types/node": "^20.12.4",
46
+ "node-gyp": "^10.1.0",
47
47
  "prebuildify": "^6.0.0",
48
- "typescript": "^5.3.3"
48
+ "typescript": "^5.4.4"
49
49
  },
50
50
  "main": "javascript/dist/cjs/usearch.js",
51
51
  "module": "javascript/dist/esm/usearch.js",
Binary file
Binary file
Binary file
Binary file