usearch 0.19.2 → 0.20.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.
@@ -48,8 +48,11 @@
48
48
  // OS-specific includes
49
49
  #if defined(USEARCH_DEFINED_WINDOWS)
50
50
  #define _USE_MATH_DEFINES
51
+ #define NOMINMAX
51
52
  #include <Windows.h>
52
53
  #include <sys/stat.h> // `fstat` for file size
54
+ #undef NOMINMAX
55
+ #undef _USE_MATH_DEFINES
53
56
  #else
54
57
  #include <fcntl.h> // `fallocate`
55
58
  #include <stdlib.h> // `posix_memalign`
@@ -159,6 +162,10 @@ template <std::size_t multiple_ak> std::size_t divide_round_up(std::size_t num)
159
162
  return (num + multiple_ak - 1) / multiple_ak;
160
163
  }
161
164
 
165
+ inline std::size_t divide_round_up(std::size_t num, std::size_t denominator) noexcept {
166
+ return (num + denominator - 1) / denominator;
167
+ }
168
+
162
169
  inline std::size_t ceil2(std::size_t v) noexcept {
163
170
  v--;
164
171
  v |= v >> 1;
@@ -171,12 +178,16 @@ inline std::size_t ceil2(std::size_t v) noexcept {
171
178
  return v;
172
179
  }
173
180
 
174
- template <typename at> void misaligned_store(void* ptr, at v) noexcept { std::memcpy(ptr, &v, sizeof(v)); }
181
+ template <typename at> void misaligned_store(void* ptr, at v) noexcept {
182
+ static_assert(!std::is_reference<at>::value);
183
+ std::memcpy(ptr, &v, sizeof(at));
184
+ }
175
185
 
176
186
  /// @brief Simply dereferencing misaligned pointers can be dangerous.
177
187
  template <typename at> at misaligned_load(void* ptr) noexcept {
188
+ static_assert(!std::is_reference<at>::value);
178
189
  at v;
179
- std::memcpy(&v, ptr, sizeof(v));
190
+ std::memcpy(&v, ptr, sizeof(at));
180
191
  return v;
181
192
  }
182
193
 
@@ -637,7 +648,9 @@ template <typename allocator_at = std::allocator<char>> class visits_bitset_gt {
637
648
 
638
649
  void reset() noexcept {
639
650
  if (slots_)
640
- allocator_t{}.deallocate((byte_t*)slots_, count_ * sizeof(slot_t)), slots_ = nullptr, count_ = 0;
651
+ allocator_t{}.deallocate((byte_t*)slots_, count_ * sizeof(slot_t));
652
+ slots_ = nullptr;
653
+ count_ = 0;
641
654
  }
642
655
 
643
656
  bool resize(std::size_t capacity) noexcept {
@@ -658,10 +671,8 @@ template <typename allocator_at = std::allocator<char>> class visits_bitset_gt {
658
671
  }
659
672
 
660
673
  visits_bitset_gt(visits_bitset_gt&& other) noexcept {
661
- slots_ = other.slots_;
662
- count_ = other.count_;
663
- other.slots_ = nullptr;
664
- other.count_ = 0;
674
+ slots_ = exchange(other.slots_, nullptr);
675
+ count_ = exchange(other.count_, 0);
665
676
  }
666
677
 
667
678
  visits_bitset_gt& operator=(visits_bitset_gt&& other) noexcept {
@@ -670,6 +681,9 @@ template <typename allocator_at = std::allocator<char>> class visits_bitset_gt {
670
681
  return *this;
671
682
  }
672
683
 
684
+ visits_bitset_gt(visits_bitset_gt const&) = delete;
685
+ visits_bitset_gt& operator=(visits_bitset_gt const&) = delete;
686
+
673
687
  inline bool test(std::size_t i) const noexcept { return slots_[i / bits_per_slot()] & (1ul << (i & bits_mask())); }
674
688
  inline void set(std::size_t i) noexcept { slots_[i / bits_per_slot()] |= (1ul << (i & bits_mask())); }
675
689
 
@@ -908,17 +922,18 @@ class sorted_buffer_gt {
908
922
  if (!new_elements)
909
923
  return false;
910
924
 
911
- if (size_) {
925
+ if (size_)
912
926
  std::memcpy(new_elements, elements_, size_ * sizeof(element_t));
927
+ if (elements_)
913
928
  allocator.deallocate(elements_, capacity_);
914
- }
929
+
915
930
  elements_ = new_elements;
916
931
  capacity_ = new_capacity;
917
932
  return true;
918
933
  }
919
934
 
920
935
  inline void insert_reserved(element_t&& element) noexcept {
921
- std::size_t slot = std::lower_bound(elements_, elements_ + size_, element, &less) - elements_;
936
+ std::size_t slot = size_ ? std::lower_bound(elements_, elements_ + size_, element, &less) - elements_ : 0;
922
937
  std::size_t to_move = size_ - slot;
923
938
  element_t* source = elements_ + size_ - 1;
924
939
  for (; to_move; --to_move, --source)
@@ -931,7 +946,7 @@ class sorted_buffer_gt {
931
946
  * @return `true` if the entry was added, `false` if it wasn't relevant enough.
932
947
  */
933
948
  inline bool insert(element_t&& element, std::size_t limit) noexcept {
934
- std::size_t slot = std::lower_bound(elements_, elements_ + size_, element, &less) - elements_;
949
+ std::size_t slot = size_ ? std::lower_bound(elements_, elements_ + size_, element, &less) - elements_ : 0;
935
950
  if (slot == limit)
936
951
  return false;
937
952
  std::size_t to_move = size_ - slot - (size_ == limit);
@@ -972,10 +987,10 @@ class usearch_pack_m uint40_t {
972
987
 
973
988
  public:
974
989
  inline uint40_t() noexcept { std::memset(octets, 0, 5); }
975
- inline uint40_t(std::uint32_t n) noexcept { std::memcpy(octets + 1, (char*)&n, 4), octets[0] = 0; }
976
- inline uint40_t(std::uint64_t n) noexcept { std::memcpy(octets, (char*)&n + 3, 5); }
990
+ inline uint40_t(std::uint32_t n) noexcept { std::memcpy(octets, (char*)&n, 4); }
991
+ inline uint40_t(std::uint64_t n) noexcept { std::memcpy(octets, (char*)&n, 5); }
977
992
  #if defined(USEARCH_DEFINED_CLANG) && defined(USEARCH_DEFINED_APPLE)
978
- inline uint40_t(std::size_t n) noexcept { std::memcpy(octets, (char*)&n + 3, 5); }
993
+ inline uint40_t(std::size_t n) noexcept { std::memcpy(octets, (char*)&n, 5); }
979
994
  #endif
980
995
 
981
996
  uint40_t(uint40_t&&) = default;
@@ -983,24 +998,40 @@ class usearch_pack_m uint40_t {
983
998
  uint40_t& operator=(uint40_t&&) = default;
984
999
  uint40_t& operator=(uint40_t const&) = default;
985
1000
 
986
- inline uint40_t& operator+=(std::uint32_t i) noexcept {
987
- std::uint32_t& tail = *reinterpret_cast<std::uint32_t*>(octets + 1);
988
- octets[0] += static_cast<unsigned char>((tail + i) < tail);
989
- tail += i;
1001
+ inline uint40_t& operator+=(std::uint32_t n) noexcept {
1002
+ std::uint32_t& tail = *reinterpret_cast<std::uint32_t*>(octets);
1003
+ octets[4] += static_cast<unsigned char>((tail + n) < tail);
1004
+ tail += n;
1005
+ return *this;
1006
+ }
1007
+
1008
+ inline uint40_t& operator+=(std::size_t n) noexcept {
1009
+ unsigned char* n_octets = reinterpret_cast<unsigned char*>(&n);
1010
+ std::uint32_t& n_tail = *reinterpret_cast<std::uint32_t*>(&n);
1011
+ std::uint32_t& tail = *reinterpret_cast<std::uint32_t*>(octets);
1012
+ octets[4] += static_cast<unsigned char>((tail + n_tail) < tail);
1013
+ tail += n_tail;
1014
+ octets[4] += n_octets[4];
990
1015
  return *this;
991
1016
  }
992
1017
 
1018
+ inline uint40_t operator+(std::size_t n) noexcept {
1019
+ uint40_t other(*this);
1020
+ other += n;
1021
+ return other;
1022
+ }
1023
+
993
1024
  inline operator std::size_t() const noexcept {
994
1025
  std::size_t result = 0;
995
- std::memcpy((char*)&result + 3, octets, 5);
1026
+ std::memcpy((char*)&result, octets, 5);
996
1027
  return result;
997
1028
  }
998
1029
 
999
- inline uint40_t& operator++() noexcept { return *this += 1; }
1030
+ inline uint40_t& operator++() noexcept { return *this += 1u; }
1000
1031
 
1001
1032
  inline uint40_t operator++(int) noexcept {
1002
1033
  uint40_t old = *this;
1003
- *this += 1;
1034
+ *this += 1u;
1004
1035
  return old;
1005
1036
  }
1006
1037
  };
@@ -1305,6 +1336,7 @@ class index_gt {
1305
1336
  using id_t = id_at;
1306
1337
  using allocator_t = allocator_at;
1307
1338
  using point_allocator_t = point_allocator_at;
1339
+ static_assert(sizeof(label_t) >= sizeof(id_t), "Having tiny labels doesn't make sense.");
1308
1340
 
1309
1341
  using scalar_t = typename metric_t::scalar_t;
1310
1342
  using vector_view_t = span_gt<scalar_t const>;
@@ -1313,11 +1345,13 @@ class index_gt {
1313
1345
  struct member_ref_t {
1314
1346
  misaligned_ref_gt<label_t> label;
1315
1347
  vector_view_t vector;
1348
+ id_t id;
1316
1349
  };
1317
1350
 
1318
1351
  struct member_cref_t {
1319
1352
  label_t label;
1320
1353
  vector_view_t vector;
1354
+ id_t id;
1321
1355
  };
1322
1356
 
1323
1357
  template <typename ref_at, typename index_at> class member_iterator_gt {
@@ -1337,7 +1371,11 @@ class index_gt {
1337
1371
  using pointer = void;
1338
1372
  using reference = ref_t;
1339
1373
 
1340
- reference operator*() const noexcept { return ref_t(index_->node_with_id_(offset_)); }
1374
+ reference operator*() const noexcept {
1375
+ node_t node = index_->node_with_id_(offset_);
1376
+ return {node.label(), node.vector_view(), static_cast<id_t>(offset_)};
1377
+ }
1378
+
1341
1379
  member_iterator_gt operator++(int) noexcept { return member_iterator_gt(index_, offset_ + 1); }
1342
1380
  member_iterator_gt operator--(int) noexcept { return member_iterator_gt(index_, offset_ - 1); }
1343
1381
  member_iterator_gt operator+(difference_type d) noexcept { return member_iterator_gt(index_, offset_ + d); }
@@ -1449,9 +1487,9 @@ class index_gt {
1449
1487
  node_t(node_t const&) = default;
1450
1488
  node_t& operator=(node_t const&) = default;
1451
1489
 
1452
- label_t label() const noexcept { return misaligned_load<label_t>(tape_); }
1453
- dim_t dim() const noexcept { return misaligned_load<dim_t>(tape_ + sizeof(label_t)); }
1454
- level_t level() const noexcept { return misaligned_load<level_t>(tape_ + sizeof(label_t) + sizeof(dim_t)); }
1490
+ misaligned_ref_gt<label_t> label() const noexcept { return {tape_}; }
1491
+ misaligned_ref_gt<dim_t> dim() const noexcept { return {tape_ + sizeof(label_t)}; }
1492
+ misaligned_ref_gt<level_t> level() const noexcept { return {tape_ + sizeof(label_t) + sizeof(dim_t)}; }
1455
1493
 
1456
1494
  void label(label_t v) noexcept { return misaligned_store<label_t>(tape_, v); }
1457
1495
  void dim(dim_t v) noexcept { return misaligned_store<dim_t>(tape_ + sizeof(label_t), v); }
@@ -1601,8 +1639,11 @@ class index_gt {
1601
1639
 
1602
1640
  if (nodes_)
1603
1641
  node_allocator_t{}.deallocate(exchange(nodes_, nullptr), limits_.elements);
1604
- if (contexts_)
1642
+ if (contexts_) {
1643
+ for (std::size_t i = 0; i != limits_.threads(); ++i)
1644
+ contexts_[i].~context_t();
1605
1645
  context_allocator_t{}.deallocate(exchange(contexts_, nullptr), limits_.threads());
1646
+ }
1606
1647
  limits_ = index_limits_t{0, 0};
1607
1648
  capacity_ = 0;
1608
1649
  reset_view_();
@@ -1726,7 +1767,7 @@ class index_gt {
1726
1767
  };
1727
1768
 
1728
1769
  struct match_t {
1729
- member_cref_t element;
1770
+ member_cref_t member;
1730
1771
  distance_t distance;
1731
1772
  };
1732
1773
 
@@ -1759,14 +1800,23 @@ class index_gt {
1759
1800
 
1760
1801
  inline operator std::size_t() const noexcept { return count; }
1761
1802
  inline std::size_t size() const noexcept { return count; }
1762
- inline match_t operator[](std::size_t i) const noexcept {
1803
+ inline match_t operator[](std::size_t i) const noexcept { return at(i); }
1804
+ inline bool contains(label_t label) const noexcept {
1805
+ for (std::size_t i = 0; i != count; ++i)
1806
+ if (at(i).member.label == label)
1807
+ return true;
1808
+ return false;
1809
+ }
1810
+ inline match_t at(std::size_t i) const noexcept {
1763
1811
  candidate_t const* top_ordered = top_.data();
1764
- return {member_cref_t(index_.node_with_id_(top_ordered[i].id)), top_ordered[i].distance};
1812
+ candidate_t candidate = top_ordered[i];
1813
+ node_t node = index_.node_with_id_(candidate.id);
1814
+ return {member_cref_t{node.label(), node.vector_view(), candidate.id}, candidate.distance};
1765
1815
  }
1766
1816
  inline std::size_t dump_to(label_t* labels, distance_t* distances) const noexcept {
1767
1817
  for (std::size_t i = 0; i != count; ++i) {
1768
1818
  match_t result = operator[](i);
1769
- labels[i] = result.element.label;
1819
+ labels[i] = result.member.label;
1770
1820
  distances[i] = result.distance;
1771
1821
  }
1772
1822
  return count;
@@ -1774,7 +1824,7 @@ class index_gt {
1774
1824
  inline std::size_t dump_to(label_t* labels) const noexcept {
1775
1825
  for (std::size_t i = 0; i != count; ++i) {
1776
1826
  match_t result = operator[](i);
1777
- labels[i] = result.element.label;
1827
+ labels[i] = result.member.label;
1778
1828
  }
1779
1829
  return count;
1780
1830
  }
@@ -1845,6 +1895,7 @@ class index_gt {
1845
1895
  // TODO: Handle out of memory conditions
1846
1896
  search_to_insert_(closest_id, vector, level, config.expansion, context);
1847
1897
  closest_id = connect_new_node_(new_id, level, context);
1898
+ reconnect_neighbor_nodes_(new_id, level, context);
1848
1899
  }
1849
1900
 
1850
1901
  // Normalize stats
@@ -1859,15 +1910,23 @@ class index_gt {
1859
1910
  return result;
1860
1911
  }
1861
1912
 
1913
+ struct dummy_predicate_t {
1914
+ constexpr bool operator()(match_t const&) const noexcept { return true; }
1915
+ };
1916
+
1862
1917
  /**
1863
1918
  * @brief Searches for the closest elements to the given ::query. Thread-safe.
1864
1919
  *
1865
1920
  * @param[in] query Contiguous range of scalars forming a vector view.
1866
1921
  * @param[in] wanted The upper bound for the number of results to return.
1867
1922
  * @param[in] config Configuration options for this specific operation.
1923
+ * @param[in] predicate Optional filtering predicate for `member_cref_t`.
1868
1924
  * @return Smart object referencing temporary memory. Valid until next `search()` or `add()`.
1869
1925
  */
1870
- search_result_t search(vector_view_t query, std::size_t wanted, search_config_t config = {}) const noexcept {
1926
+ template <typename predicate_at = dummy_predicate_t>
1927
+ search_result_t search( //
1928
+ vector_view_t query, std::size_t wanted, search_config_t config = {},
1929
+ predicate_at&& predicate = dummy_predicate_t{}) const noexcept {
1871
1930
 
1872
1931
  context_t& context = contexts_[config.thread];
1873
1932
  top_candidates_t& top = context.top_candidates;
@@ -1882,7 +1941,7 @@ class index_gt {
1882
1941
  if (config.exact) {
1883
1942
  if (!top.reserve(wanted))
1884
1943
  return result.failed("Out of memory!");
1885
- search_exact_(query, wanted, context);
1944
+ search_exact_(query, wanted, context, std::forward<predicate_at>(predicate));
1886
1945
  } else {
1887
1946
  next_candidates_t& next = context.next_candidates;
1888
1947
  std::size_t expansion = (std::max)(config.expansion, wanted);
@@ -1893,7 +1952,7 @@ class index_gt {
1893
1952
 
1894
1953
  id_t closest_id = search_for_one_(entry_id_, query, max_level_, 0, context);
1895
1954
  // For bottom layer we need a more optimized procedure
1896
- if (!search_to_find_in_base_(closest_id, query, expansion, context))
1955
+ if (!search_to_find_in_base_(closest_id, query, expansion, context, std::forward<predicate_at>(predicate)))
1897
1956
  return result.failed("Out of memory!");
1898
1957
  }
1899
1958
 
@@ -1907,9 +1966,10 @@ class index_gt {
1907
1966
  return result;
1908
1967
  }
1909
1968
 
1969
+ template <typename predicate_at = dummy_predicate_t>
1910
1970
  search_result_t search_around( //
1911
1971
  id_t hint, vector_view_t query, std::size_t wanted, //
1912
- search_config_t config = {}) const noexcept {
1972
+ search_config_t config = {}, predicate_at&& predicate = dummy_predicate_t{}) const noexcept {
1913
1973
 
1914
1974
  context_t& context = contexts_[config.thread];
1915
1975
  top_candidates_t& top = context.top_candidates;
@@ -1929,7 +1989,7 @@ class index_gt {
1929
1989
  result.measurements = context.measurements_count;
1930
1990
  result.cycles = context.iteration_cycles;
1931
1991
 
1932
- search_to_find_in_base_(hint, query, expansion, context);
1992
+ search_to_find_in_base_(hint, query, expansion, context, std::forward<predicate_at>(predicate));
1933
1993
  top.sort_ascending();
1934
1994
  top.shrink(wanted);
1935
1995
 
@@ -1940,6 +2000,10 @@ class index_gt {
1940
2000
  return result;
1941
2001
  }
1942
2002
 
2003
+ #pragma endregion
2004
+
2005
+ #pragma region Metadata
2006
+
1943
2007
  struct stats_t {
1944
2008
  std::size_t nodes;
1945
2009
  std::size_t edges;
@@ -1954,7 +2018,7 @@ class index_gt {
1954
2018
  node_t node = node_with_id_(i);
1955
2019
  std::size_t max_edges = node.level() * config_.connectivity + base_level_multiple_() * config_.connectivity;
1956
2020
  std::size_t edges = 0;
1957
- for (level_t level = 0; level != node.level(); ++level)
2021
+ for (level_t level = 0; level <= node.level(); ++level)
1958
2022
  edges += neighbors_(node, level).size();
1959
2023
 
1960
2024
  result.allocated_bytes += node_bytes_(node);
@@ -2003,6 +2067,8 @@ class index_gt {
2003
2067
  return total;
2004
2068
  }
2005
2069
 
2070
+ std::size_t memory_usage_per_node(dim_t dim, level_t level) const noexcept { return node_bytes_(dim, level); }
2071
+
2006
2072
  void change_metric(metric_t const& m) noexcept {
2007
2073
  metric_ = m;
2008
2074
  for (std::size_t i = 0; i != limits_.threads(); ++i)
@@ -2281,7 +2347,6 @@ class index_gt {
2281
2347
  std::size_t node_vector_bytes = dim * sizeof(scalar_t);
2282
2348
  nodes_[i] = node_t{tape, (scalar_t*)(tape + node_bytes - node_vector_bytes)};
2283
2349
  progress += node_bytes;
2284
- max_level_ = (std::max)(max_level_, level);
2285
2350
  }
2286
2351
 
2287
2352
  return {};
@@ -2392,7 +2457,6 @@ class index_gt {
2392
2457
 
2393
2458
  node_t new_node = node_with_id_(new_id);
2394
2459
  top_candidates_t& top = context.top_candidates;
2395
- std::size_t const connectivity_max = level ? config_.connectivity : pre_.connectivity_max_base;
2396
2460
 
2397
2461
  // Outgoing links from `new_id`:
2398
2462
  neighbors_ref_t new_neighbors = neighbors_(new_node, level);
@@ -2407,7 +2471,17 @@ class index_gt {
2407
2471
  }
2408
2472
  }
2409
2473
 
2474
+ return new_neighbors[0];
2475
+ }
2476
+
2477
+ void reconnect_neighbor_nodes_(id_t new_id, level_t level, context_t& context) usearch_noexcept_m {
2478
+
2479
+ node_t new_node = node_with_id_(new_id);
2480
+ top_candidates_t& top = context.top_candidates;
2481
+ neighbors_ref_t new_neighbors = neighbors_(new_node, level);
2482
+
2410
2483
  // Reverse links from the neighbors:
2484
+ std::size_t const connectivity_max = level ? config_.connectivity : pre_.connectivity_max_base;
2411
2485
  for (id_t close_id : new_neighbors) {
2412
2486
  node_t close_node = node_with_id_(close_id);
2413
2487
  node_lock_t close_lock = node_lock_(close_id);
@@ -2437,8 +2511,6 @@ class index_gt {
2437
2511
  for (std::size_t idx = 0; idx != top_view.size(); idx++)
2438
2512
  close_header.push_back(top_view[idx].id);
2439
2513
  }
2440
-
2441
- return new_neighbors[0];
2442
2514
  }
2443
2515
 
2444
2516
  level_t choose_random_level_(std::default_random_engine& level_generator) const noexcept {
@@ -2534,8 +2606,10 @@ class index_gt {
2534
2606
  * Doesn't lock any nodes, assuming read-only simultaneous access.
2535
2607
  * @return `true` if procedure succeeded, `false` if run out of memory.
2536
2608
  */
2609
+ template <typename predicate_at>
2537
2610
  bool search_to_find_in_base_( //
2538
- id_t start_id, vector_view_t query, std::size_t expansion, context_t& context) const noexcept {
2611
+ id_t start_id, vector_view_t query, std::size_t expansion, context_t& context,
2612
+ predicate_at&& predicate) const noexcept {
2539
2613
 
2540
2614
  visits_bitset_t& visits = context.visits;
2541
2615
  next_candidates_t& next = context.next_candidates; // pop min, push
@@ -2569,14 +2643,19 @@ class index_gt {
2569
2643
  continue;
2570
2644
 
2571
2645
  visits.set(successor_id);
2572
- distance_t successor_dist = context.measure(query, node_with_id_(successor_id));
2646
+ node_t successor = node_with_id_(successor_id);
2647
+ distance_t successor_dist = context.measure(query, successor);
2573
2648
 
2574
2649
  if (top.size() < top_limit || successor_dist < radius) {
2575
2650
  // This can substantially grow our priority queue:
2576
2651
  next.insert({-successor_dist, successor_id});
2577
- // This will automatically evict poor matches:
2578
- top.insert({successor_dist, successor_id}, top_limit);
2579
- radius = top.top().distance;
2652
+ if (predicate( //
2653
+ match_t{member_cref_t{successor.label(), successor.vector_view(), successor_id},
2654
+ successor_dist})) {
2655
+ // This will automatically evict poor matches:
2656
+ top.insert({successor_dist, successor_id}, top_limit);
2657
+ radius = top.top().distance;
2658
+ }
2580
2659
  }
2581
2660
  }
2582
2661
  }
@@ -2587,12 +2666,21 @@ class index_gt {
2587
2666
  /**
2588
2667
  * @brief Iterates through all managed vectors, without actually touching the index.
2589
2668
  */
2590
- void search_exact_(vector_view_t query, std::size_t count, context_t& context) const noexcept {
2669
+ template <typename predicate_at>
2670
+ void search_exact_( //
2671
+ vector_view_t query, std::size_t count, context_t& context, //
2672
+ predicate_at&& predicate) const noexcept {
2673
+
2591
2674
  top_candidates_t& top = context.top_candidates;
2592
2675
  top.clear();
2593
2676
  top.reserve(count);
2594
- for (std::size_t i = 0; i != size(); ++i)
2595
- top.insert({context.measure(query, node_with_id_(i)), static_cast<id_t>(i)}, count);
2677
+ for (std::size_t i = 0; i != size(); ++i) {
2678
+ id_t id = static_cast<id_t>(i);
2679
+ node_t node = node_with_id_(i);
2680
+ distance_t distance = context.measure(query, node);
2681
+ if (predicate(match_t{member_cref_t{node.label(), node.vector_view(), id}, distance}))
2682
+ top.insert(candidate_t{distance, id}, count);
2683
+ }
2596
2684
  }
2597
2685
 
2598
2686
  void prefetch_neighbors_(neighbors_ref_t, visits_bitset_t const&) const noexcept {}
@@ -652,7 +652,7 @@ class index_punned_dense_gt {
652
652
  }
653
653
  };
654
654
 
655
- using punned_small_t = index_punned_dense_gt<std::int64_t, std::uint32_t>;
655
+ using punned_small_t = index_punned_dense_gt<std::uint64_t, std::uint32_t>;
656
656
  using punned_big_t = index_punned_dense_gt<uuid_t, uint40_t>;
657
657
 
658
658
  } // namespace usearch
@@ -295,6 +295,8 @@ class f8_bits_t {
295
295
  inline explicit operator std::int32_t() const noexcept { return int8_; }
296
296
  inline explicit operator std::int64_t() const noexcept { return int8_; }
297
297
 
298
+ inline f8_bits_t(f16_t v)
299
+ : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
298
300
  inline f8_bits_t(float v)
299
301
  : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
300
302
  inline f8_bits_t(double v)
@@ -9,14 +9,16 @@
9
9
  using namespace unum::usearch;
10
10
  using namespace unum;
11
11
 
12
- using label_t = jint;
13
- using span_t = span_gt<float>;
12
+ using metric_t = index_punned_dense_metric_t;
14
13
  using distance_t = punned_distance_t;
15
- using punned_t = index_punned_dense_gt<label_t>;
14
+ using index_t = punned_small_t;
16
15
 
17
- using add_result_t = typename punned_t::add_result_t;
18
- using search_result_t = typename punned_t::search_result_t;
19
- using serialization_result_t = typename punned_t::serialization_result_t;
16
+ using add_result_t = typename index_t::add_result_t;
17
+ using search_result_t = typename index_t::search_result_t;
18
+ using serialization_result_t = typename index_t::serialization_result_t;
19
+ using label_t = typename index_t::label_t;
20
+ using id_t = typename index_t::id_t;
21
+ using vector_view_t = unum::usearch::span_gt<float>;
20
22
 
21
23
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1create( //
22
24
  JNIEnv* env, jclass, //
@@ -41,14 +43,14 @@ JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1create( //
41
43
 
42
44
  scalar_kind_t accuracy = scalar_kind_from_name(accuracy_cstr, accuracy_length);
43
45
  metric_kind_t metric_kind = metric_from_name(metric_cstr, metric_length);
44
- punned_t index = punned_t::make(static_cast<std::size_t>(dimensions), metric_kind, config, accuracy,
45
- expansion_add, expansion_search);
46
+ index_t index = index_t::make( //
47
+ static_cast<std::size_t>(dimensions), metric_kind, config, accuracy, expansion_add, expansion_search);
46
48
  if (!index.reserve(static_cast<std::size_t>(capacity))) {
47
49
  jclass jc = (*env).FindClass("java/lang/Error");
48
50
  if (jc)
49
51
  (*env).ThrowNew(jc, "Failed to reserve desired capacity!");
50
52
  } else {
51
- punned_t* result_ptr = new punned_t(std::move(index));
53
+ index_t* result_ptr = new index_t(std::move(index));
52
54
  std::memcpy(&result, &result_ptr, sizeof(jlong));
53
55
  }
54
56
 
@@ -65,7 +67,7 @@ JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1create( //
65
67
 
66
68
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1save(JNIEnv* env, jclass, jlong c_ptr, jstring path) {
67
69
  char const* path_cstr = (*env).GetStringUTFChars(path, 0);
68
- if (!reinterpret_cast<punned_t*>(c_ptr)->save(path_cstr)) {
70
+ if (!reinterpret_cast<index_t*>(c_ptr)->save(path_cstr)) {
69
71
  jclass jc = (*env).FindClass("java/lang/Error");
70
72
  if (jc)
71
73
  (*env).ThrowNew(jc, "Failed to dump vector index to path!");
@@ -75,7 +77,7 @@ JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1save(JNIEnv* env, jclass
75
77
 
76
78
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1load(JNIEnv* env, jclass, jlong c_ptr, jstring path) {
77
79
  char const* path_cstr = (*env).GetStringUTFChars(path, 0);
78
- if (!reinterpret_cast<punned_t*>(c_ptr)->load(path_cstr)) {
80
+ if (!reinterpret_cast<index_t*>(c_ptr)->load(path_cstr)) {
79
81
  jclass jc = (*env).FindClass("java/lang/Error");
80
82
  if (jc)
81
83
  (*env).ThrowNew(jc, "Failed to load vector index from path!");
@@ -85,7 +87,7 @@ JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1load(JNIEnv* env, jclass
85
87
 
86
88
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1view(JNIEnv* env, jclass, jlong c_ptr, jstring path) {
87
89
  char const* path_cstr = (*env).GetStringUTFChars(path, 0);
88
- if (!reinterpret_cast<punned_t*>(c_ptr)->view(path_cstr)) {
90
+ if (!reinterpret_cast<index_t*>(c_ptr)->view(path_cstr)) {
89
91
  jclass jc = (*env).FindClass("java/lang/Error");
90
92
  if (jc)
91
93
  (*env).ThrowNew(jc, "Failed to view vector index from path!");
@@ -94,27 +96,27 @@ JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1view(JNIEnv* env, jclass
94
96
  }
95
97
 
96
98
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1destroy(JNIEnv*, jclass, jlong c_ptr) {
97
- delete reinterpret_cast<punned_t*>(c_ptr);
99
+ delete reinterpret_cast<index_t*>(c_ptr);
98
100
  }
99
101
 
100
102
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1size(JNIEnv*, jclass, jlong c_ptr) {
101
- return reinterpret_cast<punned_t*>(c_ptr)->size();
103
+ return reinterpret_cast<index_t*>(c_ptr)->size();
102
104
  }
103
105
 
104
106
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1connectivity(JNIEnv*, jclass, jlong c_ptr) {
105
- return reinterpret_cast<punned_t*>(c_ptr)->connectivity();
107
+ return reinterpret_cast<index_t*>(c_ptr)->connectivity();
106
108
  }
107
109
 
108
110
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1dimensions(JNIEnv*, jclass, jlong c_ptr) {
109
- return reinterpret_cast<punned_t*>(c_ptr)->dimensions();
111
+ return reinterpret_cast<index_t*>(c_ptr)->dimensions();
110
112
  }
111
113
 
112
114
  JNIEXPORT jlong JNICALL Java_cloud_unum_usearch_Index_c_1capacity(JNIEnv*, jclass, jlong c_ptr) {
113
- return reinterpret_cast<punned_t*>(c_ptr)->capacity();
115
+ return reinterpret_cast<index_t*>(c_ptr)->capacity();
114
116
  }
115
117
 
116
118
  JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1reserve(JNIEnv* env, jclass, jlong c_ptr, jlong capacity) {
117
- if (!reinterpret_cast<punned_t*>(c_ptr)->reserve(static_cast<std::size_t>(capacity))) {
119
+ if (!reinterpret_cast<index_t*>(c_ptr)->reserve(static_cast<std::size_t>(capacity))) {
118
120
  jclass jc = (*env).FindClass("java/lang/Error");
119
121
  if (jc)
120
122
  (*env).ThrowNew(jc, "Failed to grow vector index!");
@@ -126,9 +128,9 @@ JNIEXPORT void JNICALL Java_cloud_unum_usearch_Index_c_1add( //
126
128
 
127
129
  jfloat* vector_data = (*env).GetFloatArrayElements(vector, 0);
128
130
  jsize vector_dims = (*env).GetArrayLength(vector);
129
- span_t vector_span = span_t{vector_data, static_cast<std::size_t>(vector_dims)};
131
+ vector_view_t vector_span = vector_view_t{vector_data, static_cast<std::size_t>(vector_dims)};
130
132
 
131
- if (!reinterpret_cast<punned_t*>(c_ptr)->add(label, vector_span)) {
133
+ if (!reinterpret_cast<index_t*>(c_ptr)->add(static_cast<label_t>(label), vector_span)) {
132
134
  jclass jc = (*env).FindClass("java/lang/Error");
133
135
  if (jc)
134
136
  (*env).ThrowNew(jc, "Failed to insert a new point in vector index!");
@@ -150,10 +152,10 @@ JNIEXPORT jintArray JNICALL Java_cloud_unum_usearch_Index_c_1search( //
150
152
 
151
153
  jfloat* vector_data = (*env).GetFloatArrayElements(vector, 0);
152
154
  jsize vector_dims = (*env).GetArrayLength(vector);
153
- span_t vector_span = span_t{vector_data, static_cast<std::size_t>(vector_dims)};
154
- search_result_t result = reinterpret_cast<punned_t*>(c_ptr)->search(vector_span, static_cast<std::size_t>(wanted));
155
+ vector_view_t vector_span = vector_view_t{vector_data, static_cast<std::size_t>(vector_dims)};
156
+ search_result_t result = reinterpret_cast<index_t*>(c_ptr)->search(vector_span, static_cast<std::size_t>(wanted));
155
157
  if (result) {
156
- std::size_t found = result.dump_to(matches_data, NULL);
158
+ std::size_t found = result.dump_to(reinterpret_cast<label_t*>(matches_data), NULL);
157
159
  (*env).SetIntArrayRegion(matches, 0, found, matches_data);
158
160
  } else {
159
161
  jclass jc = (*env).FindClass("java/lang/Error");
@@ -9,14 +9,9 @@
9
9
  * @see NodeJS docs: https://nodejs.org/api/addons.html#hello-world
10
10
  *
11
11
  */
12
-
13
- #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
14
- #define _USE_MATH_DEFINES
15
- #define NAPI_CPP_EXCEPTIONS
16
- #endif
17
-
18
12
  #include <new> // `std::bad_alloc`
19
13
 
14
+ #define NAPI_CPP_EXCEPTIONS
20
15
  #include <napi.h>
21
16
  #include <node_api.h>
22
17
 
@@ -25,9 +20,13 @@
25
20
  using namespace unum::usearch;
26
21
  using namespace unum;
27
22
 
28
- using label_t = std::uint32_t;
23
+ using metric_t = index_punned_dense_metric_t;
29
24
  using distance_t = punned_distance_t;
30
- using punned_t = index_punned_dense_gt<label_t>;
25
+ using index_t = punned_small_t;
26
+ using add_result_t = typename index_t::add_result_t;
27
+ using search_result_t = typename index_t::search_result_t;
28
+ using label_t = typename index_t::label_t;
29
+ using id_t = typename index_t::id_t;
31
30
 
32
31
  class Index : public Napi::ObjectWrap<Index> {
33
32
  public:
@@ -47,7 +46,7 @@ class Index : public Napi::ObjectWrap<Index> {
47
46
  void Add(Napi::CallbackInfo const& ctx);
48
47
  Napi::Value Search(Napi::CallbackInfo const& ctx);
49
48
 
50
- std::unique_ptr<punned_t> native_;
49
+ std::unique_ptr<index_t> native_;
51
50
  };
52
51
 
53
52
  Napi::Object Index::Init(Napi::Env env, Napi::Object exports) {
@@ -124,7 +123,7 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
124
123
  }
125
124
 
126
125
  native_.reset(
127
- new punned_t(punned_t::make(dimensions, metric_kind, config, accuracy, expansion_add, expansion_search)));
126
+ new index_t(index_t::make(dimensions, metric_kind, config, accuracy, expansion_add, expansion_search)));
128
127
  native_->reserve(limits);
129
128
  }
130
129
 
@@ -235,7 +234,7 @@ Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
235
234
  return {};
236
235
  }
237
236
 
238
- Napi::Uint32Array matches_js = Napi::Uint32Array::New(env, wanted);
237
+ Napi::TypedArrayOf<label_t> matches_js = Napi::TypedArrayOf<label_t>::New(env, wanted);
239
238
  Napi::Float32Array distances_js = Napi::Float32Array::New(env, wanted);
240
239
  try {
241
240
  std::size_t count = native_->search(vector, wanted).dump_to(matches_js.Data(), distances_js.Data());