usearch 2.17.1 → 2.19.5
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/README.md +6 -2
- package/c/lib.cpp +13 -2
- package/c/module.modulemap +4 -0
- package/c/usearch.h +13 -4
- package/cpp/README.md +1 -1
- package/cpp/bench.cpp +2 -2
- package/cpp/test.cpp +110 -16
- package/include/usearch/index.hpp +91 -35
- package/include/usearch/index_dense.hpp +34 -19
- package/include/usearch/index_plugins.hpp +29 -7
- package/javascript/README.md +8 -6
- package/javascript/dist/cjs/usearch.d.ts +9 -4
- package/javascript/dist/cjs/usearch.js +23 -8
- package/javascript/dist/esm/usearch.d.ts +9 -4
- package/javascript/dist/esm/usearch.js +21 -7
- package/javascript/lib.cpp +137 -59
- package/javascript/usearch.test.js +359 -192
- package/javascript/usearch.ts +35 -11
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/CONTRIBUTING.md +23 -8
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +1 -1
- package/simsimd/README.md +49 -9
- package/simsimd/build.rs +7 -3
- package/simsimd/c/lib.c +6 -1
- package/simsimd/include/simsimd/binary.h +3 -3
- package/simsimd/include/simsimd/dot.h +2 -2
- package/simsimd/include/simsimd/elementwise.h +26 -17
- package/simsimd/include/simsimd/simsimd.h +125 -49
- package/simsimd/include/simsimd/sparse.h +30 -29
- package/simsimd/include/simsimd/spatial.h +4 -3
- package/simsimd/include/simsimd/types.h +19 -8
- package/simsimd/javascript/lib.c +0 -1
- package/simsimd/package-lock.json +411 -362
- package/simsimd/package.json +9 -9
- package/simsimd/rust/lib.rs +63 -29
- package/simsimd/scripts/bench.cxx +13 -0
- package/simsimd/scripts/bench_cosine.rs +8 -0
- package/simsimd/scripts/bench_native.rs +4 -0
- package/simsimd/scripts/bench_sqeuclidean.rs +8 -0
- package/simsimd/scripts/test.c +22 -0
- package/simsimd/scripts/test.py +80 -4
- package/simsimd/swift/SimSIMD.swift +17 -0
- package/simsimd/swift/Test.swift +36 -0
package/README.md
CHANGED
|
@@ -161,7 +161,7 @@ This can result in __20x cost reduction__ on AWS and other public clouds.
|
|
|
161
161
|
index.save("index.usearch")
|
|
162
162
|
|
|
163
163
|
loaded_copy = index.load("index.usearch")
|
|
164
|
-
view = Index.restore("index.usearch", view=True)
|
|
164
|
+
view = Index.restore("index.usearch", view=True, ...)
|
|
165
165
|
|
|
166
166
|
other_view = Index(ndim=..., metric=...)
|
|
167
167
|
other_view.view("index.usearch")
|
|
@@ -528,7 +528,11 @@ index = Index(ndim=ndim, metric=CompiledMetric(
|
|
|
528
528
|
|
|
529
529
|
- [x] ClickHouse: [C++](https://github.com/ClickHouse/ClickHouse/pull/53447), [docs](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/annindexes#usearch).
|
|
530
530
|
- [x] DuckDB: [post](https://duckdb.org/2024/05/03/vector-similarity-search-vss.html).
|
|
531
|
+
- [x] ScyllaDB: [Rust](https://github.com/scylladb/vector-store), [presentation](https://www.slideshare.net/slideshow/vector-search-with-scylladb-by-szymon-wasik/276571548).
|
|
532
|
+
- [x] TiDB & TiFlash: [C++](https://github.com/pingcap/tiflash), [announcement](https://www.pingcap.com/article/introduce-vector-search-indexes-in-tidb/).
|
|
533
|
+
- [x] YugaByte: [C++](https://github.com/yugabyte/yugabyte-db/blob/366b9f5e3c4df3a1a17d553db41d6dc50146f488/src/yb/vector_index/usearch_wrapper.cc).
|
|
531
534
|
- [x] Google: [UniSim](https://github.com/google/unisim), [RetSim](https://arxiv.org/abs/2311.17264) paper.
|
|
535
|
+
- [x] MemGraph: [C++](https://github.com/memgraph/memgraph/blob/784dd8520f65050d033aea8b29446e84e487d091/src/storage/v2/indices/vector_index.cpp), [announcement](https://memgraph.com/blog/simplify-data-retrieval-memgraph-vector-search).
|
|
532
536
|
- [x] LanternDB: [C++](https://github.com/lanterndata/lantern), [Rust](https://github.com/lanterndata/lantern_extras), [docs](https://lantern.dev/blog/hnsw-index-creation).
|
|
533
537
|
- [x] LangChain: [Python](https://github.com/langchain-ai/langchain/releases/tag/v0.0.257) and [JavaScript](https://github.com/hwchase17/langchainjs/releases/tag/0.0.125).
|
|
534
538
|
- [x] Microsoft Semantic Kernel: [Python](https://github.com/microsoft/semantic-kernel/releases/tag/python-0.3.9.dev) and C#.
|
|
@@ -546,7 +550,7 @@ doi = {10.5281/zenodo.7949416},
|
|
|
546
550
|
author = {Vardanian, Ash},
|
|
547
551
|
title = {{USearch by Unum Cloud}},
|
|
548
552
|
url = {https://github.com/unum-cloud/usearch},
|
|
549
|
-
version = {2.
|
|
553
|
+
version = {2.19.5},
|
|
550
554
|
year = {2023},
|
|
551
555
|
month = oct,
|
|
552
556
|
}
|
package/c/lib.cpp
CHANGED
|
@@ -59,6 +59,7 @@ scalar_kind_t scalar_kind_to_cpp(usearch_scalar_kind_t kind) {
|
|
|
59
59
|
case usearch_scalar_f32_k: return scalar_kind_t::f32_k;
|
|
60
60
|
case usearch_scalar_f64_k: return scalar_kind_t::f64_k;
|
|
61
61
|
case usearch_scalar_f16_k: return scalar_kind_t::f16_k;
|
|
62
|
+
case usearch_scalar_bf16_k: return scalar_kind_t::bf16_k;
|
|
62
63
|
case usearch_scalar_i8_k: return scalar_kind_t::i8_k;
|
|
63
64
|
case usearch_scalar_b1_k: return scalar_kind_t::b1x8_k;
|
|
64
65
|
default: return scalar_kind_t::unknown_k;
|
|
@@ -70,6 +71,7 @@ usearch_scalar_kind_t scalar_kind_to_c(scalar_kind_t kind) {
|
|
|
70
71
|
case scalar_kind_t::f32_k: return usearch_scalar_f32_k;
|
|
71
72
|
case scalar_kind_t::f64_k: return usearch_scalar_f64_k;
|
|
72
73
|
case scalar_kind_t::f16_k: return usearch_scalar_f16_k;
|
|
74
|
+
case scalar_kind_t::bf16_k: return usearch_scalar_bf16_k;
|
|
73
75
|
case scalar_kind_t::i8_k: return usearch_scalar_i8_k;
|
|
74
76
|
case scalar_kind_t::b1x8_k: return usearch_scalar_b1_k;
|
|
75
77
|
default: return usearch_scalar_unknown_k;
|
|
@@ -81,6 +83,7 @@ add_result_t add_(index_dense_t* index, usearch_key_t key, void const* vector, s
|
|
|
81
83
|
case scalar_kind_t::f32_k: return index->add(key, (f32_t const*)vector);
|
|
82
84
|
case scalar_kind_t::f64_k: return index->add(key, (f64_t const*)vector);
|
|
83
85
|
case scalar_kind_t::f16_k: return index->add(key, (f16_t const*)vector);
|
|
86
|
+
case scalar_kind_t::bf16_k: return index->add(key, (bf16_t const*)vector);
|
|
84
87
|
case scalar_kind_t::i8_k: return index->add(key, (i8_t const*)vector);
|
|
85
88
|
case scalar_kind_t::b1x8_k: return index->add(key, (b1x8_t const*)vector);
|
|
86
89
|
default: return add_result_t{}.failed("Unknown scalar kind!");
|
|
@@ -92,6 +95,7 @@ std::size_t get_(index_dense_t* index, usearch_key_t key, size_t count, void* ve
|
|
|
92
95
|
case scalar_kind_t::f32_k: return index->get(key, (f32_t*)vector, count);
|
|
93
96
|
case scalar_kind_t::f64_k: return index->get(key, (f64_t*)vector, count);
|
|
94
97
|
case scalar_kind_t::f16_k: return index->get(key, (f16_t*)vector, count);
|
|
98
|
+
case scalar_kind_t::bf16_k: return index->get(key, (bf16_t*)vector, count);
|
|
95
99
|
case scalar_kind_t::i8_k: return index->get(key, (i8_t*)vector, count);
|
|
96
100
|
case scalar_kind_t::b1x8_k: return index->get(key, (b1x8_t*)vector, count);
|
|
97
101
|
default: return search_result_t(*index).failed("Unknown scalar kind!");
|
|
@@ -108,6 +112,8 @@ search_result_t search_(index_dense_t* index, void const* vector, scalar_kind_t
|
|
|
108
112
|
return index->filtered_search((f64_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
109
113
|
case scalar_kind_t::f16_k:
|
|
110
114
|
return index->filtered_search((f16_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
115
|
+
case scalar_kind_t::bf16_k:
|
|
116
|
+
return index->filtered_search((bf16_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
111
117
|
case scalar_kind_t::i8_k:
|
|
112
118
|
return index->filtered_search((i8_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
113
119
|
case scalar_kind_t::b1x8_k:
|
|
@@ -401,7 +407,7 @@ USEARCH_EXPORT size_t usearch_search(
|
|
|
401
407
|
return 0;
|
|
402
408
|
}
|
|
403
409
|
|
|
404
|
-
return result.dump_to(found_keys, found_distances);
|
|
410
|
+
return result.dump_to(found_keys, found_distances, results_limit);
|
|
405
411
|
}
|
|
406
412
|
|
|
407
413
|
USEARCH_EXPORT size_t usearch_filtered_search( //
|
|
@@ -419,7 +425,7 @@ USEARCH_EXPORT size_t usearch_filtered_search( /
|
|
|
419
425
|
return 0;
|
|
420
426
|
}
|
|
421
427
|
|
|
422
|
-
return result.dump_to(found_keys, found_distances);
|
|
428
|
+
return result.dump_to(found_keys, found_distances, results_limit);
|
|
423
429
|
}
|
|
424
430
|
|
|
425
431
|
USEARCH_EXPORT size_t usearch_get( //
|
|
@@ -493,4 +499,9 @@ USEARCH_EXPORT void usearch_exact_search( //
|
|
|
493
499
|
query_distances[i] = static_cast<usearch_distance_t>(query_result[i].distance);
|
|
494
500
|
}
|
|
495
501
|
}
|
|
502
|
+
|
|
503
|
+
USEARCH_EXPORT void usearch_clear(usearch_index_t index, usearch_error_t* error) {
|
|
504
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
505
|
+
reinterpret_cast<index_dense_t*>(index)->clear();
|
|
506
|
+
}
|
|
496
507
|
}
|
package/c/usearch.h
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#ifndef UNUM_USEARCH_H
|
|
2
2
|
#define UNUM_USEARCH_H
|
|
3
3
|
|
|
4
|
+
#include <stdbool.h> // `bool`
|
|
5
|
+
#include <stddef.h> // `size_t`
|
|
6
|
+
#include <stdint.h> // `uint64_t`
|
|
7
|
+
|
|
4
8
|
#ifdef __cplusplus
|
|
5
9
|
extern "C" {
|
|
6
10
|
#endif
|
|
@@ -13,10 +17,6 @@ extern "C" {
|
|
|
13
17
|
#endif
|
|
14
18
|
#endif
|
|
15
19
|
|
|
16
|
-
#include <stdbool.h> // `bool`
|
|
17
|
-
#include <stddef.h> // `size_t`
|
|
18
|
-
#include <stdint.h> // `uint64_t`
|
|
19
|
-
|
|
20
20
|
USEARCH_EXPORT typedef void* usearch_index_t;
|
|
21
21
|
USEARCH_EXPORT typedef uint64_t usearch_key_t;
|
|
22
22
|
USEARCH_EXPORT typedef float usearch_distance_t;
|
|
@@ -58,6 +58,7 @@ USEARCH_EXPORT typedef enum usearch_scalar_kind_t {
|
|
|
58
58
|
usearch_scalar_f16_k = 3,
|
|
59
59
|
usearch_scalar_i8_k = 4,
|
|
60
60
|
usearch_scalar_b1_k = 5,
|
|
61
|
+
usearch_scalar_bf16_k = 6,
|
|
61
62
|
} usearch_scalar_kind_t;
|
|
62
63
|
|
|
63
64
|
USEARCH_EXPORT typedef struct usearch_init_options_t {
|
|
@@ -472,6 +473,14 @@ USEARCH_EXPORT void usearch_exact_search( //
|
|
|
472
473
|
usearch_distance_t* distances, size_t distances_stride, //
|
|
473
474
|
usearch_error_t* error);
|
|
474
475
|
|
|
476
|
+
/**
|
|
477
|
+
* @brief Erases all the vectors from the index.
|
|
478
|
+
* @param[inout] index The handle to the USearch index to be modified.
|
|
479
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
480
|
+
*/
|
|
481
|
+
USEARCH_EXPORT void usearch_clear(usearch_index_t index,
|
|
482
|
+
usearch_error_t* error);
|
|
483
|
+
|
|
475
484
|
#ifdef __cplusplus
|
|
476
485
|
}
|
|
477
486
|
#endif
|
package/cpp/README.md
CHANGED
|
@@ -164,7 +164,7 @@ As in many other bulk-processing APIs, the `executor` and `progress` are optiona
|
|
|
164
164
|
In its high-level interface, USearch supports a variety of metrics, including the most popular ones:
|
|
165
165
|
|
|
166
166
|
- `metric_cos_gt<scalar_t>` for "Cosine" or "Angular" distance.
|
|
167
|
-
- `metric_ip_gt<scalar_t>` for "Inner Product" or "Dot Product" distance.
|
|
167
|
+
- `metric_ip_gt<scalar_t>` for "Inner Product" or "Dot Product" distance with normalized vectors.
|
|
168
168
|
- `metric_l2sq_gt<scalar_t>` for the squared "L2" or "Euclidean" distance.
|
|
169
169
|
- `metric_jaccard_gt<scalar_t>` for "Jaccard" distance between two ordered sets of unique elements.
|
|
170
170
|
- `metric_hamming_gt<scalar_t>` for "Hamming" distance, as the number of shared bits in hashes.
|
package/cpp/bench.cpp
CHANGED
|
@@ -327,7 +327,7 @@ void search_many( //
|
|
|
327
327
|
config.thread = omp_get_thread_num();
|
|
328
328
|
#endif
|
|
329
329
|
span_gt<scalar_at const> vector{vectors + dims * i, dims};
|
|
330
|
-
index.search(vector, wanted, config.thread).dump_to(ids + wanted * i, distances + wanted * i);
|
|
330
|
+
index.search(vector, wanted, config.thread).dump_to(ids + wanted * i, distances + wanted * i, wanted);
|
|
331
331
|
printer.progress++;
|
|
332
332
|
if (config.thread == 0)
|
|
333
333
|
printer.refresh();
|
|
@@ -514,7 +514,7 @@ struct args_t {
|
|
|
514
514
|
};
|
|
515
515
|
|
|
516
516
|
template <typename index_at, typename dataset_at> //
|
|
517
|
-
void run_punned(dataset_at& dataset, args_t const& args,
|
|
517
|
+
void run_punned(dataset_at& dataset, args_t const& args, index_dense_config_t config, index_limits_t limits) {
|
|
518
518
|
|
|
519
519
|
scalar_kind_t quantization = args.quantization();
|
|
520
520
|
std::printf("-- Quantization: %s\n", scalar_kind_name(quantization));
|
package/cpp/test.cpp
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
#include <algorithm> // `std::shuffle`
|
|
16
16
|
#include <cassert> // `assert`
|
|
17
|
+
#include <cmath> // `std::abs`
|
|
17
18
|
#include <random> // `std::default_random_engine`
|
|
18
19
|
#include <stdexcept> // `std::terminate`
|
|
19
20
|
#include <unordered_map> // `std::unordered_map`
|
|
@@ -35,7 +36,7 @@ void __expect(bool must_be_true, char const* file, int line, char const* message
|
|
|
35
36
|
message = message ? message : "C++ unit test failed";
|
|
36
37
|
char buffer[512];
|
|
37
38
|
std::snprintf(buffer, sizeof(buffer), "%s at %s:%d", message, file, line);
|
|
38
|
-
|
|
39
|
+
usearch_raise_runtime_error(buffer);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
template <typename value_at>
|
|
@@ -673,16 +674,16 @@ void test_cosine(std::size_t collection_size, std::size_t dimensions) {
|
|
|
673
674
|
scalar_t const* row(std::size_t i) const noexcept { return (*vector_of_vectors_ptr)[i].data(); }
|
|
674
675
|
|
|
675
676
|
float operator()(member_cref_t const& a, member_cref_t const& b) const {
|
|
676
|
-
return metric_cos_gt<scalar_t>{}(row(get_slot(b)), row(get_slot(a)), dimensions);
|
|
677
|
+
return metric_cos_gt<scalar_t, float>{}(row(get_slot(b)), row(get_slot(a)), dimensions);
|
|
677
678
|
}
|
|
678
679
|
float operator()(scalar_t const* some_vector, member_cref_t const& member) const {
|
|
679
|
-
return metric_cos_gt<scalar_t>{}(some_vector, row(get_slot(member)), dimensions);
|
|
680
|
+
return metric_cos_gt<scalar_t, float>{}(some_vector, row(get_slot(member)), dimensions);
|
|
680
681
|
}
|
|
681
682
|
float operator()(member_citerator_t const& a, member_citerator_t const& b) const {
|
|
682
|
-
return metric_cos_gt<scalar_t>{}(row(get_slot(b)), row(get_slot(a)), dimensions);
|
|
683
|
+
return metric_cos_gt<scalar_t, float>{}(row(get_slot(b)), row(get_slot(a)), dimensions);
|
|
683
684
|
}
|
|
684
685
|
float operator()(scalar_t const* some_vector, member_citerator_t const& member) const {
|
|
685
|
-
return metric_cos_gt<scalar_t>{}(some_vector, row(get_slot(member)), dimensions);
|
|
686
|
+
return metric_cos_gt<scalar_t, float>{}(some_vector, row(get_slot(member)), dimensions);
|
|
686
687
|
}
|
|
687
688
|
};
|
|
688
689
|
|
|
@@ -872,21 +873,23 @@ void test_absurd(std::size_t dimensions, std::size_t connectivity, std::size_t e
|
|
|
872
873
|
* @param dataset_count Number of vectors in the dataset.
|
|
873
874
|
* @param queries_count Number of query vectors.
|
|
874
875
|
* @param wanted_count Number of top matches required from each query.
|
|
876
|
+
* @tparam scalar_at Data type of the elements in the vectors.
|
|
875
877
|
*/
|
|
878
|
+
template <typename scalar_at>
|
|
876
879
|
void test_exact_search(std::size_t dataset_count, std::size_t queries_count, std::size_t wanted_count) {
|
|
877
880
|
std::size_t dimensions = 32;
|
|
878
|
-
metric_punned_t metric(dimensions, metric_kind_t::cos_k);
|
|
881
|
+
metric_punned_t metric(dimensions, metric_kind_t::cos_k, scalar_kind<scalar_at>());
|
|
879
882
|
|
|
880
|
-
std::random_device
|
|
881
|
-
std::mt19937
|
|
882
|
-
std::uniform_real_distribution<>
|
|
883
|
-
std::vector<
|
|
884
|
-
std::generate(dataset.begin(), dataset.end(), [&] { return
|
|
883
|
+
std::random_device seed_source;
|
|
884
|
+
std::mt19937 generator(seed_source());
|
|
885
|
+
std::uniform_real_distribution<> distribution(0.0, 1.0); // ! We can't pass `scalar_at` to the distribution
|
|
886
|
+
std::vector<scalar_at> dataset(dataset_count * dimensions);
|
|
887
|
+
std::generate(dataset.begin(), dataset.end(), [&] { return static_cast<scalar_at>(distribution(generator)); });
|
|
885
888
|
|
|
886
889
|
exact_search_t search;
|
|
887
|
-
auto results = search(
|
|
888
|
-
(byte_t const*)dataset.data(), dataset_count, dimensions * sizeof(
|
|
889
|
-
(byte_t const*)dataset.data(), queries_count, dimensions * sizeof(
|
|
890
|
+
auto results = search( //
|
|
891
|
+
(byte_t const*)dataset.data(), dataset_count, dimensions * sizeof(scalar_at), //
|
|
892
|
+
(byte_t const*)dataset.data(), queries_count, dimensions * sizeof(scalar_at), //
|
|
890
893
|
wanted_count, metric);
|
|
891
894
|
|
|
892
895
|
for (std::size_t i = 0; i < results.size(); ++i)
|
|
@@ -1096,10 +1099,94 @@ template <typename key_at, typename slot_at> void test_replacing_update() {
|
|
|
1096
1099
|
expect_eq(final_search[2].member.key, 44);
|
|
1097
1100
|
}
|
|
1098
1101
|
|
|
1102
|
+
/**
|
|
1103
|
+
* Tests the filtered search functionality of the index.
|
|
1104
|
+
*/
|
|
1105
|
+
void test_filtered_search() {
|
|
1106
|
+
constexpr std::size_t dataset_count = 2048;
|
|
1107
|
+
constexpr std::size_t dimensions = 32;
|
|
1108
|
+
metric_punned_t metric(dimensions, metric_kind_t::cos_k);
|
|
1109
|
+
|
|
1110
|
+
std::random_device seed_source;
|
|
1111
|
+
std::mt19937 generator(seed_source());
|
|
1112
|
+
std::uniform_real_distribution<float> distribution(0.0, 1.0);
|
|
1113
|
+
using vector_of_vectors_t = std::vector<std::vector<float>>;
|
|
1114
|
+
|
|
1115
|
+
vector_of_vectors_t vector_of_vectors(dataset_count);
|
|
1116
|
+
for (auto& vector : vector_of_vectors) {
|
|
1117
|
+
vector.resize(dimensions);
|
|
1118
|
+
std::generate(vector.begin(), vector.end(), [&] { return distribution(generator); });
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
index_dense_t index = index_dense_t::make(metric);
|
|
1122
|
+
index.reserve(dataset_count);
|
|
1123
|
+
for (std::size_t idx = 0; idx < dataset_count; ++idx)
|
|
1124
|
+
index.add(idx, vector_of_vectors[idx].data());
|
|
1125
|
+
expect_eq(index.size(), dataset_count);
|
|
1126
|
+
|
|
1127
|
+
{
|
|
1128
|
+
auto predicate = [](index_dense_t::key_t key) { return key != 0; };
|
|
1129
|
+
auto results = index.filtered_search(vector_of_vectors[0].data(), 10, predicate);
|
|
1130
|
+
expect_eq(10, results.size()); // ! Should not contain 0
|
|
1131
|
+
for (std::size_t i = 0; i != results.size(); ++i)
|
|
1132
|
+
expect(0 != results[i].member.key);
|
|
1133
|
+
}
|
|
1134
|
+
{
|
|
1135
|
+
auto predicate = [](index_dense_t::key_t) { return false; };
|
|
1136
|
+
auto results = index.filtered_search(vector_of_vectors[0].data(), 10, predicate);
|
|
1137
|
+
expect_eq(0, results.size()); // ! Should not contain 0
|
|
1138
|
+
}
|
|
1139
|
+
{
|
|
1140
|
+
auto predicate = [](index_dense_t::key_t key) { return key == 10; };
|
|
1141
|
+
auto results = index.filtered_search(vector_of_vectors[0].data(), 10, predicate);
|
|
1142
|
+
expect_eq(1, results.size()); // ! Should not contain 0
|
|
1143
|
+
expect_eq(10, results[0].member.key);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
void test_isolate() {
|
|
1148
|
+
constexpr std::size_t dataset_count = 16;
|
|
1149
|
+
constexpr std::size_t dimensions = 32;
|
|
1150
|
+
metric_punned_t metric(dimensions, metric_kind_t::cos_k);
|
|
1151
|
+
|
|
1152
|
+
std::random_device seed_source;
|
|
1153
|
+
std::mt19937 generator(seed_source());
|
|
1154
|
+
std::uniform_real_distribution<float> distribution(0.0, 1.0);
|
|
1155
|
+
using vector_of_vectors_t = std::vector<std::vector<float>>;
|
|
1156
|
+
|
|
1157
|
+
vector_of_vectors_t vector_of_vectors(dataset_count);
|
|
1158
|
+
for (auto& vector : vector_of_vectors) {
|
|
1159
|
+
vector.resize(dimensions);
|
|
1160
|
+
std::generate(vector.begin(), vector.end(), [&] { return distribution(generator); });
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
index_dense_t index = index_dense_t::make(metric);
|
|
1164
|
+
index.reserve(dataset_count);
|
|
1165
|
+
for (std::size_t idx = 0; idx < dataset_count; ++idx) {
|
|
1166
|
+
index.add(idx, vector_of_vectors[idx].data());
|
|
1167
|
+
}
|
|
1168
|
+
expect_eq(index.size(), dataset_count);
|
|
1169
|
+
|
|
1170
|
+
for (std::size_t idx = 0; idx < dataset_count; ++idx) {
|
|
1171
|
+
if (idx % 2 == 0)
|
|
1172
|
+
index.remove(idx);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
auto result = index.isolate();
|
|
1176
|
+
for (std::size_t idx = 0; idx < dataset_count; ++idx) {
|
|
1177
|
+
auto result = index.search(vector_of_vectors[idx].data(), 16);
|
|
1178
|
+
expect_eq(result.size(), dataset_count / 2);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1099
1182
|
int main(int, char**) {
|
|
1100
1183
|
test_uint40();
|
|
1101
1184
|
test_cosine<float, std::int64_t, uint40_t>(10, 10);
|
|
1102
1185
|
|
|
1186
|
+
// Non-default floating-point types may result in many compilation & rounding issues.
|
|
1187
|
+
test_cosine<f16_t, std::int64_t, uint40_t>(10, 10);
|
|
1188
|
+
test_cosine<bf16_t, std::int64_t, uint40_t>(10, 10);
|
|
1189
|
+
|
|
1103
1190
|
// Test plugins, like K-Means clustering.
|
|
1104
1191
|
{
|
|
1105
1192
|
std::size_t vectors_count = 1000, centroids_count = 10, dimensions = 256;
|
|
@@ -1121,8 +1208,11 @@ int main(int, char**) {
|
|
|
1121
1208
|
std::printf("Testing exact search\n");
|
|
1122
1209
|
for (std::size_t dataset_count : {10, 100})
|
|
1123
1210
|
for (std::size_t queries_count : {1, 10})
|
|
1124
|
-
for (std::size_t wanted_count : {1, 5})
|
|
1125
|
-
test_exact_search(dataset_count, queries_count, wanted_count);
|
|
1211
|
+
for (std::size_t wanted_count : {1, 5}) {
|
|
1212
|
+
test_exact_search<float>(dataset_count, queries_count, wanted_count);
|
|
1213
|
+
test_exact_search<f16_t>(dataset_count, queries_count, wanted_count);
|
|
1214
|
+
test_exact_search<bf16_t>(dataset_count, queries_count, wanted_count);
|
|
1215
|
+
}
|
|
1126
1216
|
|
|
1127
1217
|
// Make sure the initializers and the algorithms can work with inadequately small values.
|
|
1128
1218
|
// Be warned - this combinatorial explosion of tests produces close to __500'000__ tests!
|
|
@@ -1149,6 +1239,8 @@ int main(int, char**) {
|
|
|
1149
1239
|
test_cosine<float, std::int64_t, slot32_t>(collection_size, dimensions);
|
|
1150
1240
|
std::printf("- Indexing %zu vectors with cos: <float, std::int64_t, uint40_t> \n", collection_size);
|
|
1151
1241
|
test_cosine<float, std::int64_t, uint40_t>(collection_size, dimensions);
|
|
1242
|
+
std::printf("- Indexing %zu vectors with cos: <bf16, std::int64_t, uint40_t> \n", collection_size);
|
|
1243
|
+
test_cosine<bf16_t, std::int64_t, uint40_t>(collection_size, dimensions);
|
|
1152
1244
|
}
|
|
1153
1245
|
|
|
1154
1246
|
// Test with binary vectors
|
|
@@ -1163,5 +1255,7 @@ int main(int, char**) {
|
|
|
1163
1255
|
test_sets<std::int64_t, slot32_t>(set_size, 20, 30);
|
|
1164
1256
|
test_strings<std::int64_t, slot32_t>();
|
|
1165
1257
|
|
|
1258
|
+
test_filtered_search();
|
|
1259
|
+
test_isolate();
|
|
1166
1260
|
return 0;
|
|
1167
1261
|
}
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
#define UNUM_USEARCH_HPP
|
|
9
9
|
|
|
10
10
|
#define USEARCH_VERSION_MAJOR 2
|
|
11
|
-
#define USEARCH_VERSION_MINOR
|
|
12
|
-
#define USEARCH_VERSION_PATCH
|
|
11
|
+
#define USEARCH_VERSION_MINOR 19
|
|
12
|
+
#define USEARCH_VERSION_PATCH 5
|
|
13
13
|
|
|
14
14
|
// Inferring C++ version
|
|
15
15
|
// https://stackoverflow.com/a/61552074
|
|
@@ -149,15 +149,15 @@
|
|
|
149
149
|
#else
|
|
150
150
|
#define usearch_assert_m(must_be_true, message) \
|
|
151
151
|
if (!(must_be_true)) { \
|
|
152
|
-
|
|
152
|
+
usearch_raise_runtime_error(message); \
|
|
153
153
|
}
|
|
154
154
|
#define usearch_noexcept_m
|
|
155
155
|
#endif
|
|
156
156
|
|
|
157
157
|
extern "C" {
|
|
158
|
-
/// @brief Helper function to simplify debugging - trace just one symbol - `
|
|
158
|
+
/// @brief Helper function to simplify debugging - trace just one symbol - `usearch_raise_runtime_error`.
|
|
159
159
|
/// Assuming the `extern C` block, the name won't be mangled.
|
|
160
|
-
inline static void
|
|
160
|
+
inline static void usearch_raise_runtime_error(char const* message) {
|
|
161
161
|
// On Windows we compile with `/EHc` flag, which specifies that functions
|
|
162
162
|
// with C linkage do not throw C++ exceptions.
|
|
163
163
|
#if !defined(__cpp_exceptions) || defined(USEARCH_DEFINED_WINDOWS)
|
|
@@ -202,7 +202,7 @@ template <typename at> void misaligned_store(void* ptr, at v) noexcept {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
/// @brief Simply dereferencing misaligned pointers can be dangerous.
|
|
205
|
-
template <typename at> at misaligned_load(void* ptr) noexcept {
|
|
205
|
+
template <typename at> at misaligned_load(void const* ptr) noexcept {
|
|
206
206
|
static_assert(!std::is_reference<at>::value, "Can't load a reference");
|
|
207
207
|
at v;
|
|
208
208
|
std::memcpy(&v, ptr, sizeof(at));
|
|
@@ -1751,6 +1751,9 @@ class memory_mapped_file_t {
|
|
|
1751
1751
|
{
|
|
1752
1752
|
}
|
|
1753
1753
|
|
|
1754
|
+
memory_mapped_file_t(memory_mapped_file_t const&) = delete;
|
|
1755
|
+
memory_mapped_file_t& operator=(memory_mapped_file_t const&) = delete;
|
|
1756
|
+
|
|
1754
1757
|
memory_mapped_file_t(byte_t* data, std::size_t length) noexcept : ptr_(data), length_(length) {}
|
|
1755
1758
|
|
|
1756
1759
|
memory_mapped_file_t& operator=(memory_mapped_file_t&& other) noexcept {
|
|
@@ -1900,7 +1903,7 @@ template <typename key_at> inline key_at get_key(member_ref_gt<key_at> const& m)
|
|
|
1900
1903
|
* If classical containers store @b Key->Value mappings, this one can
|
|
1901
1904
|
* be seen as a network of keys, accelerating approximate @b Value~>Key visited_members.
|
|
1902
1905
|
*
|
|
1903
|
-
* Unlike most implementations, this one is generic
|
|
1906
|
+
* Unlike most implementations, this one is generic and can be used for any search,
|
|
1904
1907
|
* not just within equi-dimensional vectors. Examples range from Texts to similar Chess
|
|
1905
1908
|
* positions, Geo-Spatial Search, and even Graphs.
|
|
1906
1909
|
*
|
|
@@ -2174,6 +2177,20 @@ class index_gt {
|
|
|
2174
2177
|
misaligned_store<compressed_slot_t>(tape_ + shift(n), slot);
|
|
2175
2178
|
misaligned_store<neighbors_count_t>(tape_, n + 1);
|
|
2176
2179
|
}
|
|
2180
|
+
template <typename allow_slot_at> std::size_t erase_if(allow_slot_at&& allow_slot) noexcept {
|
|
2181
|
+
std::size_t old_count = misaligned_load<neighbors_count_t>(tape_);
|
|
2182
|
+
std::size_t removed_count = 0;
|
|
2183
|
+
for (std::size_t i = 0; i < old_count; ++i) {
|
|
2184
|
+
compressed_slot_t slot = misaligned_load<compressed_slot_t>(tape_ + shift(i));
|
|
2185
|
+
if (allow_slot(slot)) {
|
|
2186
|
+
removed_count++;
|
|
2187
|
+
} else {
|
|
2188
|
+
misaligned_store<compressed_slot_t>(tape_ + shift(i - removed_count), slot);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
misaligned_store<neighbors_count_t>(tape_, old_count - removed_count);
|
|
2192
|
+
return removed_count;
|
|
2193
|
+
}
|
|
2177
2194
|
};
|
|
2178
2195
|
|
|
2179
2196
|
/**
|
|
@@ -2183,6 +2200,7 @@ class index_gt {
|
|
|
2183
2200
|
*/
|
|
2184
2201
|
struct usearch_align_m context_t {
|
|
2185
2202
|
top_candidates_t top_candidates{};
|
|
2203
|
+
top_candidates_t top_for_refine{};
|
|
2186
2204
|
next_candidates_t next_candidates{};
|
|
2187
2205
|
visits_hash_set_t visits{};
|
|
2188
2206
|
std::default_random_engine level_generator{};
|
|
@@ -2326,7 +2344,7 @@ class index_gt {
|
|
|
2326
2344
|
state_result_t failed(error_t message) noexcept { return {std::move(index), std::move(message)}; }
|
|
2327
2345
|
operator index_gt&&() && {
|
|
2328
2346
|
if (error)
|
|
2329
|
-
|
|
2347
|
+
usearch_raise_runtime_error(error.what());
|
|
2330
2348
|
return std::move(index);
|
|
2331
2349
|
}
|
|
2332
2350
|
};
|
|
@@ -2498,6 +2516,13 @@ class index_gt {
|
|
|
2498
2516
|
if (nodes_)
|
|
2499
2517
|
std::memcpy(new_nodes.data(), nodes_.data(), sizeof(node_t) * size());
|
|
2500
2518
|
|
|
2519
|
+
// Pre-reserve the capacity for `top_for_refine`, which always contains at most one more
|
|
2520
|
+
// element than the connectivity factors.
|
|
2521
|
+
std::size_t connectivity_max = (std::max)(config_.connectivity_base, config_.connectivity);
|
|
2522
|
+
for (std::size_t i = 0; i != new_contexts.size(); ++i)
|
|
2523
|
+
if (!new_contexts[i].top_for_refine.reserve(connectivity_max + 1))
|
|
2524
|
+
return false;
|
|
2525
|
+
|
|
2501
2526
|
limits_ = limits;
|
|
2502
2527
|
nodes_capacity_ = limits.members;
|
|
2503
2528
|
nodes_ = std::move(new_nodes);
|
|
@@ -2670,6 +2695,49 @@ class index_gt {
|
|
|
2670
2695
|
}
|
|
2671
2696
|
return count;
|
|
2672
2697
|
}
|
|
2698
|
+
|
|
2699
|
+
/**
|
|
2700
|
+
* @brief Extracts the search results into a user-provided buffer.
|
|
2701
|
+
* @return The number of results stored in the buffer.
|
|
2702
|
+
* @param[in] keys The buffer to store the keys of the search results.
|
|
2703
|
+
* @param[in] distances The buffer to store the distances to the search results.
|
|
2704
|
+
* @param[in] capacity The maximum number of results that can be stored in the buffers.
|
|
2705
|
+
*/
|
|
2706
|
+
inline std::size_t dump_to(vector_key_t* keys, distance_t* distances, std::size_t capacity) const noexcept {
|
|
2707
|
+
std::size_t i = 0;
|
|
2708
|
+
std::size_t initialized_count = (std::min)(count, capacity);
|
|
2709
|
+
for (; i != initialized_count; ++i) {
|
|
2710
|
+
match_t result = operator[](i);
|
|
2711
|
+
keys[i] = result.member.key;
|
|
2712
|
+
distances[i] = result.distance;
|
|
2713
|
+
}
|
|
2714
|
+
for (; i != capacity; ++i) {
|
|
2715
|
+
keys[i] = vector_key_t{};
|
|
2716
|
+
distances[i] = std::numeric_limits<distance_t>::has_signaling_NaN
|
|
2717
|
+
? std::numeric_limits<distance_t>::signaling_NaN()
|
|
2718
|
+
: std::numeric_limits<distance_t>::max();
|
|
2719
|
+
}
|
|
2720
|
+
return initialized_count;
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
/**
|
|
2724
|
+
* @brief Extracts the search results into a user-provided buffer.
|
|
2725
|
+
* @return The number of results stored in the buffer.
|
|
2726
|
+
* @param[in] keys The buffer to store the keys of the search results.
|
|
2727
|
+
* @param[in] capacity The maximum number of results that can be stored in the buffers.
|
|
2728
|
+
*/
|
|
2729
|
+
inline std::size_t dump_to(vector_key_t* keys, std::size_t capacity) const noexcept {
|
|
2730
|
+
std::size_t i = 0;
|
|
2731
|
+
std::size_t initialized_count = (std::min)(this->count, capacity);
|
|
2732
|
+
for (; i != initialized_count; ++i) {
|
|
2733
|
+
match_t result = operator[](i);
|
|
2734
|
+
keys[i] = result.member.key;
|
|
2735
|
+
}
|
|
2736
|
+
for (; i != capacity; ++i)
|
|
2737
|
+
keys[i] = vector_key_t{};
|
|
2738
|
+
|
|
2739
|
+
return initialized_count;
|
|
2740
|
+
}
|
|
2673
2741
|
};
|
|
2674
2742
|
|
|
2675
2743
|
struct cluster_result_t {
|
|
@@ -3179,17 +3247,11 @@ class index_gt {
|
|
|
3179
3247
|
|
|
3180
3248
|
std::size_t memory_usage_per_node(level_t level) const noexcept { return node_bytes_(level); }
|
|
3181
3249
|
|
|
3182
|
-
double inverse_log_connectivity() const {
|
|
3183
|
-
return pre_.inverse_log_connectivity;
|
|
3184
|
-
}
|
|
3250
|
+
double inverse_log_connectivity() const { return pre_.inverse_log_connectivity; }
|
|
3185
3251
|
|
|
3186
|
-
std::size_t neighbors_base_bytes() const {
|
|
3187
|
-
return pre_.neighbors_base_bytes;
|
|
3188
|
-
}
|
|
3252
|
+
std::size_t neighbors_base_bytes() const { return pre_.neighbors_base_bytes; }
|
|
3189
3253
|
|
|
3190
|
-
std::size_t neighbors_bytes() const {
|
|
3191
|
-
return pre_.neighbors_bytes;
|
|
3192
|
-
}
|
|
3254
|
+
std::size_t neighbors_bytes() const { return pre_.neighbors_bytes; }
|
|
3193
3255
|
|
|
3194
3256
|
#if defined(USEARCH_USE_PRAGMA_REGION)
|
|
3195
3257
|
#pragma endregion
|
|
@@ -3649,14 +3711,10 @@ class index_gt {
|
|
|
3649
3711
|
node_t node = node_at_(node_idx);
|
|
3650
3712
|
for (level_t level = 0; level <= node.level(); ++level) {
|
|
3651
3713
|
neighbors_ref_t neighbors = neighbors_(node, level);
|
|
3652
|
-
|
|
3653
|
-
neighbors.clear();
|
|
3654
|
-
for (std::size_t i = 0; i != old_size; ++i) {
|
|
3655
|
-
compressed_slot_t neighbor_slot = neighbors[i];
|
|
3714
|
+
neighbors.erase_if([&](compressed_slot_t neighbor_slot) {
|
|
3656
3715
|
node_t neighbor = node_at_(neighbor_slot);
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
}
|
|
3716
|
+
return !allow_member(member_cref_t{neighbor.ckey(), neighbor_slot});
|
|
3717
|
+
});
|
|
3660
3718
|
}
|
|
3661
3719
|
++processed;
|
|
3662
3720
|
if (thread_idx == 0)
|
|
@@ -3790,7 +3848,7 @@ class index_gt {
|
|
|
3790
3848
|
metric_at&& metric, compressed_slot_t new_slot, candidates_view_t new_neighbors, value_at&& value,
|
|
3791
3849
|
level_t level, context_t& context) usearch_noexcept_m {
|
|
3792
3850
|
|
|
3793
|
-
top_candidates_t&
|
|
3851
|
+
top_candidates_t& top_for_refine = context.top_for_refine;
|
|
3794
3852
|
std::size_t const connectivity_max = level ? config_.connectivity : config_.connectivity_base;
|
|
3795
3853
|
|
|
3796
3854
|
// Reverse links from the neighbors:
|
|
@@ -3817,19 +3875,16 @@ class index_gt {
|
|
|
3817
3875
|
continue;
|
|
3818
3876
|
}
|
|
3819
3877
|
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
usearch_assert_m((top.capacity() >= (close_header.size() + 1)),
|
|
3823
|
-
"The memory must have been reserved in `add`");
|
|
3824
|
-
top.insert_reserved({context.measure(value, citerator_at(close_slot), metric), new_slot});
|
|
3878
|
+
top_for_refine.clear();
|
|
3879
|
+
top_for_refine.insert_reserved({context.measure(value, citerator_at(close_slot), metric), new_slot});
|
|
3825
3880
|
for (compressed_slot_t successor_slot : close_header)
|
|
3826
|
-
|
|
3881
|
+
top_for_refine.insert_reserved(
|
|
3827
3882
|
{context.measure(citerator_at(close_slot), citerator_at(successor_slot), metric), successor_slot});
|
|
3828
3883
|
|
|
3829
3884
|
// Export the results:
|
|
3830
3885
|
close_header.clear();
|
|
3831
|
-
candidates_view_t top_view =
|
|
3832
|
-
|
|
3886
|
+
candidates_view_t top_view = refine_(metric, connectivity_max, top_for_refine, context,
|
|
3887
|
+
context.computed_distances_in_reverse_refines);
|
|
3833
3888
|
usearch_assert_m(top_view.size(), "This would lead to isolated nodes");
|
|
3834
3889
|
for (std::size_t idx = 0; idx != top_view.size(); idx++)
|
|
3835
3890
|
close_header.push_back(top_view[idx].slot);
|
|
@@ -4178,9 +4233,10 @@ class index_gt {
|
|
|
4178
4233
|
// This can substantially grow our priority queue:
|
|
4179
4234
|
next.insert({-successor_dist, successor_slot});
|
|
4180
4235
|
if (is_dummy<predicate_at>() ||
|
|
4181
|
-
predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot}))
|
|
4236
|
+
predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) {
|
|
4182
4237
|
top.insert({successor_dist, successor_slot}, top_limit);
|
|
4183
|
-
|
|
4238
|
+
radius = top.top().distance;
|
|
4239
|
+
}
|
|
4184
4240
|
}
|
|
4185
4241
|
}
|
|
4186
4242
|
}
|