usearch 0.21.0 → 0.22.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.
- package/.github/workflows/prerelease.yml +19 -0
- package/.github/workflows/release.yml +32 -1
- package/.github/workflows/update_version.sh +2 -1
- package/CMakeLists.txt +4 -0
- package/Cargo.toml +1 -1
- package/README.md +117 -626
- package/c/lib.cpp +19 -15
- package/c/usearch.h +26 -23
- package/cmake/emscripten.cmake +8 -0
- package/conanfile.py +1 -1
- package/cpp/README.md +88 -0
- package/cpp/bench.cpp +32 -41
- package/cpp/test.cpp +13 -7
- package/docs/golang/index.rst +6 -0
- package/docs/index.rst +6 -2
- package/docs/javascript/index.rst +1 -1
- package/docs/python/index.rst +1 -1
- package/docs/python/reference.rst +9 -2
- package/docs/rust/index.rst +6 -0
- package/docs/swift/index.rst +6 -0
- package/golang/README.md +28 -0
- package/include/usearch/index.hpp +204 -78
- package/include/usearch/index_punned_dense.hpp +239 -85
- package/java/README.md +22 -0
- package/javascript/README.md +36 -0
- package/javascript/lib.cpp +1 -1
- package/package.json +1 -1
- package/python/README.md +283 -0
- package/python/lib.cpp +61 -11
- package/python/scripts/join.py +7 -3
- package/python/scripts/test.py +28 -243
- package/python/scripts/test_jit.py +244 -0
- package/python/usearch/index.py +97 -25
- package/rust/README.md +67 -0
- package/swift/README.md +20 -0
- package/wasm/CMakeLists.txt +15 -0
- package/wasm/README.md +13 -0
- package/wasm/lib.cpp +5 -0
- package/wasmer.toml +8 -0
- package/wolfram/README.md +13 -0
|
@@ -41,6 +41,13 @@
|
|
|
41
41
|
#define USEARCH_DEFINED_ARM
|
|
42
42
|
#endif
|
|
43
43
|
|
|
44
|
+
// Inferring hardware bitness: 32 vs 64
|
|
45
|
+
#if __WORDSIZE == 64
|
|
46
|
+
#define USEARCH_64BIT_ENV
|
|
47
|
+
#else
|
|
48
|
+
#define USEARCH_32BIT_ENV
|
|
49
|
+
#endif
|
|
50
|
+
|
|
44
51
|
#if !defined(USEARCH_USE_OPENMP)
|
|
45
52
|
#define USEARCH_USE_OPENMP 0
|
|
46
53
|
#endif
|
|
@@ -173,19 +180,21 @@ inline std::size_t ceil2(std::size_t v) noexcept {
|
|
|
173
180
|
v |= v >> 4;
|
|
174
181
|
v |= v >> 8;
|
|
175
182
|
v |= v >> 16;
|
|
183
|
+
#ifdef USEARCH_64BIT_ENV
|
|
176
184
|
v |= v >> 32;
|
|
185
|
+
#endif
|
|
177
186
|
v++;
|
|
178
187
|
return v;
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
template <typename at> void misaligned_store(void* ptr, at v) noexcept {
|
|
182
|
-
static_assert(!std::is_reference<at>::value);
|
|
191
|
+
static_assert(!std::is_reference<at>::value, "Can't store a reference");
|
|
183
192
|
std::memcpy(ptr, &v, sizeof(at));
|
|
184
193
|
}
|
|
185
194
|
|
|
186
195
|
/// @brief Simply dereferencing misaligned pointers can be dangerous.
|
|
187
196
|
template <typename at> at misaligned_load(void* ptr) noexcept {
|
|
188
|
-
static_assert(!std::is_reference<at>::value);
|
|
197
|
+
static_assert(!std::is_reference<at>::value, "Can't load a reference");
|
|
189
198
|
at v;
|
|
190
199
|
std::memcpy(&v, ptr, sizeof(at));
|
|
191
200
|
return v;
|
|
@@ -984,12 +993,19 @@ class sorted_buffer_gt {
|
|
|
984
993
|
|
|
985
994
|
/**
|
|
986
995
|
* @brief Five-byte integer type to address node clouds with over 4B entries.
|
|
996
|
+
*
|
|
997
|
+
* @note Avoid usage in 32bit environment
|
|
987
998
|
*/
|
|
988
999
|
class usearch_pack_m uint40_t {
|
|
989
1000
|
unsigned char octets[5];
|
|
990
1001
|
|
|
1002
|
+
inline uint40_t& broadcast(unsigned char c) {
|
|
1003
|
+
std::memset(octets, c, 5);
|
|
1004
|
+
return *this;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
991
1007
|
public:
|
|
992
|
-
inline uint40_t() noexcept {
|
|
1008
|
+
inline uint40_t() noexcept { broadcast(0); }
|
|
993
1009
|
inline uint40_t(std::uint32_t n) noexcept { std::memcpy(octets, (char*)&n, 4); }
|
|
994
1010
|
inline uint40_t(std::uint64_t n) noexcept { std::memcpy(octets, (char*)&n, 5); }
|
|
995
1011
|
#if defined(USEARCH_DEFINED_CLANG) && defined(USEARCH_DEFINED_APPLE)
|
|
@@ -1026,7 +1042,11 @@ class usearch_pack_m uint40_t {
|
|
|
1026
1042
|
|
|
1027
1043
|
inline operator std::size_t() const noexcept {
|
|
1028
1044
|
std::size_t result = 0;
|
|
1029
|
-
|
|
1045
|
+
#ifdef USEARCH_64BIT_ENV
|
|
1046
|
+
std::memcpy((char*)&result + 3, octets, 5);
|
|
1047
|
+
#else
|
|
1048
|
+
std::memcpy((char*)&result, octets + 1, 4);
|
|
1049
|
+
#endif
|
|
1030
1050
|
return result;
|
|
1031
1051
|
}
|
|
1032
1052
|
|
|
@@ -1037,6 +1057,9 @@ class usearch_pack_m uint40_t {
|
|
|
1037
1057
|
*this += 1u;
|
|
1038
1058
|
return old;
|
|
1039
1059
|
}
|
|
1060
|
+
|
|
1061
|
+
inline static uint40_t max() noexcept { return uint40_t{}.broadcast(0xFF); }
|
|
1062
|
+
inline static uint40_t min() noexcept { return uint40_t{}.broadcast(0); }
|
|
1040
1063
|
};
|
|
1041
1064
|
|
|
1042
1065
|
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
@@ -1057,23 +1080,38 @@ class ring_gt {
|
|
|
1057
1080
|
using value_type = element_t;
|
|
1058
1081
|
|
|
1059
1082
|
private:
|
|
1060
|
-
element_t* elements_;
|
|
1061
|
-
std::size_t capacity_;
|
|
1062
|
-
std::size_t head_;
|
|
1063
|
-
std::size_t tail_;
|
|
1064
|
-
bool empty_;
|
|
1065
|
-
allocator_t allocator_;
|
|
1083
|
+
element_t* elements_{};
|
|
1084
|
+
std::size_t capacity_{};
|
|
1085
|
+
std::size_t head_{};
|
|
1086
|
+
std::size_t tail_{};
|
|
1087
|
+
bool empty_{true};
|
|
1088
|
+
allocator_t allocator_{};
|
|
1066
1089
|
|
|
1067
1090
|
public:
|
|
1068
|
-
explicit ring_gt(allocator_t const& alloc = allocator_t()) noexcept
|
|
1069
|
-
: elements_(nullptr), capacity_(0), head_(0), tail_(0), empty_(true), allocator_(alloc) {}
|
|
1091
|
+
explicit ring_gt(allocator_t const& alloc = allocator_t()) noexcept : allocator_(alloc) {}
|
|
1070
1092
|
|
|
1071
1093
|
ring_gt(ring_gt const&) = delete;
|
|
1072
1094
|
ring_gt& operator=(ring_gt const&) = delete;
|
|
1073
1095
|
|
|
1096
|
+
ring_gt(ring_gt&& other) noexcept { swap(other); }
|
|
1097
|
+
ring_gt& operator=(ring_gt&& other) noexcept {
|
|
1098
|
+
swap(other);
|
|
1099
|
+
return *this;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
void swap(ring_gt& other) noexcept {
|
|
1103
|
+
std::swap(elements_, other.elements_);
|
|
1104
|
+
std::swap(capacity_, other.capacity_);
|
|
1105
|
+
std::swap(head_, other.head_);
|
|
1106
|
+
std::swap(tail_, other.tail_);
|
|
1107
|
+
std::swap(empty_, other.empty_);
|
|
1108
|
+
std::swap(allocator_, other.allocator_);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1074
1111
|
~ring_gt() noexcept { reset(); }
|
|
1075
1112
|
|
|
1076
1113
|
bool empty() const noexcept { return empty_; }
|
|
1114
|
+
size_t capacity() const noexcept { return capacity_; }
|
|
1077
1115
|
size_t size() const noexcept {
|
|
1078
1116
|
if (empty_)
|
|
1079
1117
|
return 0;
|
|
@@ -1083,6 +1121,12 @@ class ring_gt {
|
|
|
1083
1121
|
return capacity_ - (tail_ - head_);
|
|
1084
1122
|
}
|
|
1085
1123
|
|
|
1124
|
+
void clear() noexcept {
|
|
1125
|
+
head_ = 0;
|
|
1126
|
+
tail_ = 0;
|
|
1127
|
+
empty_ = true;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1086
1130
|
void reset() noexcept {
|
|
1087
1131
|
if (elements_)
|
|
1088
1132
|
allocator_.deallocate(elements_, capacity_);
|
|
@@ -1093,13 +1137,24 @@ class ring_gt {
|
|
|
1093
1137
|
empty_ = true;
|
|
1094
1138
|
}
|
|
1095
1139
|
|
|
1096
|
-
bool
|
|
1140
|
+
bool reserve(std::size_t n) noexcept {
|
|
1141
|
+
if (n < size())
|
|
1142
|
+
return false; // prevent data loss
|
|
1143
|
+
n = (std::max<std::size_t>)(n, 64u);
|
|
1097
1144
|
element_t* elements = allocator_.allocate(n);
|
|
1098
1145
|
if (!elements)
|
|
1099
1146
|
return false;
|
|
1147
|
+
|
|
1148
|
+
std::size_t i = 0;
|
|
1149
|
+
while (try_pop(elements[i]))
|
|
1150
|
+
i++;
|
|
1151
|
+
|
|
1100
1152
|
reset();
|
|
1101
1153
|
elements_ = elements;
|
|
1102
1154
|
capacity_ = n;
|
|
1155
|
+
head_ = i;
|
|
1156
|
+
tail_ = 0;
|
|
1157
|
+
empty_ = (i == 0);
|
|
1103
1158
|
return true;
|
|
1104
1159
|
}
|
|
1105
1160
|
|
|
@@ -1126,6 +1181,8 @@ class ring_gt {
|
|
|
1126
1181
|
empty_ = head_ == tail_;
|
|
1127
1182
|
return true;
|
|
1128
1183
|
}
|
|
1184
|
+
|
|
1185
|
+
element_t const& operator[](std::size_t i) const noexcept { return elements_[(tail_ + i) % capacity_]; }
|
|
1129
1186
|
};
|
|
1130
1187
|
|
|
1131
1188
|
/// @brief Number of neighbors per graph node.
|
|
@@ -1173,11 +1230,11 @@ struct index_config_t {
|
|
|
1173
1230
|
};
|
|
1174
1231
|
|
|
1175
1232
|
struct index_limits_t {
|
|
1176
|
-
std::size_t
|
|
1233
|
+
std::size_t members = 0;
|
|
1177
1234
|
std::size_t threads_add = std::thread::hardware_concurrency();
|
|
1178
1235
|
std::size_t threads_search = std::thread::hardware_concurrency();
|
|
1179
1236
|
|
|
1180
|
-
index_limits_t(std::size_t n, std::size_t t) noexcept :
|
|
1237
|
+
index_limits_t(std::size_t n, std::size_t t) noexcept : members(n), threads_add(t), threads_search(t) {}
|
|
1181
1238
|
index_limits_t(std::size_t n = 0) noexcept : index_limits_t(n, std::thread::hardware_concurrency()) {}
|
|
1182
1239
|
std::size_t threads() const noexcept { return (std::max)(threads_add, threads_search); }
|
|
1183
1240
|
std::size_t concurrency() const noexcept { return (std::min)(threads_add, threads_search); }
|
|
@@ -1457,7 +1514,7 @@ struct dummy_label_to_label_mapping_t {
|
|
|
1457
1514
|
*
|
|
1458
1515
|
*/
|
|
1459
1516
|
template <typename metric_at = ip_gt<float>, //
|
|
1460
|
-
typename label_at = std::
|
|
1517
|
+
typename label_at = std::int64_t, //
|
|
1461
1518
|
typename id_at = std::uint32_t, //
|
|
1462
1519
|
typename allocator_at = std::allocator<char>, //
|
|
1463
1520
|
typename point_allocator_at = allocator_at> //
|
|
@@ -1474,16 +1531,18 @@ class index_gt {
|
|
|
1474
1531
|
using vector_view_t = span_gt<scalar_t const>;
|
|
1475
1532
|
using distance_t = return_type_gt<metric_t, vector_view_t, vector_view_t>;
|
|
1476
1533
|
|
|
1477
|
-
struct
|
|
1478
|
-
|
|
1534
|
+
struct member_cref_t {
|
|
1535
|
+
label_t label;
|
|
1479
1536
|
vector_view_t vector;
|
|
1480
1537
|
id_t id;
|
|
1481
1538
|
};
|
|
1482
1539
|
|
|
1483
|
-
struct
|
|
1484
|
-
label_t label;
|
|
1540
|
+
struct member_ref_t {
|
|
1541
|
+
misaligned_ref_gt<label_t> label;
|
|
1485
1542
|
vector_view_t vector;
|
|
1486
1543
|
id_t id;
|
|
1544
|
+
|
|
1545
|
+
inline operator member_cref_t() const noexcept { return {label, vector, id}; }
|
|
1487
1546
|
};
|
|
1488
1547
|
|
|
1489
1548
|
template <typename ref_at, typename index_at> class member_iterator_gt {
|
|
@@ -1774,6 +1833,9 @@ class index_gt {
|
|
|
1774
1833
|
member_iterator_t begin() noexcept { return {this, 0}; }
|
|
1775
1834
|
member_iterator_t end() noexcept { return {this, size()}; }
|
|
1776
1835
|
|
|
1836
|
+
member_cref_t at(std::size_t i) const noexcept { return member_cref_t(node_with_id_(i)); }
|
|
1837
|
+
member_ref_t at(std::size_t i) noexcept { return member_ref_t(node_with_id_(i)); }
|
|
1838
|
+
|
|
1777
1839
|
#pragma region Adjusting Configuration
|
|
1778
1840
|
|
|
1779
1841
|
/**
|
|
@@ -1799,7 +1861,7 @@ class index_gt {
|
|
|
1799
1861
|
clear();
|
|
1800
1862
|
|
|
1801
1863
|
if (nodes_)
|
|
1802
|
-
node_allocator_t{}.deallocate(exchange(nodes_, nullptr), limits_.
|
|
1864
|
+
node_allocator_t{}.deallocate(exchange(nodes_, nullptr), limits_.members);
|
|
1803
1865
|
if (contexts_) {
|
|
1804
1866
|
for (std::size_t i = 0; i != limits_.threads(); ++i)
|
|
1805
1867
|
contexts_[i].~context_t();
|
|
@@ -1842,13 +1904,16 @@ class index_gt {
|
|
|
1842
1904
|
*/
|
|
1843
1905
|
bool reserve(index_limits_t limits) usearch_noexcept_m {
|
|
1844
1906
|
|
|
1845
|
-
|
|
1907
|
+
if (limits.threads_add <= limits_.threads_add //
|
|
1908
|
+
&& limits.threads_search <= limits_.threads_search //
|
|
1909
|
+
&& limits.members <= limits_.members)
|
|
1910
|
+
return true;
|
|
1846
1911
|
|
|
1847
|
-
if (!nodes_mutexes_.resize(limits.
|
|
1912
|
+
if (!nodes_mutexes_.resize(limits.members))
|
|
1848
1913
|
return false;
|
|
1849
1914
|
|
|
1850
1915
|
node_allocator_t node_allocator;
|
|
1851
|
-
node_t* new_nodes = node_allocator.allocate(limits.
|
|
1916
|
+
node_t* new_nodes = node_allocator.allocate(limits.members);
|
|
1852
1917
|
if (!new_nodes)
|
|
1853
1918
|
return false;
|
|
1854
1919
|
|
|
@@ -1856,17 +1921,17 @@ class index_gt {
|
|
|
1856
1921
|
context_allocator_t context_allocator;
|
|
1857
1922
|
context_t* new_contexts = context_allocator.allocate(limits_threads);
|
|
1858
1923
|
if (!new_contexts) {
|
|
1859
|
-
node_allocator.deallocate(new_nodes, limits.
|
|
1924
|
+
node_allocator.deallocate(new_nodes, limits.members);
|
|
1860
1925
|
return false;
|
|
1861
1926
|
}
|
|
1862
1927
|
for (std::size_t i = 0; i != limits_threads; ++i) {
|
|
1863
1928
|
context_t& context = new_contexts[i];
|
|
1864
1929
|
new (&context) context_t();
|
|
1865
1930
|
context.metric = metric_;
|
|
1866
|
-
if (!context.visits.resize(limits.
|
|
1931
|
+
if (!context.visits.resize(limits.members)) {
|
|
1867
1932
|
for (std::size_t j = 0; j != i; ++j)
|
|
1868
1933
|
context.visits.reset();
|
|
1869
|
-
node_allocator.deallocate(new_nodes, limits.
|
|
1934
|
+
node_allocator.deallocate(new_nodes, limits.members);
|
|
1870
1935
|
context_allocator.deallocate(new_contexts, limits_threads);
|
|
1871
1936
|
return false;
|
|
1872
1937
|
}
|
|
@@ -1886,13 +1951,12 @@ class index_gt {
|
|
|
1886
1951
|
|
|
1887
1952
|
// Move the nodes info, and deallocate previous buffers.
|
|
1888
1953
|
if (nodes_)
|
|
1889
|
-
std::memcpy(new_nodes, nodes_, sizeof(node_t) * size()),
|
|
1890
|
-
node_allocator.deallocate(nodes_, limits_.elements);
|
|
1954
|
+
std::memcpy(new_nodes, nodes_, sizeof(node_t) * size()), node_allocator.deallocate(nodes_, limits_.members);
|
|
1891
1955
|
if (contexts_)
|
|
1892
1956
|
context_allocator.deallocate(contexts_, limits_.threads());
|
|
1893
1957
|
|
|
1894
1958
|
limits_ = limits;
|
|
1895
|
-
capacity_ = limits.
|
|
1959
|
+
capacity_ = limits.members;
|
|
1896
1960
|
nodes_ = new_nodes;
|
|
1897
1961
|
contexts_ = new_contexts;
|
|
1898
1962
|
return true;
|
|
@@ -2053,16 +2117,7 @@ class index_gt {
|
|
|
2053
2117
|
result.measurements = context.measurements_count;
|
|
2054
2118
|
result.cycles = context.iteration_cycles;
|
|
2055
2119
|
|
|
2056
|
-
|
|
2057
|
-
id_t closest_id = search_for_one_(entry_id_copy, vector, max_level_copy, target_level, context);
|
|
2058
|
-
|
|
2059
|
-
// From `target_level` down perform proper extensive search
|
|
2060
|
-
for (level_t level = (std::min)(target_level, max_level_copy); level >= 0; --level) {
|
|
2061
|
-
// TODO: Handle out of memory conditions
|
|
2062
|
-
search_to_insert_(closest_id, vector, level, config.expansion, context);
|
|
2063
|
-
closest_id = connect_new_node_(new_id, level, context);
|
|
2064
|
-
reconnect_neighbor_nodes_(new_id, level, context);
|
|
2065
|
-
}
|
|
2120
|
+
connect_node_across_levels_(new_id, vector, entry_id_copy, max_level_copy, target_level, config, context);
|
|
2066
2121
|
|
|
2067
2122
|
// Normalize stats
|
|
2068
2123
|
result.measurements = context.measurements_count - result.measurements;
|
|
@@ -2076,6 +2131,51 @@ class index_gt {
|
|
|
2076
2131
|
return result;
|
|
2077
2132
|
}
|
|
2078
2133
|
|
|
2134
|
+
/**
|
|
2135
|
+
* @brief Update an existing entry, replacing a vector and a label. Thread-safe.
|
|
2136
|
+
*
|
|
2137
|
+
* @param[in] old_id Existing internal identifier for a node to be replaced.
|
|
2138
|
+
* @param[in] label External identifier/name/descriptor for the vector.
|
|
2139
|
+
* @param[in] vector Contiguous range of scalars forming a vector view.
|
|
2140
|
+
* @param[in] config Configuration options for this specific operation.
|
|
2141
|
+
*/
|
|
2142
|
+
add_result_t update(id_t old_id, label_t label, vector_view_t vector, add_config_t config = {}) usearch_noexcept_m {
|
|
2143
|
+
|
|
2144
|
+
usearch_assert_m(!is_immutable(), "Can't add to an immutable index");
|
|
2145
|
+
add_result_t result;
|
|
2146
|
+
|
|
2147
|
+
// Make sure we have enough local memory to perform this request
|
|
2148
|
+
context_t& context = contexts_[config.thread];
|
|
2149
|
+
top_candidates_t& top = context.top_candidates;
|
|
2150
|
+
next_candidates_t& next = context.next_candidates;
|
|
2151
|
+
top.clear();
|
|
2152
|
+
next.clear();
|
|
2153
|
+
|
|
2154
|
+
// The top list needs one more slot than the connectivity of the base level
|
|
2155
|
+
// for the heuristic, that tries to squeeze one more element into saturated list.
|
|
2156
|
+
std::size_t top_limit = (std::max)(base_level_multiple_() * config_.connectivity + 1, config.expansion);
|
|
2157
|
+
if (!top.reserve(top_limit))
|
|
2158
|
+
return result.failed("Out of memory!");
|
|
2159
|
+
if (!next.reserve(config.expansion))
|
|
2160
|
+
return result.failed("Out of memory!");
|
|
2161
|
+
|
|
2162
|
+
node_lock_t new_lock = node_lock_(old_id);
|
|
2163
|
+
node_t node = node_with_id_(old_id);
|
|
2164
|
+
|
|
2165
|
+
// Pull stats
|
|
2166
|
+
result.measurements = context.measurements_count;
|
|
2167
|
+
result.cycles = context.iteration_cycles;
|
|
2168
|
+
|
|
2169
|
+
connect_node_across_levels_(old_id, vector, entry_id_, max_level_, node.level(), config, context);
|
|
2170
|
+
node.label(label);
|
|
2171
|
+
|
|
2172
|
+
// Normalize stats
|
|
2173
|
+
result.measurements = context.measurements_count - result.measurements;
|
|
2174
|
+
result.cycles = context.iteration_cycles - result.cycles;
|
|
2175
|
+
|
|
2176
|
+
return result;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2079
2179
|
/**
|
|
2080
2180
|
* @brief Searches for the closest elements to the given ::query. Thread-safe.
|
|
2081
2181
|
*
|
|
@@ -2128,40 +2228,6 @@ class index_gt {
|
|
|
2128
2228
|
return result;
|
|
2129
2229
|
}
|
|
2130
2230
|
|
|
2131
|
-
template <typename predicate_at = dummy_predicate_t>
|
|
2132
|
-
search_result_t search_around( //
|
|
2133
|
-
id_t hint, vector_view_t query, std::size_t wanted, //
|
|
2134
|
-
search_config_t config = {}, predicate_at&& predicate = dummy_predicate_t{}) const noexcept {
|
|
2135
|
-
|
|
2136
|
-
context_t& context = contexts_[config.thread];
|
|
2137
|
-
top_candidates_t& top = context.top_candidates;
|
|
2138
|
-
next_candidates_t& next = context.next_candidates;
|
|
2139
|
-
search_result_t result{*this, top};
|
|
2140
|
-
|
|
2141
|
-
if (!size_)
|
|
2142
|
-
return result;
|
|
2143
|
-
|
|
2144
|
-
std::size_t expansion = (std::max)(config.expansion, wanted);
|
|
2145
|
-
if (!next.reserve(expansion))
|
|
2146
|
-
return result.failed("Out of memory!");
|
|
2147
|
-
if (!top.reserve(expansion))
|
|
2148
|
-
return result.failed("Out of memory!");
|
|
2149
|
-
|
|
2150
|
-
// Go down the level, tracking only the closest match
|
|
2151
|
-
result.measurements = context.measurements_count;
|
|
2152
|
-
result.cycles = context.iteration_cycles;
|
|
2153
|
-
|
|
2154
|
-
search_to_find_in_base_(hint, query, expansion, context, std::forward<predicate_at>(predicate));
|
|
2155
|
-
top.sort_ascending();
|
|
2156
|
-
top.shrink(wanted);
|
|
2157
|
-
|
|
2158
|
-
// Normalize stats
|
|
2159
|
-
result.measurements = context.measurements_count - result.measurements;
|
|
2160
|
-
result.cycles = context.iteration_cycles - result.cycles;
|
|
2161
|
-
result.count = top.size();
|
|
2162
|
-
return result;
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
2231
|
#pragma endregion
|
|
2166
2232
|
|
|
2167
2233
|
#pragma region Metadata
|
|
@@ -2222,7 +2288,7 @@ class index_gt {
|
|
|
2222
2288
|
}
|
|
2223
2289
|
|
|
2224
2290
|
// Temporary data-structures, proportional to the number of nodes:
|
|
2225
|
-
total += limits_.
|
|
2291
|
+
total += limits_.members * sizeof(node_t) + allocator_entry_bytes;
|
|
2226
2292
|
|
|
2227
2293
|
// Temporary data-structures, proportional to the number of threads:
|
|
2228
2294
|
total += limits_.threads() * sizeof(context_t) + allocator_entry_bytes * 3;
|
|
@@ -2377,7 +2443,7 @@ class index_gt {
|
|
|
2377
2443
|
pre_ = precompute_(config_);
|
|
2378
2444
|
|
|
2379
2445
|
index_limits_t limits;
|
|
2380
|
-
limits.
|
|
2446
|
+
limits.members = state.size;
|
|
2381
2447
|
if (!reserve(limits)) {
|
|
2382
2448
|
std::fclose(file);
|
|
2383
2449
|
return result.failed("Out of memory");
|
|
@@ -2497,7 +2563,7 @@ class index_gt {
|
|
|
2497
2563
|
pre_ = precompute_(config_);
|
|
2498
2564
|
|
|
2499
2565
|
index_limits_t limits;
|
|
2500
|
-
limits.
|
|
2566
|
+
limits.members = state.size;
|
|
2501
2567
|
limits.threads_add = 0;
|
|
2502
2568
|
if (!reserve(limits))
|
|
2503
2569
|
return result.failed("Out of memory!");
|
|
@@ -2545,6 +2611,11 @@ class index_gt {
|
|
|
2545
2611
|
* @brief Adapts the Male-Optimal Stable Marriage algorithm for unequal sets
|
|
2546
2612
|
* to perform fast one-to-one matching between two large collections
|
|
2547
2613
|
* of vectors, using approximate nearest neighbors search.
|
|
2614
|
+
*
|
|
2615
|
+
* @param[inout] first_to_second Container to map ::first labels to ::second.
|
|
2616
|
+
* @param[inout] second_to_first Container to map ::second labels to ::first.
|
|
2617
|
+
* @param[in] executor Thread-pool to execute the job in parallel.
|
|
2618
|
+
* @param[in] progress Callback to report the execution progress.
|
|
2548
2619
|
*/
|
|
2549
2620
|
template < //
|
|
2550
2621
|
typename first_to_second_at = dummy_label_to_label_mapping_t, //
|
|
@@ -2578,6 +2649,44 @@ class index_gt {
|
|
|
2578
2649
|
std::forward<progress_at>(progress));
|
|
2579
2650
|
}
|
|
2580
2651
|
|
|
2652
|
+
/**
|
|
2653
|
+
* @brief Scans the whole collection, removing the links leading towards
|
|
2654
|
+
* banned entries. This essentially isolates some nodes from the rest
|
|
2655
|
+
* of the graph, while keeping their outgoing links, in case the node
|
|
2656
|
+
* is structurally relevant and has a crucial role in the index.
|
|
2657
|
+
* It won't reclaim the memory.
|
|
2658
|
+
*
|
|
2659
|
+
* @param[in] allow_member Predicate to mark nodes for isolation.
|
|
2660
|
+
* @param[in] executor Thread-pool to execute the job in parallel.
|
|
2661
|
+
* @param[in] progress Callback to report the execution progress.
|
|
2662
|
+
*/
|
|
2663
|
+
template < //
|
|
2664
|
+
typename allow_member_at = dummy_predicate_t, //
|
|
2665
|
+
typename executor_at = dummy_executor_t, //
|
|
2666
|
+
typename progress_at = dummy_progress_t //
|
|
2667
|
+
>
|
|
2668
|
+
void isolate( //
|
|
2669
|
+
allow_member_at&& allow_member, //
|
|
2670
|
+
executor_at&& executor = executor_at{}, //
|
|
2671
|
+
progress_at&& progress = progress_at{}) noexcept {
|
|
2672
|
+
|
|
2673
|
+
// Erase all the incoming links
|
|
2674
|
+
executor.execute_bulk(size(), [&](std::size_t node_idx, std::size_t) {
|
|
2675
|
+
node_t node = node_with_id_(node_idx);
|
|
2676
|
+
for (level_t level = 0; level <= node.level(); ++level) {
|
|
2677
|
+
neighbors_ref_t neighbors = neighbors_(node, level);
|
|
2678
|
+
std::size_t old_size = neighbors.size();
|
|
2679
|
+
neighbors.clear();
|
|
2680
|
+
for (std::size_t i = 0; i != old_size; ++i) {
|
|
2681
|
+
id_t neighbor_id = neighbors[i];
|
|
2682
|
+
node_t neighbor = node_with_id_(neighbor_id);
|
|
2683
|
+
if (allow_member(member_cref_t{neighbor.label(), neighbor.vector_view(), neighbor_id}))
|
|
2684
|
+
neighbors.push_back(neighbor_id);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2581
2690
|
private:
|
|
2582
2691
|
template <typename first_to_second_at, typename second_to_first_at, typename executor_at, typename progress_at>
|
|
2583
2692
|
static join_result_t join_small_and_big_( //
|
|
@@ -2605,7 +2714,7 @@ class index_gt {
|
|
|
2605
2714
|
// free men will be added/pulled.
|
|
2606
2715
|
std::mutex free_men_mutex{};
|
|
2607
2716
|
ring_gt<id_t, ids_allocator_t> free_men;
|
|
2608
|
-
free_men.
|
|
2717
|
+
free_men.reserve(men.size());
|
|
2609
2718
|
for (std::size_t i = 0; i != men.size(); ++i)
|
|
2610
2719
|
free_men.push(static_cast<id_t>(i));
|
|
2611
2720
|
|
|
@@ -2872,6 +2981,23 @@ class index_gt {
|
|
|
2872
2981
|
return {nodes_mutexes_, idx};
|
|
2873
2982
|
}
|
|
2874
2983
|
|
|
2984
|
+
void connect_node_across_levels_( //
|
|
2985
|
+
id_t node_id, vector_view_t vector, //
|
|
2986
|
+
id_t entry_id, level_t max_level, level_t target_level, //
|
|
2987
|
+
add_config_t const& config, context_t& context) usearch_noexcept_m {
|
|
2988
|
+
|
|
2989
|
+
// Go down the level, tracking only the closest match
|
|
2990
|
+
id_t closest_id = search_for_one_(entry_id, vector, max_level, target_level, context);
|
|
2991
|
+
|
|
2992
|
+
// From `target_level` down perform proper extensive search
|
|
2993
|
+
for (level_t level = (std::min)(target_level, max_level); level >= 0; --level) {
|
|
2994
|
+
// TODO: Handle out of memory conditions
|
|
2995
|
+
search_to_insert_(closest_id, vector, level, config.expansion, context);
|
|
2996
|
+
closest_id = connect_new_node_(node_id, level, context);
|
|
2997
|
+
reconnect_neighbor_nodes_(node_id, level, context);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
|
|
2875
3001
|
id_t connect_new_node_(id_t new_id, level_t level, context_t& context) usearch_noexcept_m {
|
|
2876
3002
|
|
|
2877
3003
|
node_t new_node = node_with_id_(new_id);
|