usearch 2.7.7 → 2.8.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/.dockerignore +93 -0
- package/.github/workflows/Dockerfile.libs +6 -2
- package/.github/workflows/prerelease.yml +242 -245
- package/.github/workflows/release.yml +399 -357
- package/.gitignore +1 -0
- package/.vscode/settings.json +3 -1
- package/CITATION.cff +1 -1
- package/CMakeLists.txt +35 -5
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/build.gradle +6 -0
- package/c/README.md +3 -3
- package/c/lib.cpp +75 -11
- package/c/usearch.h +22 -1
- package/conanfile.py +1 -1
- package/cpp/README.md +2 -1
- package/cpp/bench.cpp +3 -0
- package/cpp/test.cpp +12 -12
- package/csharp/nuget/nuget-package.props +1 -1
- package/docs/compilation.md +5 -13
- package/golang/lib.go +4 -1
- package/include/usearch/index.hpp +31 -30
- package/include/usearch/index_dense.hpp +49 -48
- package/include/usearch/index_plugins.hpp +70 -8
- package/java/cloud/unum/usearch/Index.java +118 -26
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +8 -8
- package/javascript/usearch.js +1 -0
- package/objc/USearchObjective.mm +4 -1
- package/objc/include/USearchObjective.h +1 -0
- package/package.json +1 -1
- package/pyproject.toml +10 -8
- package/python/lib.cpp +3 -2
- package/python/scripts/bench.ipynb +24 -2
- package/python/usearch/index.py +8 -3
- package/rust/lib.cpp +14 -12
- package/rust/lib.hpp +13 -13
- package/rust/lib.rs +47 -0
- package/setup.py +15 -3
- package/simsimd/.github/workflows/prerelease.yml +7 -8
- package/simsimd/.github/workflows/release.yml +18 -20
- package/simsimd/README.md +13 -8
- package/simsimd/binding.gyp +1 -1
- package/simsimd/cpp/bench.cxx +17 -3
- package/simsimd/include/simsimd/binary.h +1 -3
- package/simsimd/include/simsimd/probability.h +234 -361
- package/simsimd/include/simsimd/simsimd.h +95 -65
- package/simsimd/include/simsimd/spatial.h +0 -3
- package/simsimd/include/simsimd/types.h +12 -7
- package/simsimd/javascript/test.js +23 -5
- package/simsimd/package.json +2 -2
- package/wasmer.toml +1 -1
- package/wolfram/lib.cpp +1 -1
- package/csharp/eng/build_lib.sh +0 -65
- package/csharp/eng/clean.sh +0 -12
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
#define UNUM_USEARCH_HPP
|
|
11
11
|
|
|
12
12
|
#define USEARCH_VERSION_MAJOR 2
|
|
13
|
-
#define USEARCH_VERSION_MINOR
|
|
14
|
-
#define USEARCH_VERSION_PATCH
|
|
13
|
+
#define USEARCH_VERSION_MINOR 8
|
|
14
|
+
#define USEARCH_VERSION_PATCH 1
|
|
15
15
|
|
|
16
16
|
// Inferring C++ version
|
|
17
17
|
// https://stackoverflow.com/a/61552074
|
|
@@ -1686,14 +1686,15 @@ template <typename distance_at = default_distance_t, //
|
|
|
1686
1686
|
class index_gt {
|
|
1687
1687
|
public:
|
|
1688
1688
|
using distance_t = distance_at;
|
|
1689
|
-
using
|
|
1689
|
+
using vector_key_t = key_at;
|
|
1690
|
+
using key_t = vector_key_t;
|
|
1690
1691
|
using compressed_slot_t = compressed_slot_at;
|
|
1691
1692
|
using dynamic_allocator_t = dynamic_allocator_at;
|
|
1692
1693
|
using tape_allocator_t = tape_allocator_at;
|
|
1693
|
-
static_assert(sizeof(
|
|
1694
|
+
static_assert(sizeof(vector_key_t) >= sizeof(compressed_slot_t), "Having tiny keys doesn't make sense.");
|
|
1694
1695
|
|
|
1695
|
-
using member_cref_t = member_cref_gt<
|
|
1696
|
-
using member_ref_t = member_ref_gt<
|
|
1696
|
+
using member_cref_t = member_cref_gt<vector_key_t>;
|
|
1697
|
+
using member_ref_t = member_ref_gt<vector_key_t>;
|
|
1697
1698
|
|
|
1698
1699
|
template <typename ref_at, typename index_at> class member_iterator_gt {
|
|
1699
1700
|
using ref_t = ref_at;
|
|
@@ -1714,10 +1715,10 @@ class index_gt {
|
|
|
1714
1715
|
using reference = ref_t;
|
|
1715
1716
|
|
|
1716
1717
|
reference operator*() const noexcept { return {index_->node_at_(slot_).key(), slot_}; }
|
|
1717
|
-
|
|
1718
|
+
vector_key_t key() const noexcept { return index_->node_at_(slot_).key(); }
|
|
1718
1719
|
|
|
1719
1720
|
friend inline std::size_t get_slot(member_iterator_gt const& it) noexcept { return it.slot_; }
|
|
1720
|
-
friend inline
|
|
1721
|
+
friend inline vector_key_t get_key(member_iterator_gt const& it) noexcept { return it.key(); }
|
|
1721
1722
|
|
|
1722
1723
|
member_iterator_gt operator++(int) noexcept { return member_iterator_gt(index_, slot_ + 1); }
|
|
1723
1724
|
member_iterator_gt operator--(int) noexcept { return member_iterator_gt(index_, slot_ - 1); }
|
|
@@ -1738,7 +1739,7 @@ class index_gt {
|
|
|
1738
1739
|
using member_citerator_t = member_iterator_gt<member_cref_t, index_gt const>;
|
|
1739
1740
|
|
|
1740
1741
|
// STL compatibility:
|
|
1741
|
-
using value_type =
|
|
1742
|
+
using value_type = vector_key_t;
|
|
1742
1743
|
using allocator_type = dynamic_allocator_t;
|
|
1743
1744
|
using size_type = std::size_t;
|
|
1744
1745
|
using difference_type = std::ptrdiff_t;
|
|
@@ -1774,7 +1775,7 @@ class index_gt {
|
|
|
1774
1775
|
/**
|
|
1775
1776
|
* @brief How many bytes of memory are needed to form the "head" of the node.
|
|
1776
1777
|
*/
|
|
1777
|
-
static constexpr std::size_t node_head_bytes_() { return sizeof(
|
|
1778
|
+
static constexpr std::size_t node_head_bytes_() { return sizeof(vector_key_t) + sizeof(level_t); }
|
|
1778
1779
|
|
|
1779
1780
|
using nodes_mutexes_t = bitset_gt<dynamic_allocator_t>;
|
|
1780
1781
|
|
|
@@ -1800,7 +1801,7 @@ class index_gt {
|
|
|
1800
1801
|
/**
|
|
1801
1802
|
* @brief A loosely-structured handle for every node. One such node is created for every member.
|
|
1802
1803
|
* To minimize memory usage and maximize the number of entries per cache-line, it only
|
|
1803
|
-
* stores to pointers. The internal tape starts with a `
|
|
1804
|
+
* stores to pointers. The internal tape starts with a `vector_key_t` @b key, then
|
|
1804
1805
|
* a `level_t` for the number of graph @b levels in which this member appears,
|
|
1805
1806
|
* then the { `neighbors_count_t`, `compressed_slot_t`, `compressed_slot_t` ... } sequences
|
|
1806
1807
|
* for @b each-level.
|
|
@@ -1818,12 +1819,12 @@ class index_gt {
|
|
|
1818
1819
|
node_t(node_t const&) = default;
|
|
1819
1820
|
node_t& operator=(node_t const&) = default;
|
|
1820
1821
|
|
|
1821
|
-
misaligned_ref_gt<
|
|
1822
|
-
misaligned_ref_gt<
|
|
1823
|
-
misaligned_ref_gt<level_t> level() const noexcept { return {tape_ + sizeof(
|
|
1822
|
+
misaligned_ref_gt<vector_key_t const> ckey() const noexcept { return {tape_}; }
|
|
1823
|
+
misaligned_ref_gt<vector_key_t> key() const noexcept { return {tape_}; }
|
|
1824
|
+
misaligned_ref_gt<level_t> level() const noexcept { return {tape_ + sizeof(vector_key_t)}; }
|
|
1824
1825
|
|
|
1825
|
-
void key(
|
|
1826
|
-
void level(level_t v) noexcept { return misaligned_store<level_t>(tape_ + sizeof(
|
|
1826
|
+
void key(vector_key_t v) noexcept { return misaligned_store<vector_key_t>(tape_, v); }
|
|
1827
|
+
void level(level_t v) noexcept { return misaligned_store<level_t>(tape_ + sizeof(vector_key_t), v); }
|
|
1827
1828
|
};
|
|
1828
1829
|
|
|
1829
1830
|
static_assert(std::is_trivially_copy_constructible<node_t>::value, "Nodes must be light!");
|
|
@@ -2192,7 +2193,7 @@ class index_gt {
|
|
|
2192
2193
|
inline match_t operator[](std::size_t i) const noexcept { return at(i); }
|
|
2193
2194
|
inline match_t front() const noexcept { return at(0); }
|
|
2194
2195
|
inline match_t back() const noexcept { return at(count - 1); }
|
|
2195
|
-
inline bool contains(
|
|
2196
|
+
inline bool contains(vector_key_t key) const noexcept {
|
|
2196
2197
|
for (std::size_t i = 0; i != count; ++i)
|
|
2197
2198
|
if (at(i).member.key == key)
|
|
2198
2199
|
return true;
|
|
@@ -2205,7 +2206,7 @@ class index_gt {
|
|
|
2205
2206
|
return {member_cref_t{node.ckey(), candidate.slot}, candidate.distance};
|
|
2206
2207
|
}
|
|
2207
2208
|
inline std::size_t merge_into( //
|
|
2208
|
-
|
|
2209
|
+
vector_key_t* keys, distance_t* distances, //
|
|
2209
2210
|
std::size_t old_count, std::size_t max_count) const noexcept {
|
|
2210
2211
|
|
|
2211
2212
|
std::size_t merged_count = old_count;
|
|
@@ -2217,7 +2218,7 @@ class index_gt {
|
|
|
2217
2218
|
continue;
|
|
2218
2219
|
|
|
2219
2220
|
std::size_t count_worse = merged_count - offset - (max_count == merged_count);
|
|
2220
|
-
std::memmove(keys + offset + 1, keys + offset, count_worse * sizeof(
|
|
2221
|
+
std::memmove(keys + offset + 1, keys + offset, count_worse * sizeof(vector_key_t));
|
|
2221
2222
|
std::memmove(distances + offset + 1, distances + offset, count_worse * sizeof(distance_t));
|
|
2222
2223
|
keys[offset] = result.member.key;
|
|
2223
2224
|
distances[offset] = result.distance;
|
|
@@ -2225,7 +2226,7 @@ class index_gt {
|
|
|
2225
2226
|
}
|
|
2226
2227
|
return merged_count;
|
|
2227
2228
|
}
|
|
2228
|
-
inline std::size_t dump_to(
|
|
2229
|
+
inline std::size_t dump_to(vector_key_t* keys, distance_t* distances) const noexcept {
|
|
2229
2230
|
for (std::size_t i = 0; i != count; ++i) {
|
|
2230
2231
|
match_t result = operator[](i);
|
|
2231
2232
|
keys[i] = result.member.key;
|
|
@@ -2233,7 +2234,7 @@ class index_gt {
|
|
|
2233
2234
|
}
|
|
2234
2235
|
return count;
|
|
2235
2236
|
}
|
|
2236
|
-
inline std::size_t dump_to(
|
|
2237
|
+
inline std::size_t dump_to(vector_key_t* keys) const noexcept {
|
|
2237
2238
|
for (std::size_t i = 0; i != count; ++i) {
|
|
2238
2239
|
match_t result = operator[](i);
|
|
2239
2240
|
keys[i] = result.member.key;
|
|
@@ -2264,7 +2265,7 @@ class index_gt {
|
|
|
2264
2265
|
* It should be callable into distinctly different scenarios:
|
|
2265
2266
|
* - `distance_t operator() (value_at, entry_at)` - from new object to existing entries.
|
|
2266
2267
|
* - `distance_t operator() (entry_at, entry_at)` - between existing entries.
|
|
2267
|
-
* Where any possible `entry_at` has both two interfaces: `std::size_t slot()`, `
|
|
2268
|
+
* Where any possible `entry_at` has both two interfaces: `std::size_t slot()`, `vector_key_t key()`.
|
|
2268
2269
|
*
|
|
2269
2270
|
* @param[in] key External identifier/name/descriptor for the new entry.
|
|
2270
2271
|
* @param[in] value Content that will be compared against other entries to index.
|
|
@@ -2279,7 +2280,7 @@ class index_gt {
|
|
|
2279
2280
|
typename prefetch_at = dummy_prefetch_t //
|
|
2280
2281
|
>
|
|
2281
2282
|
add_result_t add( //
|
|
2282
|
-
|
|
2283
|
+
vector_key_t key, value_at&& value, metric_at&& metric, //
|
|
2283
2284
|
index_update_config_t config = {}, //
|
|
2284
2285
|
callback_at&& callback = callback_at{}, //
|
|
2285
2286
|
prefetch_at&& prefetch = prefetch_at{}) usearch_noexcept_m {
|
|
@@ -2371,7 +2372,7 @@ class index_gt {
|
|
|
2371
2372
|
* - `distance_t operator() (entry_at, entry_at)` - between existing entries.
|
|
2372
2373
|
* For any possible `entry_at` following interfaces will work:
|
|
2373
2374
|
* - `std::size_t get_slot(entry_at const &)`
|
|
2374
|
-
* - `
|
|
2375
|
+
* - `vector_key_t get_key(entry_at const &)`
|
|
2375
2376
|
*
|
|
2376
2377
|
* @param[in] iterator Iterator pointing to an existing entry to be replaced.
|
|
2377
2378
|
* @param[in] key External identifier/name/descriptor for the entry.
|
|
@@ -2388,7 +2389,7 @@ class index_gt {
|
|
|
2388
2389
|
>
|
|
2389
2390
|
add_result_t update( //
|
|
2390
2391
|
member_iterator_t iterator, //
|
|
2391
|
-
|
|
2392
|
+
vector_key_t key, //
|
|
2392
2393
|
value_at&& value, //
|
|
2393
2394
|
metric_at&& metric, //
|
|
2394
2395
|
index_update_config_t config = {}, //
|
|
@@ -3142,7 +3143,7 @@ class index_gt {
|
|
|
3142
3143
|
return data ? span_bytes_t{data, node_bytes} : span_bytes_t{};
|
|
3143
3144
|
}
|
|
3144
3145
|
|
|
3145
|
-
node_t node_make_(
|
|
3146
|
+
node_t node_make_(vector_key_t key, level_t level) noexcept {
|
|
3146
3147
|
span_bytes_t node_bytes = node_malloc_(level);
|
|
3147
3148
|
if (!node_bytes)
|
|
3148
3149
|
return {};
|
|
@@ -3334,10 +3335,10 @@ class index_gt {
|
|
|
3334
3335
|
bool operator==(candidates_iterator_t const& other) noexcept { return current_ == other.current_; }
|
|
3335
3336
|
bool operator!=(candidates_iterator_t const& other) noexcept { return current_ != other.current_; }
|
|
3336
3337
|
|
|
3337
|
-
|
|
3338
|
+
vector_key_t key() const noexcept { return index_->node_at_(slot()).key(); }
|
|
3338
3339
|
compressed_slot_t slot() const noexcept { return neighbors_[current_]; }
|
|
3339
3340
|
friend inline std::size_t get_slot(candidates_iterator_t const& it) noexcept { return it.slot(); }
|
|
3340
|
-
friend inline
|
|
3341
|
+
friend inline vector_key_t get_key(candidates_iterator_t const& it) noexcept { return it.key(); }
|
|
3341
3342
|
};
|
|
3342
3343
|
|
|
3343
3344
|
struct candidates_range_t {
|
|
@@ -3683,8 +3684,8 @@ static join_result_t join( //
|
|
|
3683
3684
|
|
|
3684
3685
|
using distance_t = typename men_at::distance_t;
|
|
3685
3686
|
using dynamic_allocator_traits_t = typename men_at::dynamic_allocator_traits_t;
|
|
3686
|
-
using man_key_t = typename men_at::
|
|
3687
|
-
using woman_key_t = typename women_at::
|
|
3687
|
+
using man_key_t = typename men_at::vector_key_t;
|
|
3688
|
+
using woman_key_t = typename women_at::vector_key_t;
|
|
3688
3689
|
|
|
3689
3690
|
// Use the `compressed_slot_t` type of the larger collection
|
|
3690
3691
|
using compressed_slot_t = typename women_at::compressed_slot_t;
|
|
@@ -301,13 +301,14 @@ inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_map
|
|
|
301
301
|
template <typename key_at = default_key_t, typename compressed_slot_at = default_slot_t> //
|
|
302
302
|
class index_dense_gt {
|
|
303
303
|
public:
|
|
304
|
-
using
|
|
304
|
+
using vector_key_t = key_at;
|
|
305
|
+
using key_t = vector_key_t;
|
|
305
306
|
using compressed_slot_t = compressed_slot_at;
|
|
306
307
|
using distance_t = distance_punned_t;
|
|
307
308
|
using metric_t = metric_punned_t;
|
|
308
309
|
|
|
309
|
-
using member_ref_t = member_ref_gt<
|
|
310
|
-
using member_cref_t = member_cref_gt<
|
|
310
|
+
using member_ref_t = member_ref_gt<vector_key_t>;
|
|
311
|
+
using member_cref_t = member_cref_gt<vector_key_t>;
|
|
311
312
|
|
|
312
313
|
using head_t = index_dense_head_t;
|
|
313
314
|
using head_buffer_t = index_dense_head_buffer_t;
|
|
@@ -323,7 +324,7 @@ class index_dense_gt {
|
|
|
323
324
|
using cast_t = std::function<bool(byte_t const*, std::size_t, byte_t*)>;
|
|
324
325
|
/// @brief Punned index.
|
|
325
326
|
using index_t = index_gt< //
|
|
326
|
-
distance_t,
|
|
327
|
+
distance_t, vector_key_t, compressed_slot_t, //
|
|
327
328
|
dynamic_allocator_t, tape_allocator_t>;
|
|
328
329
|
using index_allocator_t = aligned_allocator_gt<index_t, 64>;
|
|
329
330
|
|
|
@@ -395,23 +396,23 @@ class index_dense_gt {
|
|
|
395
396
|
using unique_lock_t = std::unique_lock<shared_mutex_t>;
|
|
396
397
|
|
|
397
398
|
struct key_and_slot_t {
|
|
398
|
-
|
|
399
|
+
vector_key_t key;
|
|
399
400
|
compressed_slot_t slot;
|
|
400
401
|
|
|
401
402
|
bool any_slot() const { return slot == default_free_value<compressed_slot_t>(); }
|
|
402
|
-
static key_and_slot_t any_slot(
|
|
403
|
+
static key_and_slot_t any_slot(vector_key_t key) { return {key, default_free_value<compressed_slot_t>()}; }
|
|
403
404
|
};
|
|
404
405
|
|
|
405
406
|
struct lookup_key_hash_t {
|
|
406
407
|
using is_transparent = void;
|
|
407
|
-
std::size_t operator()(key_and_slot_t const& k) const noexcept { return std::hash<
|
|
408
|
-
std::size_t operator()(
|
|
408
|
+
std::size_t operator()(key_and_slot_t const& k) const noexcept { return std::hash<vector_key_t>{}(k.key); }
|
|
409
|
+
std::size_t operator()(vector_key_t const& k) const noexcept { return std::hash<vector_key_t>{}(k); }
|
|
409
410
|
};
|
|
410
411
|
|
|
411
412
|
struct lookup_key_same_t {
|
|
412
413
|
using is_transparent = void;
|
|
413
|
-
bool operator()(key_and_slot_t const& a,
|
|
414
|
-
bool operator()(
|
|
414
|
+
bool operator()(key_and_slot_t const& a, vector_key_t const& b) const noexcept { return a.key == b; }
|
|
415
|
+
bool operator()(vector_key_t const& a, key_and_slot_t const& b) const noexcept { return a == b.key; }
|
|
415
416
|
bool operator()(key_and_slot_t const& a, key_and_slot_t const& b) const noexcept { return a.key == b.key; }
|
|
416
417
|
};
|
|
417
418
|
|
|
@@ -428,7 +429,7 @@ class index_dense_gt {
|
|
|
428
429
|
mutable std::mutex free_keys_mutex_;
|
|
429
430
|
|
|
430
431
|
/// @brief A constant for the reserved key value, used to mark deleted entries.
|
|
431
|
-
|
|
432
|
+
vector_key_t free_key_ = default_free_value<vector_key_t>();
|
|
432
433
|
|
|
433
434
|
public:
|
|
434
435
|
using search_result_t = typename index_t::search_result_t;
|
|
@@ -497,7 +498,7 @@ class index_dense_gt {
|
|
|
497
498
|
static index_dense_gt make( //
|
|
498
499
|
metric_t metric, //
|
|
499
500
|
index_dense_config_t config = {}, //
|
|
500
|
-
|
|
501
|
+
vector_key_t free_key = default_free_value<vector_key_t>()) {
|
|
501
502
|
|
|
502
503
|
scalar_kind_t scalar_kind = metric.scalar_kind();
|
|
503
504
|
std::size_t hardware_threads = std::thread::hardware_concurrency();
|
|
@@ -573,7 +574,7 @@ class index_dense_gt {
|
|
|
573
574
|
}
|
|
574
575
|
|
|
575
576
|
dynamic_allocator_t const& allocator() const { return typed_->dynamic_allocator(); }
|
|
576
|
-
|
|
577
|
+
vector_key_t const& free_key() const { return free_key_; }
|
|
577
578
|
|
|
578
579
|
/**
|
|
579
580
|
* @brief A relatively accurate lower bound on the amount of memory consumed by the system.
|
|
@@ -600,11 +601,11 @@ class index_dense_gt {
|
|
|
600
601
|
};
|
|
601
602
|
|
|
602
603
|
// clang-format off
|
|
603
|
-
add_result_t add(
|
|
604
|
-
add_result_t add(
|
|
605
|
-
add_result_t add(
|
|
606
|
-
add_result_t add(
|
|
607
|
-
add_result_t add(
|
|
604
|
+
add_result_t add(vector_key_t key, b1x8_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_b1x8); }
|
|
605
|
+
add_result_t add(vector_key_t key, i8_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_i8); }
|
|
606
|
+
add_result_t add(vector_key_t key, f16_t const* vector, std::size_t thread = any_thread(), bool force_vector_copy = true) { return add_(key, vector, thread, force_vector_copy, casts_.from_f16); }
|
|
607
|
+
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); }
|
|
608
|
+
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); }
|
|
608
609
|
|
|
609
610
|
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); }
|
|
610
611
|
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); }
|
|
@@ -612,11 +613,11 @@ class index_dense_gt {
|
|
|
612
613
|
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); }
|
|
613
614
|
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); }
|
|
614
615
|
|
|
615
|
-
std::size_t get(
|
|
616
|
-
std::size_t get(
|
|
617
|
-
std::size_t get(
|
|
618
|
-
std::size_t get(
|
|
619
|
-
std::size_t get(
|
|
616
|
+
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); }
|
|
617
|
+
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); }
|
|
618
|
+
std::size_t get(vector_key_t key, f16_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f16); }
|
|
619
|
+
std::size_t get(vector_key_t key, f32_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f32); }
|
|
620
|
+
std::size_t get(vector_key_t key, f64_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f64); }
|
|
620
621
|
|
|
621
622
|
cluster_result_t cluster(b1x8_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_b1x8); }
|
|
622
623
|
cluster_result_t cluster(i8_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_i8); }
|
|
@@ -624,11 +625,11 @@ class index_dense_gt {
|
|
|
624
625
|
cluster_result_t cluster(f32_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_f32); }
|
|
625
626
|
cluster_result_t cluster(f64_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_f64); }
|
|
626
627
|
|
|
627
|
-
aggregated_distances_t distance_between(
|
|
628
|
-
aggregated_distances_t distance_between(
|
|
629
|
-
aggregated_distances_t distance_between(
|
|
630
|
-
aggregated_distances_t distance_between(
|
|
631
|
-
aggregated_distances_t distance_between(
|
|
628
|
+
aggregated_distances_t distance_between(vector_key_t key, b1x8_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_b1x8); }
|
|
629
|
+
aggregated_distances_t distance_between(vector_key_t key, i8_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_i8); }
|
|
630
|
+
aggregated_distances_t distance_between(vector_key_t key, f16_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_f16); }
|
|
631
|
+
aggregated_distances_t distance_between(vector_key_t key, f32_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_f32); }
|
|
632
|
+
aggregated_distances_t distance_between(vector_key_t key, f64_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to_f64); }
|
|
632
633
|
// clang-format on
|
|
633
634
|
|
|
634
635
|
/**
|
|
@@ -636,7 +637,7 @@ class index_dense_gt {
|
|
|
636
637
|
* If either key maps into more than one vector, will aggregate results
|
|
637
638
|
* exporting the mean, maximum, and minimum values.
|
|
638
639
|
*/
|
|
639
|
-
aggregated_distances_t distance_between(
|
|
640
|
+
aggregated_distances_t distance_between(vector_key_t a, vector_key_t b, std::size_t = any_thread()) const {
|
|
640
641
|
shared_lock_t lock(slot_lookup_mutex_);
|
|
641
642
|
aggregated_distances_t result;
|
|
642
643
|
if (!multi()) {
|
|
@@ -696,7 +697,7 @@ class index_dense_gt {
|
|
|
696
697
|
/**
|
|
697
698
|
* @brief Identifies a node in a given `level`, that is the closest to the `key`.
|
|
698
699
|
*/
|
|
699
|
-
cluster_result_t cluster(
|
|
700
|
+
cluster_result_t cluster(vector_key_t key, std::size_t level, std::size_t thread = any_thread()) const {
|
|
700
701
|
|
|
701
702
|
// Check if such `key` is even present.
|
|
702
703
|
shared_lock_t slots_lock(slot_lookup_mutex_);
|
|
@@ -837,7 +838,7 @@ class index_dense_gt {
|
|
|
837
838
|
// Describes types used
|
|
838
839
|
head.kind_metric = metric_.metric_kind();
|
|
839
840
|
head.kind_scalar = metric_.scalar_kind();
|
|
840
|
-
head.kind_key = unum::usearch::scalar_kind<
|
|
841
|
+
head.kind_key = unum::usearch::scalar_kind<vector_key_t>();
|
|
841
842
|
head.kind_compressed_slot = unum::usearch::scalar_kind<compressed_slot_t>();
|
|
842
843
|
|
|
843
844
|
head.count_present = size();
|
|
@@ -926,7 +927,7 @@ class index_dense_gt {
|
|
|
926
927
|
return result.failed("File format may be different, please rebuild");
|
|
927
928
|
|
|
928
929
|
// Check the types used
|
|
929
|
-
if (head.kind_key != unum::usearch::scalar_kind<
|
|
930
|
+
if (head.kind_key != unum::usearch::scalar_kind<vector_key_t>())
|
|
930
931
|
return result.failed("Key type doesn't match, consider rebuilding");
|
|
931
932
|
if (head.kind_compressed_slot != unum::usearch::scalar_kind<compressed_slot_t>())
|
|
932
933
|
return result.failed("Slot type doesn't match, consider rebuilding");
|
|
@@ -1010,7 +1011,7 @@ class index_dense_gt {
|
|
|
1010
1011
|
return result.failed("File format may be different, please rebuild");
|
|
1011
1012
|
|
|
1012
1013
|
// Check the types used
|
|
1013
|
-
if (head.kind_key != unum::usearch::scalar_kind<
|
|
1014
|
+
if (head.kind_key != unum::usearch::scalar_kind<vector_key_t>())
|
|
1014
1015
|
return result.failed("Key type doesn't match, consider rebuilding");
|
|
1015
1016
|
if (head.kind_compressed_slot != unum::usearch::scalar_kind<compressed_slot_t>())
|
|
1016
1017
|
return result.failed("Slot type doesn't match, consider rebuilding");
|
|
@@ -1164,7 +1165,7 @@ class index_dense_gt {
|
|
|
1164
1165
|
* @brief Checks if a vector with specified key is present.
|
|
1165
1166
|
* @return `true` if the key is present in the index, `false` otherwise.
|
|
1166
1167
|
*/
|
|
1167
|
-
bool contains(
|
|
1168
|
+
bool contains(vector_key_t key) const {
|
|
1168
1169
|
shared_lock_t lock(slot_lookup_mutex_);
|
|
1169
1170
|
return slot_lookup_.contains(key_and_slot_t::any_slot(key));
|
|
1170
1171
|
}
|
|
@@ -1173,7 +1174,7 @@ class index_dense_gt {
|
|
|
1173
1174
|
* @brief Count the number of vectors with specified key present.
|
|
1174
1175
|
* @return Zero if nothing is found, a positive integer otherwise.
|
|
1175
1176
|
*/
|
|
1176
|
-
std::size_t count(
|
|
1177
|
+
std::size_t count(vector_key_t key) const {
|
|
1177
1178
|
shared_lock_t lock(slot_lookup_mutex_);
|
|
1178
1179
|
return slot_lookup_.count(key_and_slot_t::any_slot(key));
|
|
1179
1180
|
}
|
|
@@ -1197,7 +1198,7 @@ class index_dense_gt {
|
|
|
1197
1198
|
* If the key was not found in the index, `result.completed` will be `false`.
|
|
1198
1199
|
* If an error occurred during the removal operation, `result.error` will contain an error message.
|
|
1199
1200
|
*/
|
|
1200
|
-
labeling_result_t remove(
|
|
1201
|
+
labeling_result_t remove(vector_key_t key) {
|
|
1201
1202
|
labeling_result_t result;
|
|
1202
1203
|
|
|
1203
1204
|
unique_lock_t lookup_lock(slot_lookup_mutex_);
|
|
@@ -1250,7 +1251,7 @@ class index_dense_gt {
|
|
|
1250
1251
|
|
|
1251
1252
|
// Remove them one-by-one
|
|
1252
1253
|
for (auto keys_it = keys_begin; keys_it != keys_end; ++keys_it) {
|
|
1253
|
-
|
|
1254
|
+
vector_key_t key = *keys_it;
|
|
1254
1255
|
auto matching_slots = slot_lookup_.equal_range(key_and_slot_t::any_slot(key));
|
|
1255
1256
|
// A removed entry would be:
|
|
1256
1257
|
// - present in `free_keys_`
|
|
@@ -1279,7 +1280,7 @@ class index_dense_gt {
|
|
|
1279
1280
|
* If the rename was successful, `result.completed` will be `true`.
|
|
1280
1281
|
* If the entry with the current key was not found, `result.completed` will be `false`.
|
|
1281
1282
|
*/
|
|
1282
|
-
labeling_result_t rename(
|
|
1283
|
+
labeling_result_t rename(vector_key_t from, vector_key_t to) {
|
|
1283
1284
|
labeling_result_t result;
|
|
1284
1285
|
unique_lock_t lookup_lock(slot_lookup_mutex_);
|
|
1285
1286
|
|
|
@@ -1307,7 +1308,7 @@ class index_dense_gt {
|
|
|
1307
1308
|
* @param[in] offset The number of keys to skip. Useful for pagination.
|
|
1308
1309
|
* @param[in] limit The maximum number of keys to export, that can fit in ::keys.
|
|
1309
1310
|
*/
|
|
1310
|
-
void export_keys(
|
|
1311
|
+
void export_keys(vector_key_t* keys, std::size_t offset, std::size_t limit) const {
|
|
1311
1312
|
shared_lock_t lock(slot_lookup_mutex_);
|
|
1312
1313
|
offset = (std::min)(offset, slot_lookup_.size());
|
|
1313
1314
|
slot_lookup_.for_each([&](key_and_slot_t const& key_and_slot) {
|
|
@@ -1455,7 +1456,7 @@ class index_dense_gt {
|
|
|
1455
1456
|
std::vector<byte_t*> new_vectors_lookup(vectors_lookup_.size());
|
|
1456
1457
|
vectors_tape_allocator_t new_vectors_allocator;
|
|
1457
1458
|
|
|
1458
|
-
auto track_slot_change = [&](
|
|
1459
|
+
auto track_slot_change = [&](vector_key_t, compressed_slot_t old_slot, compressed_slot_t new_slot) {
|
|
1459
1460
|
byte_t* new_vector = new_vectors_allocator.allocate(metric_.bytes_per_vector());
|
|
1460
1461
|
byte_t* old_vector = vectors_lookup_[old_slot];
|
|
1461
1462
|
std::memcpy(new_vector, old_vector, metric_.bytes_per_vector());
|
|
@@ -1525,7 +1526,7 @@ class index_dense_gt {
|
|
|
1525
1526
|
queries_iterator_at queries_begin, //
|
|
1526
1527
|
queries_iterator_at queries_end, //
|
|
1527
1528
|
index_dense_clustering_config_t config, //
|
|
1528
|
-
|
|
1529
|
+
vector_key_t* cluster_keys, //
|
|
1529
1530
|
distance_t* cluster_distances, //
|
|
1530
1531
|
executor_at&& executor = executor_at{}, //
|
|
1531
1532
|
progress_at&& progress = progress_at{}) {
|
|
@@ -1548,8 +1549,8 @@ class index_dense_gt {
|
|
|
1548
1549
|
|
|
1549
1550
|
// A structure used to track the popularity of a specific cluster
|
|
1550
1551
|
struct cluster_t {
|
|
1551
|
-
|
|
1552
|
-
|
|
1552
|
+
vector_key_t centroid;
|
|
1553
|
+
vector_key_t merged_into;
|
|
1553
1554
|
std::size_t popularity;
|
|
1554
1555
|
byte_t* vector;
|
|
1555
1556
|
};
|
|
@@ -1658,7 +1659,7 @@ class index_dense_gt {
|
|
|
1658
1659
|
std::sort(clusters.data(), clusters_end, centroid_id);
|
|
1659
1660
|
|
|
1660
1661
|
executor.dynamic(queries_count, [&](std::size_t thread_idx, std::size_t query_idx) {
|
|
1661
|
-
|
|
1662
|
+
vector_key_t& cluster_key = cluster_keys[query_idx];
|
|
1662
1663
|
distance_t& cluster_distance = cluster_distances[query_idx];
|
|
1663
1664
|
|
|
1664
1665
|
// Recursively trace replacements of that cluster
|
|
@@ -1715,7 +1716,7 @@ class index_dense_gt {
|
|
|
1715
1716
|
|
|
1716
1717
|
template <typename scalar_at>
|
|
1717
1718
|
add_result_t add_( //
|
|
1718
|
-
|
|
1719
|
+
vector_key_t key, scalar_at const* vector, //
|
|
1719
1720
|
std::size_t thread, bool force_vector_copy, cast_t const& cast) {
|
|
1720
1721
|
|
|
1721
1722
|
if (!multi() && contains(key))
|
|
@@ -1811,7 +1812,7 @@ class index_dense_gt {
|
|
|
1811
1812
|
|
|
1812
1813
|
template <typename scalar_at>
|
|
1813
1814
|
aggregated_distances_t distance_between_( //
|
|
1814
|
-
|
|
1815
|
+
vector_key_t key, scalar_at const* vector, //
|
|
1815
1816
|
std::size_t thread, cast_t const& cast) const {
|
|
1816
1817
|
|
|
1817
1818
|
// Cast the vector, if needed for compatibility with `metric_`
|
|
@@ -1881,12 +1882,12 @@ class index_dense_gt {
|
|
|
1881
1882
|
if (member.key == free_key_)
|
|
1882
1883
|
free_keys_.push(static_cast<compressed_slot_t>(i));
|
|
1883
1884
|
else if (config_.enable_key_lookups)
|
|
1884
|
-
slot_lookup_.try_emplace(key_and_slot_t{
|
|
1885
|
+
slot_lookup_.try_emplace(key_and_slot_t{vector_key_t(member.key), static_cast<compressed_slot_t>(i)});
|
|
1885
1886
|
}
|
|
1886
1887
|
}
|
|
1887
1888
|
|
|
1888
1889
|
template <typename scalar_at>
|
|
1889
|
-
std::size_t get_(
|
|
1890
|
+
std::size_t get_(vector_key_t key, scalar_at* reconstructed, std::size_t vectors_limit, cast_t const& cast) const {
|
|
1890
1891
|
|
|
1891
1892
|
if (!multi()) {
|
|
1892
1893
|
compressed_slot_t slot;
|