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.
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/c/lib.cpp +1 -2
- package/c/test.c +238 -126
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +3 -3
- package/cpp/test.cpp +12 -12
- package/include/usearch/index.hpp +137 -49
- package/include/usearch/index_punned_dense.hpp +1 -1
- package/include/usearch/index_punned_helpers.hpp +2 -0
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +25 -23
- package/javascript/lib.cpp +10 -11
- package/javascript/test.js +1 -1
- package/objc/USearchObjective.mm +15 -12
- package/objc/include/USearchObjective.h +3 -3
- package/package.json +2 -2
- package/python/lib.cpp +43 -48
- package/python/usearch/index.py +1 -1
- package/rust/lib.cpp +13 -13
- package/rust/lib.hpp +10 -6
- package/rust/lib.rs +2 -2
- package/swift/Index+Sugar.swift +13 -9
- package/wolfram/lib.cpp +26 -19
|
@@ -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 {
|
|
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(
|
|
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))
|
|
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
|
|
976
|
-
inline uint40_t(std::uint64_t n) noexcept { std::memcpy(octets, (char*)&n
|
|
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
|
|
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
|
|
987
|
-
std::uint32_t& tail = *reinterpret_cast<std::uint32_t*>(octets
|
|
988
|
-
octets[
|
|
989
|
-
tail +=
|
|
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
|
|
1026
|
+
std::memcpy((char*)&result, octets, 5);
|
|
996
1027
|
return result;
|
|
997
1028
|
}
|
|
998
1029
|
|
|
999
|
-
inline uint40_t& operator++() noexcept { return *this +=
|
|
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 +=
|
|
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 {
|
|
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
|
|
1453
|
-
dim_t dim() const noexcept { return
|
|
1454
|
-
level_t level() const noexcept { return
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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::
|
|
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
|
|
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
|
|
14
|
+
using index_t = punned_small_t;
|
|
16
15
|
|
|
17
|
-
using add_result_t = typename
|
|
18
|
-
using search_result_t = typename
|
|
19
|
-
using serialization_result_t = typename
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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
|
-
|
|
131
|
+
vector_view_t vector_span = vector_view_t{vector_data, static_cast<std::size_t>(vector_dims)};
|
|
130
132
|
|
|
131
|
-
if (!reinterpret_cast<
|
|
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
|
-
|
|
154
|
-
search_result_t result = reinterpret_cast<
|
|
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");
|
package/javascript/lib.cpp
CHANGED
|
@@ -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
|
|
23
|
+
using metric_t = index_punned_dense_metric_t;
|
|
29
24
|
using distance_t = punned_distance_t;
|
|
30
|
-
using
|
|
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<
|
|
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
|
|
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::
|
|
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());
|