usearch 2.17.1 → 2.19.4
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 +5 -0
- package/c/module.modulemap +4 -0
- package/c/usearch.h +12 -4
- package/cpp/README.md +1 -1
- package/cpp/bench.cpp +1 -1
- package/cpp/test.cpp +110 -16
- package/include/usearch/index.hpp +48 -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.4},
|
|
550
554
|
year = {2023},
|
|
551
555
|
month = oct,
|
|
552
556
|
}
|
package/c/lib.cpp
CHANGED
|
@@ -493,4 +493,9 @@ USEARCH_EXPORT void usearch_exact_search( //
|
|
|
493
493
|
query_distances[i] = static_cast<usearch_distance_t>(query_result[i].distance);
|
|
494
494
|
}
|
|
495
495
|
}
|
|
496
|
+
|
|
497
|
+
USEARCH_EXPORT void usearch_clear(usearch_index_t index, usearch_error_t* error) {
|
|
498
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
499
|
+
reinterpret_cast<index_dense_t*>(index)->clear();
|
|
500
|
+
}
|
|
496
501
|
}
|
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;
|
|
@@ -472,6 +472,14 @@ USEARCH_EXPORT void usearch_exact_search( //
|
|
|
472
472
|
usearch_distance_t* distances, size_t distances_stride, //
|
|
473
473
|
usearch_error_t* error);
|
|
474
474
|
|
|
475
|
+
/**
|
|
476
|
+
* @brief Erases all the vectors from the index.
|
|
477
|
+
* @param[inout] index The handle to the USearch index to be modified.
|
|
478
|
+
* @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
|
|
479
|
+
*/
|
|
480
|
+
USEARCH_EXPORT void usearch_clear(usearch_index_t index,
|
|
481
|
+
usearch_error_t* error);
|
|
482
|
+
|
|
475
483
|
#ifdef __cplusplus
|
|
476
484
|
}
|
|
477
485
|
#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
|
@@ -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 4
|
|
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);
|
|
@@ -3179,17 +3204,11 @@ class index_gt {
|
|
|
3179
3204
|
|
|
3180
3205
|
std::size_t memory_usage_per_node(level_t level) const noexcept { return node_bytes_(level); }
|
|
3181
3206
|
|
|
3182
|
-
double inverse_log_connectivity() const {
|
|
3183
|
-
return pre_.inverse_log_connectivity;
|
|
3184
|
-
}
|
|
3207
|
+
double inverse_log_connectivity() const { return pre_.inverse_log_connectivity; }
|
|
3185
3208
|
|
|
3186
|
-
std::size_t neighbors_base_bytes() const {
|
|
3187
|
-
return pre_.neighbors_base_bytes;
|
|
3188
|
-
}
|
|
3209
|
+
std::size_t neighbors_base_bytes() const { return pre_.neighbors_base_bytes; }
|
|
3189
3210
|
|
|
3190
|
-
std::size_t neighbors_bytes() const {
|
|
3191
|
-
return pre_.neighbors_bytes;
|
|
3192
|
-
}
|
|
3211
|
+
std::size_t neighbors_bytes() const { return pre_.neighbors_bytes; }
|
|
3193
3212
|
|
|
3194
3213
|
#if defined(USEARCH_USE_PRAGMA_REGION)
|
|
3195
3214
|
#pragma endregion
|
|
@@ -3649,14 +3668,10 @@ class index_gt {
|
|
|
3649
3668
|
node_t node = node_at_(node_idx);
|
|
3650
3669
|
for (level_t level = 0; level <= node.level(); ++level) {
|
|
3651
3670
|
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];
|
|
3671
|
+
neighbors.erase_if([&](compressed_slot_t neighbor_slot) {
|
|
3656
3672
|
node_t neighbor = node_at_(neighbor_slot);
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
}
|
|
3673
|
+
return !allow_member(member_cref_t{neighbor.ckey(), neighbor_slot});
|
|
3674
|
+
});
|
|
3660
3675
|
}
|
|
3661
3676
|
++processed;
|
|
3662
3677
|
if (thread_idx == 0)
|
|
@@ -3790,7 +3805,7 @@ class index_gt {
|
|
|
3790
3805
|
metric_at&& metric, compressed_slot_t new_slot, candidates_view_t new_neighbors, value_at&& value,
|
|
3791
3806
|
level_t level, context_t& context) usearch_noexcept_m {
|
|
3792
3807
|
|
|
3793
|
-
top_candidates_t&
|
|
3808
|
+
top_candidates_t& top_for_refine = context.top_for_refine;
|
|
3794
3809
|
std::size_t const connectivity_max = level ? config_.connectivity : config_.connectivity_base;
|
|
3795
3810
|
|
|
3796
3811
|
// Reverse links from the neighbors:
|
|
@@ -3817,19 +3832,16 @@ class index_gt {
|
|
|
3817
3832
|
continue;
|
|
3818
3833
|
}
|
|
3819
3834
|
|
|
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});
|
|
3835
|
+
top_for_refine.clear();
|
|
3836
|
+
top_for_refine.insert_reserved({context.measure(value, citerator_at(close_slot), metric), new_slot});
|
|
3825
3837
|
for (compressed_slot_t successor_slot : close_header)
|
|
3826
|
-
|
|
3838
|
+
top_for_refine.insert_reserved(
|
|
3827
3839
|
{context.measure(citerator_at(close_slot), citerator_at(successor_slot), metric), successor_slot});
|
|
3828
3840
|
|
|
3829
3841
|
// Export the results:
|
|
3830
3842
|
close_header.clear();
|
|
3831
|
-
candidates_view_t top_view =
|
|
3832
|
-
|
|
3843
|
+
candidates_view_t top_view = refine_(metric, connectivity_max, top_for_refine, context,
|
|
3844
|
+
context.computed_distances_in_reverse_refines);
|
|
3833
3845
|
usearch_assert_m(top_view.size(), "This would lead to isolated nodes");
|
|
3834
3846
|
for (std::size_t idx = 0; idx != top_view.size(); idx++)
|
|
3835
3847
|
close_header.push_back(top_view[idx].slot);
|
|
@@ -4178,9 +4190,10 @@ class index_gt {
|
|
|
4178
4190
|
// This can substantially grow our priority queue:
|
|
4179
4191
|
next.insert({-successor_dist, successor_slot});
|
|
4180
4192
|
if (is_dummy<predicate_at>() ||
|
|
4181
|
-
predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot}))
|
|
4193
|
+
predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) {
|
|
4182
4194
|
top.insert({successor_dist, successor_slot}, top_limit);
|
|
4183
|
-
|
|
4195
|
+
radius = top.top().distance;
|
|
4196
|
+
}
|
|
4184
4197
|
}
|
|
4185
4198
|
}
|
|
4186
4199
|
}
|