usearch 0.19.3 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/c/test.c +238 -126
- package/conanfile.py +1 -1
- package/cpp/test.cpp +12 -12
- package/include/usearch/index.hpp +133 -49
- package/include/usearch/index_punned_helpers.hpp +2 -0
- package/package.json +1 -1
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -167,7 +167,7 @@ index.add(/* label: */ 42, /* vector: */ {&vec[0], 3});
|
|
|
167
167
|
auto results = index.search(/* query: */ {&vec[0], 3}, 5 /* neighbors */);
|
|
168
168
|
|
|
169
169
|
for (std::size_t i = 0; i != results.size(); ++i)
|
|
170
|
-
results[i].
|
|
170
|
+
results[i].member.label, results[i].member.vector, results[i].member.id, results[i].distance;
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
The `add` is thread-safe for concurrent index construction.
|
package/c/test.c
CHANGED
|
@@ -1,182 +1,294 @@
|
|
|
1
|
-
#include <
|
|
1
|
+
#include <stdio.h>
|
|
2
2
|
#include <errno.h>
|
|
3
3
|
#include <stdio.h>
|
|
4
4
|
#include <stdlib.h>
|
|
5
|
-
#include <string.h>
|
|
6
5
|
#include <sys/stat.h>
|
|
7
|
-
#include <time.h>
|
|
8
6
|
|
|
9
7
|
#include "usearch.h"
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
float skip;
|
|
16
|
-
|
|
17
|
-
;
|
|
18
|
-
if (stat(filename, &st) == -1) {
|
|
19
|
-
printf("Failed to read file: %s err: %d: %s\n", filename, errno, strerror(errno));
|
|
20
|
-
return NULL;
|
|
9
|
+
#define ASSERT(must_be_true, message) \
|
|
10
|
+
if (!(must_be_true)) { \
|
|
11
|
+
printf("Assert: %s\n", message); \
|
|
12
|
+
exit(-1); \
|
|
21
13
|
}
|
|
22
14
|
|
|
23
|
-
file = fopen(filename, "rb");
|
|
24
|
-
if (file == NULL) {
|
|
25
|
-
printf("Failed to open file: %s\n", filename);
|
|
26
|
-
return NULL;
|
|
27
|
-
}
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
cnt = fread(vector_dim, sizeof(int), 1, file);
|
|
31
|
-
assert(cnt == 1);
|
|
32
|
-
if (st.st_size % ((*vector_dim + 1) * sizeof(float)) != 0) {
|
|
33
|
-
printf("File does not contain a whole number of vectors.\n Vector dim: %ld, file size: %ld\n", *vector_dim,
|
|
34
|
-
st.st_size);
|
|
35
|
-
fclose(file);
|
|
36
|
-
return NULL;
|
|
37
|
-
}
|
|
16
|
+
void parse_vectors_from_file(char const* file_path, size_t* count, size_t* dimension, float** data) {
|
|
38
17
|
|
|
39
|
-
|
|
18
|
+
// Open the file
|
|
19
|
+
struct stat st = {};
|
|
20
|
+
ASSERT(stat(file_path, &st) == 0, "Failed to get metadata of the file");
|
|
21
|
+
FILE* file = fopen(file_path, "rb");
|
|
22
|
+
ASSERT(file, "Failed to open the file");
|
|
40
23
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
24
|
+
// Read vectors dimension and calculate count
|
|
25
|
+
int dim = 0;
|
|
26
|
+
size_t read = fread(&dim, sizeof(int), 1, file);
|
|
27
|
+
ASSERT(read == 1, "Failed to read from the file");
|
|
28
|
+
ASSERT(st.st_size % ((dim + 1) * sizeof(float)) == 0, "File does not contain a whole number of vectors");
|
|
29
|
+
*dimension = (size_t)dim;
|
|
30
|
+
*count = st.st_size / ((dim + 1) * sizeof(float));
|
|
48
31
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
32
|
+
// Allocate memory for the vectors' data
|
|
33
|
+
*data = (float*)malloc(*count * *dimension * sizeof(float));
|
|
34
|
+
ASSERT(*data, "Failed to allocate memory");
|
|
35
|
+
|
|
36
|
+
// Read the data
|
|
37
|
+
for (size_t i = 0; i < *count; ++i) {
|
|
38
|
+
read = fread(*data + i * *dimension, sizeof(float), *dimension, file);
|
|
39
|
+
ASSERT(read == *dimension, "Failed to read from the file");
|
|
40
|
+
if (i == *count - 1)
|
|
54
41
|
break;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
42
|
+
|
|
43
|
+
// Skip
|
|
44
|
+
float skip;
|
|
45
|
+
read = fread(&skip, sizeof(float), 1, file);
|
|
46
|
+
ASSERT(read == 1, "Failed to read from the file");
|
|
58
47
|
}
|
|
59
48
|
|
|
60
49
|
fclose(file);
|
|
61
|
-
return data;
|
|
62
50
|
}
|
|
63
51
|
|
|
64
|
-
|
|
65
|
-
const int LOG_GRANULARITY = 10000;
|
|
66
|
-
|
|
67
|
-
void test_index(float* data, size_t num_vectors, size_t vector_dim) {
|
|
52
|
+
usearch_init_options_t create_options(size_t vector_dimension) {
|
|
68
53
|
usearch_init_options_t opts;
|
|
69
|
-
usearch_index_t idx;
|
|
70
|
-
usearch_error_t error = NULL;
|
|
71
|
-
clock_t start;
|
|
72
54
|
opts.connectivity = 2; // 32 in faiss
|
|
73
|
-
opts.dimensions =
|
|
55
|
+
opts.dimensions = vector_dimension;
|
|
74
56
|
opts.expansion_add = 40; // 40 in faiss
|
|
75
57
|
opts.expansion_search = 16; // 10 in faiss
|
|
76
58
|
opts.metric_kind = usearch_metric_ip_k;
|
|
77
59
|
opts.metric = NULL;
|
|
78
60
|
opts.quantization = usearch_scalar_f32_k;
|
|
61
|
+
return opts;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void test_init(size_t vectors_count, size_t vector_dimension) {
|
|
65
|
+
printf("Test: Index Initialization...\n");
|
|
66
|
+
|
|
67
|
+
// Init index
|
|
68
|
+
usearch_index_t idx = NULL;
|
|
69
|
+
usearch_error_t error = NULL;
|
|
70
|
+
usearch_init_options_t opts = create_options(vector_dimension);
|
|
71
|
+
idx = usearch_init(&opts, &error);
|
|
72
|
+
ASSERT(!error, error);
|
|
73
|
+
usearch_free(idx, &error);
|
|
74
|
+
ASSERT(!error, error);
|
|
75
|
+
|
|
76
|
+
// Init second time
|
|
77
|
+
idx = usearch_init(&opts, &error);
|
|
78
|
+
ASSERT(!error, error);
|
|
79
|
+
|
|
80
|
+
ASSERT(usearch_size(idx, &error) == 0, error);
|
|
81
|
+
ASSERT(usearch_capacity(idx, &error) == 0, error);
|
|
82
|
+
ASSERT(usearch_dimensions(idx, &error) == vector_dimension, error);
|
|
83
|
+
ASSERT(usearch_connectivity(idx, &error) == opts.connectivity, error);
|
|
84
|
+
|
|
85
|
+
// Reserve
|
|
86
|
+
usearch_reserve(idx, vectors_count, &error);
|
|
87
|
+
ASSERT(!error, error);
|
|
88
|
+
ASSERT(usearch_size(idx, &error) == 0, error);
|
|
89
|
+
ASSERT(usearch_capacity(idx, &error) == vectors_count, error);
|
|
90
|
+
ASSERT(usearch_dimensions(idx, &error) == vector_dimension, error);
|
|
91
|
+
ASSERT(usearch_connectivity(idx, &error) == opts.connectivity, error);
|
|
92
|
+
|
|
93
|
+
usearch_free(idx, &error);
|
|
94
|
+
ASSERT(!error, error);
|
|
95
|
+
|
|
96
|
+
printf("Test: Index Initialization - PASSED\n");
|
|
97
|
+
}
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
num_vectors /= 10;
|
|
83
|
-
usearch_label_t* labels = (usearch_label_t*)calloc(k, sizeof(usearch_label_t));
|
|
84
|
-
float* distances = (float*)calloc(k, sizeof(float));
|
|
99
|
+
void test_add_vector(size_t vectors_count, size_t vector_dimension, float const* data) {
|
|
100
|
+
printf("Test: Add Vector...\n");
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
usearch_index_t idx = NULL;
|
|
103
|
+
usearch_error_t error = NULL;
|
|
104
|
+
usearch_init_options_t opts = create_options(vector_dimension);
|
|
88
105
|
idx = usearch_init(&opts, &error);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
usearch_reserve(idx, vectors_count, &error);
|
|
107
|
+
|
|
108
|
+
// Add vectors
|
|
109
|
+
for (size_t i = 0; i < vectors_count; ++i) {
|
|
110
|
+
usearch_label_t label = i;
|
|
111
|
+
usearch_add(idx, label, data + i * vector_dimension, usearch_scalar_f32_k, &error);
|
|
112
|
+
ASSERT(!error, error);
|
|
92
113
|
}
|
|
93
114
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
assert(!error);
|
|
115
|
+
ASSERT(usearch_size(idx, &error) == vectors_count, error);
|
|
116
|
+
ASSERT(usearch_capacity(idx, &error) == vectors_count, error);
|
|
97
117
|
|
|
98
|
-
|
|
99
|
-
for (size_t i = 0; i <
|
|
100
|
-
if (i % LOG_GRANULARITY == 0) {
|
|
101
|
-
fprintf(stderr, "added %ld vectors...\n", i);
|
|
102
|
-
}
|
|
118
|
+
// Check vectors in the index
|
|
119
|
+
for (size_t i = 0; i < vectors_count; ++i) {
|
|
103
120
|
usearch_label_t label = i;
|
|
104
|
-
|
|
105
|
-
assert(!error);
|
|
121
|
+
ASSERT(usearch_contains(idx, label, &error), error);
|
|
106
122
|
}
|
|
123
|
+
ASSERT(!usearch_contains(idx, -1, &error), error); // Non existing label
|
|
124
|
+
|
|
125
|
+
usearch_free(idx, &error);
|
|
126
|
+
printf("Test: Add Vector - PASSED\n");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void test_find_vector(size_t vectors_count, size_t vector_dimension, float const* data) {
|
|
130
|
+
printf("Test: Find Vector...\n");
|
|
107
131
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
132
|
+
usearch_index_t idx = NULL;
|
|
133
|
+
usearch_error_t error = NULL;
|
|
134
|
+
usearch_init_options_t opts = create_options(vector_dimension);
|
|
135
|
+
idx = usearch_init(&opts, &error);
|
|
136
|
+
usearch_reserve(idx, vectors_count, &error);
|
|
137
|
+
|
|
138
|
+
// Create result buffers
|
|
139
|
+
int results_count = 10;
|
|
140
|
+
usearch_label_t* labels = (usearch_label_t*)malloc(results_count * sizeof(usearch_label_t));
|
|
141
|
+
float* distances = (float*)malloc(results_count * sizeof(float));
|
|
142
|
+
ASSERT(labels && distances, "Failed to allocate memory");
|
|
143
|
+
|
|
144
|
+
// Add vectors
|
|
145
|
+
for (size_t i = 0; i < vectors_count; ++i) {
|
|
113
146
|
usearch_label_t label = i;
|
|
114
|
-
|
|
115
|
-
|
|
147
|
+
usearch_add(idx, label, data + i * vector_dimension, usearch_scalar_f32_k, &error);
|
|
148
|
+
ASSERT(!error, error);
|
|
116
149
|
}
|
|
117
150
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
fprintf(stderr, "searched %ld vectors...\n", i);
|
|
125
|
-
}
|
|
126
|
-
const void *query_vector = data + i * vector_dim;
|
|
127
|
-
usearch_search(idx, query_vector, usearch_scalar_f32_k, k, labels, distances, &error);
|
|
128
|
-
assert(!error);
|
|
151
|
+
// Find the vectors
|
|
152
|
+
for (size_t i = 0; i < vectors_count; i++) {
|
|
153
|
+
const void *query_vector = data + i * vector_dimension;
|
|
154
|
+
size_t found_count = usearch_search(idx, query_vector, usearch_scalar_f32_k, results_count, labels, distances, &error);
|
|
155
|
+
ASSERT(!error, error);
|
|
156
|
+
ASSERT(found_count = results_count, "Vector is missing");
|
|
129
157
|
}
|
|
130
158
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
159
|
+
usearch_free(idx, &error);
|
|
160
|
+
printf("Test: Find Vector - PASSED\n");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
void test_remove_vector(size_t vectors_count, size_t vector_dimension, float const* data) {
|
|
164
|
+
printf("Test: Remove Vector...\n");
|
|
165
|
+
|
|
166
|
+
usearch_index_t idx = NULL;
|
|
167
|
+
usearch_error_t error = NULL;
|
|
168
|
+
usearch_init_options_t opts = create_options(vector_dimension);
|
|
169
|
+
idx = usearch_init(&opts, &error);
|
|
170
|
+
usearch_reserve(idx, vectors_count, &error);
|
|
171
|
+
|
|
172
|
+
// Add vectors
|
|
173
|
+
for (size_t i = 0; i < vectors_count; ++i) {
|
|
174
|
+
usearch_label_t label = i;
|
|
175
|
+
usearch_add(idx, label, data + i * vector_dimension, usearch_scalar_f32_k, &error);
|
|
176
|
+
ASSERT(!error, error);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Remove the vectors
|
|
180
|
+
for (size_t i = 0; i < vectors_count; i++) {
|
|
181
|
+
usearch_label_t label = i;
|
|
182
|
+
usearch_remove(idx, label, &error);
|
|
183
|
+
ASSERT(error, "Currently, Remove is not supported");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
usearch_free(idx, &error);
|
|
187
|
+
printf("Test: Remove Vector - PASSED\n");
|
|
188
|
+
}
|
|
136
189
|
|
|
137
|
-
|
|
138
|
-
|
|
190
|
+
void test_save_load(size_t vectors_count, size_t vector_dimension, float const* data) {
|
|
191
|
+
printf("Test: Save/Load...\n");
|
|
192
|
+
|
|
193
|
+
usearch_index_t idx = NULL;
|
|
194
|
+
usearch_error_t error = NULL;
|
|
195
|
+
usearch_init_options_t opts = create_options(vector_dimension);
|
|
196
|
+
idx = usearch_init(&opts, &error);
|
|
197
|
+
usearch_reserve(idx, vectors_count, &error);
|
|
198
|
+
|
|
199
|
+
// Add vectors
|
|
200
|
+
for (size_t i = 0; i < vectors_count; ++i) {
|
|
201
|
+
usearch_label_t label = i;
|
|
202
|
+
usearch_add(idx, label, data + i * vector_dimension, usearch_scalar_f32_k, &error);
|
|
203
|
+
ASSERT(!error, error);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Save and free the index
|
|
207
|
+
usearch_save(idx, "usearch_index.bin", &error);
|
|
208
|
+
ASSERT(!error, error);
|
|
139
209
|
usearch_free(idx, &error);
|
|
140
|
-
|
|
141
|
-
fprintf(stderr, "destroying the index took %.2f ms\n", ((double)(clock() - start)) / CLOCKS_PER_SEC * 1000);
|
|
210
|
+
ASSERT(!error, error);
|
|
142
211
|
|
|
143
|
-
|
|
144
|
-
start = clock();
|
|
212
|
+
// Reinit
|
|
145
213
|
idx = usearch_init(&opts, &error);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
214
|
+
ASSERT(!error, error);
|
|
215
|
+
ASSERT(usearch_size(idx, &error) == 0, error);
|
|
216
|
+
|
|
217
|
+
// Load
|
|
149
218
|
usearch_load(idx, "usearch_index.bin", &error);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
219
|
+
ASSERT(!error, error);
|
|
220
|
+
ASSERT(usearch_size(idx, &error) == vectors_count, error);
|
|
221
|
+
ASSERT(usearch_capacity(idx, &error) == vectors_count, error);
|
|
222
|
+
ASSERT(usearch_dimensions(idx, &error) == vector_dimension, error);
|
|
223
|
+
ASSERT(usearch_connectivity(idx, &error) == opts.connectivity, error);
|
|
224
|
+
|
|
225
|
+
// Check vectors in the index
|
|
226
|
+
for (size_t i = 0; i < vectors_count; ++i) {
|
|
227
|
+
usearch_label_t label = i;
|
|
228
|
+
ASSERT(usearch_contains(idx, label, &error), error);
|
|
229
|
+
}
|
|
230
|
+
|
|
154
231
|
usearch_free(idx, &error);
|
|
232
|
+
printf("Test: Save/Load - PASSED\n");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
void test_view(size_t vectors_count, size_t vector_dimension, float const* data) {
|
|
236
|
+
printf("Test: View...\n");
|
|
155
237
|
|
|
156
|
-
|
|
157
|
-
|
|
238
|
+
usearch_index_t idx = NULL;
|
|
239
|
+
usearch_error_t error = NULL;
|
|
240
|
+
usearch_init_options_t opts = create_options(vector_dimension);
|
|
158
241
|
idx = usearch_init(&opts, &error);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
242
|
+
usearch_reserve(idx, vectors_count, &error);
|
|
243
|
+
|
|
244
|
+
// Add vectors
|
|
245
|
+
for (size_t i = 0; i < vectors_count; ++i) {
|
|
246
|
+
usearch_label_t label = i;
|
|
247
|
+
usearch_add(idx, label, data + i * vector_dimension, usearch_scalar_f32_k, &error);
|
|
248
|
+
ASSERT(!error, error);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Save and free the index
|
|
252
|
+
usearch_save(idx, "usearch_index.bin", &error);
|
|
253
|
+
ASSERT(!error, error);
|
|
167
254
|
usearch_free(idx, &error);
|
|
255
|
+
ASSERT(!error, error);
|
|
256
|
+
|
|
257
|
+
// Reinit
|
|
258
|
+
idx = usearch_init(&opts, &error);
|
|
259
|
+
ASSERT(!error, error);
|
|
260
|
+
|
|
261
|
+
// View
|
|
262
|
+
usearch_view(idx, "usearch_index.bin", &error);
|
|
263
|
+
ASSERT(!error, error);
|
|
168
264
|
|
|
265
|
+
usearch_free(idx, &error);
|
|
266
|
+
printf("Test: View - PASSED\n");
|
|
169
267
|
}
|
|
170
268
|
|
|
171
|
-
int main() {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
269
|
+
int main(int argc, char* argv[]) {
|
|
270
|
+
// Resolve file path
|
|
271
|
+
char const* filename = "sample.fvecs";
|
|
272
|
+
if (argc == 2)
|
|
273
|
+
filename = argv[1];
|
|
176
274
|
|
|
177
|
-
|
|
275
|
+
// Read data from the file
|
|
276
|
+
float* data = NULL;
|
|
277
|
+
size_t vectors_count = 0;
|
|
278
|
+
size_t vector_dimension = 0;
|
|
279
|
+
parse_vectors_from_file(filename, &vectors_count, &vector_dimension, &data);
|
|
280
|
+
vectors_count = vectors_count > 100000 ? 100000 : vectors_count; // Just limit vectors
|
|
281
|
+
|
|
282
|
+
// Test
|
|
283
|
+
test_init(vectors_count, vector_dimension);
|
|
284
|
+
test_add_vector(vectors_count, vector_dimension, data);
|
|
285
|
+
test_find_vector(vectors_count, vector_dimension, data);
|
|
286
|
+
test_remove_vector(vectors_count, vector_dimension, data);
|
|
287
|
+
test_save_load(vectors_count, vector_dimension, data);
|
|
288
|
+
test_view(vectors_count, vector_dimension, data);
|
|
289
|
+
|
|
290
|
+
// Free vectors' buffer
|
|
291
|
+
free(data);
|
|
178
292
|
|
|
179
|
-
free(data); // Remember to free the dynamically allocated memory
|
|
180
|
-
}
|
|
181
293
|
return 0;
|
|
182
294
|
}
|
package/conanfile.py
CHANGED
|
@@ -4,7 +4,7 @@ from conans import ConanFile
|
|
|
4
4
|
class USearchConan(ConanFile):
|
|
5
5
|
|
|
6
6
|
name = 'USearch'
|
|
7
|
-
version = '0.
|
|
7
|
+
version = '0.20.0'
|
|
8
8
|
license = 'Apache 2.0'
|
|
9
9
|
url = 'https://github.com/unum-cloud/usearch'
|
|
10
10
|
description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
|
package/cpp/test.cpp
CHANGED
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
using namespace unum::usearch;
|
|
10
10
|
using namespace unum;
|
|
11
11
|
|
|
12
|
-
using point_id_t = std::int64_t;
|
|
13
|
-
|
|
14
12
|
void expect(bool must_be_true) {
|
|
15
13
|
if (!must_be_true)
|
|
16
14
|
throw std::runtime_error("Failed!");
|
|
@@ -22,6 +20,7 @@ template <typename scalar_at, typename index_at> void test3d(index_at&& index) {
|
|
|
22
20
|
using view_t = span_gt<scalar_t const>;
|
|
23
21
|
using index_t = typename std::remove_reference<index_at>::type;
|
|
24
22
|
using distance_t = typename index_t::distance_t;
|
|
23
|
+
using label_t = typename index_t::label_t;
|
|
25
24
|
|
|
26
25
|
scalar_t vec[3] = {10, 20, 15};
|
|
27
26
|
scalar_t vec_a[3] = {15, 16, 17};
|
|
@@ -31,7 +30,7 @@ template <typename scalar_at, typename index_at> void test3d(index_at&& index) {
|
|
|
31
30
|
index.add(42, view_t{&vec[0], 3ul});
|
|
32
31
|
|
|
33
32
|
// Default approximate search
|
|
34
|
-
|
|
33
|
+
label_t matched_labels[10] = {0};
|
|
35
34
|
distance_t matched_distances[10] = {0};
|
|
36
35
|
std::size_t matched_count = index.search(view_t{&vec[0], 3ul}, 5).dump_to(matched_labels, matched_distances);
|
|
37
36
|
|
|
@@ -52,7 +51,7 @@ template <typename scalar_at, typename index_at> void test3d(index_at&& index) {
|
|
|
52
51
|
// Validate scans
|
|
53
52
|
std::size_t count = 0;
|
|
54
53
|
for (auto member : index) {
|
|
55
|
-
|
|
54
|
+
label_t id = member.label;
|
|
56
55
|
expect(id >= 42 && id <= 44);
|
|
57
56
|
count++;
|
|
58
57
|
}
|
|
@@ -146,11 +145,12 @@ int main(int, char**) {
|
|
|
146
145
|
// static_assert(!std::is_same<index_gt<pearson_correlation_gt<>>::value_type, std::true_type>());
|
|
147
146
|
// static_assert(!std::is_same<index_gt<haversine_gt<>>::value_type, std::true_type>());
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
test3d<float>(index_gt<
|
|
148
|
+
using big_point_id_t = std::int64_t;
|
|
149
|
+
test3d<float>(index_gt<cos_gt<float>, big_point_id_t, std::uint32_t>{});
|
|
150
|
+
test3d<float>(index_gt<l2sq_gt<float>, big_point_id_t, std::uint32_t>{});
|
|
151
151
|
|
|
152
|
-
test3d<double>(index_gt<cos_gt<double>,
|
|
153
|
-
test3d<double>(index_gt<l2sq_gt<double>,
|
|
152
|
+
test3d<double>(index_gt<cos_gt<double>, big_point_id_t, std::uint32_t>{});
|
|
153
|
+
test3d<double>(index_gt<l2sq_gt<double>, big_point_id_t, std::uint32_t>{});
|
|
154
154
|
|
|
155
155
|
test3d<float>(punned_small_t::make(3, metric_kind_t::cos_k));
|
|
156
156
|
test3d<float>(punned_small_t::make(3, metric_kind_t::l2sq_k));
|
|
@@ -161,11 +161,11 @@ int main(int, char**) {
|
|
|
161
161
|
test3d_punned<float>(punned_small_t::make(3, metric_kind_t::cos_k));
|
|
162
162
|
test3d_punned<float>(punned_small_t::make(3, metric_kind_t::l2sq_k));
|
|
163
163
|
|
|
164
|
-
test_sets(index_gt<jaccard_gt<std::int32_t, float>,
|
|
165
|
-
test_sets(index_gt<jaccard_gt<std::int64_t, float>,
|
|
164
|
+
test_sets(index_gt<jaccard_gt<std::int32_t, float>, big_point_id_t, std::uint32_t>{});
|
|
165
|
+
test_sets(index_gt<jaccard_gt<std::int64_t, float>, big_point_id_t, std::uint32_t>{});
|
|
166
166
|
|
|
167
|
-
test_sets_moved<index_gt<jaccard_gt<std::int32_t, float>,
|
|
168
|
-
test_sets_moved<index_gt<jaccard_gt<std::int64_t, float>,
|
|
167
|
+
test_sets_moved<index_gt<jaccard_gt<std::int32_t, float>, big_point_id_t, std::uint32_t>>();
|
|
168
|
+
test_sets_moved<index_gt<jaccard_gt<std::int64_t, float>, big_point_id_t, std::uint32_t>>();
|
|
169
169
|
|
|
170
170
|
return 0;
|
|
171
171
|
}
|
|
@@ -162,6 +162,10 @@ template <std::size_t multiple_ak> std::size_t divide_round_up(std::size_t num)
|
|
|
162
162
|
return (num + multiple_ak - 1) / multiple_ak;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
inline std::size_t divide_round_up(std::size_t num, std::size_t denominator) noexcept {
|
|
166
|
+
return (num + denominator - 1) / denominator;
|
|
167
|
+
}
|
|
168
|
+
|
|
165
169
|
inline std::size_t ceil2(std::size_t v) noexcept {
|
|
166
170
|
v--;
|
|
167
171
|
v |= v >> 1;
|
|
@@ -174,12 +178,16 @@ inline std::size_t ceil2(std::size_t v) noexcept {
|
|
|
174
178
|
return v;
|
|
175
179
|
}
|
|
176
180
|
|
|
177
|
-
template <typename at> void misaligned_store(void* ptr, at v) noexcept {
|
|
181
|
+
template <typename at> void misaligned_store(void* ptr, at v) noexcept {
|
|
182
|
+
static_assert(!std::is_reference<at>::value);
|
|
183
|
+
std::memcpy(ptr, &v, sizeof(at));
|
|
184
|
+
}
|
|
178
185
|
|
|
179
186
|
/// @brief Simply dereferencing misaligned pointers can be dangerous.
|
|
180
187
|
template <typename at> at misaligned_load(void* ptr) noexcept {
|
|
188
|
+
static_assert(!std::is_reference<at>::value);
|
|
181
189
|
at v;
|
|
182
|
-
std::memcpy(&v, ptr, sizeof(
|
|
190
|
+
std::memcpy(&v, ptr, sizeof(at));
|
|
183
191
|
return v;
|
|
184
192
|
}
|
|
185
193
|
|
|
@@ -640,7 +648,9 @@ template <typename allocator_at = std::allocator<char>> class visits_bitset_gt {
|
|
|
640
648
|
|
|
641
649
|
void reset() noexcept {
|
|
642
650
|
if (slots_)
|
|
643
|
-
allocator_t{}.deallocate((byte_t*)slots_, count_ * sizeof(slot_t))
|
|
651
|
+
allocator_t{}.deallocate((byte_t*)slots_, count_ * sizeof(slot_t));
|
|
652
|
+
slots_ = nullptr;
|
|
653
|
+
count_ = 0;
|
|
644
654
|
}
|
|
645
655
|
|
|
646
656
|
bool resize(std::size_t capacity) noexcept {
|
|
@@ -661,10 +671,8 @@ template <typename allocator_at = std::allocator<char>> class visits_bitset_gt {
|
|
|
661
671
|
}
|
|
662
672
|
|
|
663
673
|
visits_bitset_gt(visits_bitset_gt&& other) noexcept {
|
|
664
|
-
slots_ = other.slots_;
|
|
665
|
-
count_ = other.count_;
|
|
666
|
-
other.slots_ = nullptr;
|
|
667
|
-
other.count_ = 0;
|
|
674
|
+
slots_ = exchange(other.slots_, nullptr);
|
|
675
|
+
count_ = exchange(other.count_, 0);
|
|
668
676
|
}
|
|
669
677
|
|
|
670
678
|
visits_bitset_gt& operator=(visits_bitset_gt&& other) noexcept {
|
|
@@ -673,6 +681,9 @@ template <typename allocator_at = std::allocator<char>> class visits_bitset_gt {
|
|
|
673
681
|
return *this;
|
|
674
682
|
}
|
|
675
683
|
|
|
684
|
+
visits_bitset_gt(visits_bitset_gt const&) = delete;
|
|
685
|
+
visits_bitset_gt& operator=(visits_bitset_gt const&) = delete;
|
|
686
|
+
|
|
676
687
|
inline bool test(std::size_t i) const noexcept { return slots_[i / bits_per_slot()] & (1ul << (i & bits_mask())); }
|
|
677
688
|
inline void set(std::size_t i) noexcept { slots_[i / bits_per_slot()] |= (1ul << (i & bits_mask())); }
|
|
678
689
|
|
|
@@ -911,17 +922,18 @@ class sorted_buffer_gt {
|
|
|
911
922
|
if (!new_elements)
|
|
912
923
|
return false;
|
|
913
924
|
|
|
914
|
-
if (size_)
|
|
925
|
+
if (size_)
|
|
915
926
|
std::memcpy(new_elements, elements_, size_ * sizeof(element_t));
|
|
927
|
+
if (elements_)
|
|
916
928
|
allocator.deallocate(elements_, capacity_);
|
|
917
|
-
|
|
929
|
+
|
|
918
930
|
elements_ = new_elements;
|
|
919
931
|
capacity_ = new_capacity;
|
|
920
932
|
return true;
|
|
921
933
|
}
|
|
922
934
|
|
|
923
935
|
inline void insert_reserved(element_t&& element) noexcept {
|
|
924
|
-
std::size_t slot = std::lower_bound(elements_, elements_ + size_, element, &less) - elements_;
|
|
936
|
+
std::size_t slot = size_ ? std::lower_bound(elements_, elements_ + size_, element, &less) - elements_ : 0;
|
|
925
937
|
std::size_t to_move = size_ - slot;
|
|
926
938
|
element_t* source = elements_ + size_ - 1;
|
|
927
939
|
for (; to_move; --to_move, --source)
|
|
@@ -934,7 +946,7 @@ class sorted_buffer_gt {
|
|
|
934
946
|
* @return `true` if the entry was added, `false` if it wasn't relevant enough.
|
|
935
947
|
*/
|
|
936
948
|
inline bool insert(element_t&& element, std::size_t limit) noexcept {
|
|
937
|
-
std::size_t slot = std::lower_bound(elements_, elements_ + size_, element, &less) - elements_;
|
|
949
|
+
std::size_t slot = size_ ? std::lower_bound(elements_, elements_ + size_, element, &less) - elements_ : 0;
|
|
938
950
|
if (slot == limit)
|
|
939
951
|
return false;
|
|
940
952
|
std::size_t to_move = size_ - slot - (size_ == limit);
|
|
@@ -975,10 +987,10 @@ class usearch_pack_m uint40_t {
|
|
|
975
987
|
|
|
976
988
|
public:
|
|
977
989
|
inline uint40_t() noexcept { std::memset(octets, 0, 5); }
|
|
978
|
-
inline uint40_t(std::uint32_t n) noexcept { std::memcpy(octets
|
|
979
|
-
inline uint40_t(std::uint64_t n) noexcept { std::memcpy(octets, (char*)&n
|
|
990
|
+
inline uint40_t(std::uint32_t n) noexcept { std::memcpy(octets, (char*)&n, 4); }
|
|
991
|
+
inline uint40_t(std::uint64_t n) noexcept { std::memcpy(octets, (char*)&n, 5); }
|
|
980
992
|
#if defined(USEARCH_DEFINED_CLANG) && defined(USEARCH_DEFINED_APPLE)
|
|
981
|
-
inline uint40_t(std::size_t n) noexcept { std::memcpy(octets, (char*)&n
|
|
993
|
+
inline uint40_t(std::size_t n) noexcept { std::memcpy(octets, (char*)&n, 5); }
|
|
982
994
|
#endif
|
|
983
995
|
|
|
984
996
|
uint40_t(uint40_t&&) = default;
|
|
@@ -986,24 +998,40 @@ class usearch_pack_m uint40_t {
|
|
|
986
998
|
uint40_t& operator=(uint40_t&&) = default;
|
|
987
999
|
uint40_t& operator=(uint40_t const&) = default;
|
|
988
1000
|
|
|
989
|
-
inline uint40_t& operator+=(std::uint32_t
|
|
990
|
-
std::uint32_t& tail = *reinterpret_cast<std::uint32_t*>(octets
|
|
991
|
-
octets[
|
|
992
|
-
tail +=
|
|
1001
|
+
inline uint40_t& operator+=(std::uint32_t n) noexcept {
|
|
1002
|
+
std::uint32_t& tail = *reinterpret_cast<std::uint32_t*>(octets);
|
|
1003
|
+
octets[4] += static_cast<unsigned char>((tail + n) < tail);
|
|
1004
|
+
tail += n;
|
|
1005
|
+
return *this;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
inline uint40_t& operator+=(std::size_t n) noexcept {
|
|
1009
|
+
unsigned char* n_octets = reinterpret_cast<unsigned char*>(&n);
|
|
1010
|
+
std::uint32_t& n_tail = *reinterpret_cast<std::uint32_t*>(&n);
|
|
1011
|
+
std::uint32_t& tail = *reinterpret_cast<std::uint32_t*>(octets);
|
|
1012
|
+
octets[4] += static_cast<unsigned char>((tail + n_tail) < tail);
|
|
1013
|
+
tail += n_tail;
|
|
1014
|
+
octets[4] += n_octets[4];
|
|
993
1015
|
return *this;
|
|
994
1016
|
}
|
|
995
1017
|
|
|
1018
|
+
inline uint40_t operator+(std::size_t n) noexcept {
|
|
1019
|
+
uint40_t other(*this);
|
|
1020
|
+
other += n;
|
|
1021
|
+
return other;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
996
1024
|
inline operator std::size_t() const noexcept {
|
|
997
1025
|
std::size_t result = 0;
|
|
998
|
-
std::memcpy((char*)&result
|
|
1026
|
+
std::memcpy((char*)&result, octets, 5);
|
|
999
1027
|
return result;
|
|
1000
1028
|
}
|
|
1001
1029
|
|
|
1002
|
-
inline uint40_t& operator++() noexcept { return *this +=
|
|
1030
|
+
inline uint40_t& operator++() noexcept { return *this += 1u; }
|
|
1003
1031
|
|
|
1004
1032
|
inline uint40_t operator++(int) noexcept {
|
|
1005
1033
|
uint40_t old = *this;
|
|
1006
|
-
*this +=
|
|
1034
|
+
*this += 1u;
|
|
1007
1035
|
return old;
|
|
1008
1036
|
}
|
|
1009
1037
|
};
|
|
@@ -1317,11 +1345,13 @@ class index_gt {
|
|
|
1317
1345
|
struct member_ref_t {
|
|
1318
1346
|
misaligned_ref_gt<label_t> label;
|
|
1319
1347
|
vector_view_t vector;
|
|
1348
|
+
id_t id;
|
|
1320
1349
|
};
|
|
1321
1350
|
|
|
1322
1351
|
struct member_cref_t {
|
|
1323
1352
|
label_t label;
|
|
1324
1353
|
vector_view_t vector;
|
|
1354
|
+
id_t id;
|
|
1325
1355
|
};
|
|
1326
1356
|
|
|
1327
1357
|
template <typename ref_at, typename index_at> class member_iterator_gt {
|
|
@@ -1341,7 +1371,11 @@ class index_gt {
|
|
|
1341
1371
|
using pointer = void;
|
|
1342
1372
|
using reference = ref_t;
|
|
1343
1373
|
|
|
1344
|
-
reference operator*() const noexcept {
|
|
1374
|
+
reference operator*() const noexcept {
|
|
1375
|
+
node_t node = index_->node_with_id_(offset_);
|
|
1376
|
+
return {node.label(), node.vector_view(), static_cast<id_t>(offset_)};
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1345
1379
|
member_iterator_gt operator++(int) noexcept { return member_iterator_gt(index_, offset_ + 1); }
|
|
1346
1380
|
member_iterator_gt operator--(int) noexcept { return member_iterator_gt(index_, offset_ - 1); }
|
|
1347
1381
|
member_iterator_gt operator+(difference_type d) noexcept { return member_iterator_gt(index_, offset_ + d); }
|
|
@@ -1453,9 +1487,9 @@ class index_gt {
|
|
|
1453
1487
|
node_t(node_t const&) = default;
|
|
1454
1488
|
node_t& operator=(node_t const&) = default;
|
|
1455
1489
|
|
|
1456
|
-
label_t label() const noexcept { return
|
|
1457
|
-
dim_t dim() const noexcept { return
|
|
1458
|
-
level_t level() const noexcept { return
|
|
1490
|
+
misaligned_ref_gt<label_t> label() const noexcept { return {tape_}; }
|
|
1491
|
+
misaligned_ref_gt<dim_t> dim() const noexcept { return {tape_ + sizeof(label_t)}; }
|
|
1492
|
+
misaligned_ref_gt<level_t> level() const noexcept { return {tape_ + sizeof(label_t) + sizeof(dim_t)}; }
|
|
1459
1493
|
|
|
1460
1494
|
void label(label_t v) noexcept { return misaligned_store<label_t>(tape_, v); }
|
|
1461
1495
|
void dim(dim_t v) noexcept { return misaligned_store<dim_t>(tape_ + sizeof(label_t), v); }
|
|
@@ -1605,8 +1639,11 @@ class index_gt {
|
|
|
1605
1639
|
|
|
1606
1640
|
if (nodes_)
|
|
1607
1641
|
node_allocator_t{}.deallocate(exchange(nodes_, nullptr), limits_.elements);
|
|
1608
|
-
if (contexts_)
|
|
1642
|
+
if (contexts_) {
|
|
1643
|
+
for (std::size_t i = 0; i != limits_.threads(); ++i)
|
|
1644
|
+
contexts_[i].~context_t();
|
|
1609
1645
|
context_allocator_t{}.deallocate(exchange(contexts_, nullptr), limits_.threads());
|
|
1646
|
+
}
|
|
1610
1647
|
limits_ = index_limits_t{0, 0};
|
|
1611
1648
|
capacity_ = 0;
|
|
1612
1649
|
reset_view_();
|
|
@@ -1730,7 +1767,7 @@ class index_gt {
|
|
|
1730
1767
|
};
|
|
1731
1768
|
|
|
1732
1769
|
struct match_t {
|
|
1733
|
-
member_cref_t
|
|
1770
|
+
member_cref_t member;
|
|
1734
1771
|
distance_t distance;
|
|
1735
1772
|
};
|
|
1736
1773
|
|
|
@@ -1763,14 +1800,23 @@ class index_gt {
|
|
|
1763
1800
|
|
|
1764
1801
|
inline operator std::size_t() const noexcept { return count; }
|
|
1765
1802
|
inline std::size_t size() const noexcept { return count; }
|
|
1766
|
-
inline match_t operator[](std::size_t i) const noexcept {
|
|
1803
|
+
inline match_t operator[](std::size_t i) const noexcept { return at(i); }
|
|
1804
|
+
inline bool contains(label_t label) const noexcept {
|
|
1805
|
+
for (std::size_t i = 0; i != count; ++i)
|
|
1806
|
+
if (at(i).member.label == label)
|
|
1807
|
+
return true;
|
|
1808
|
+
return false;
|
|
1809
|
+
}
|
|
1810
|
+
inline match_t at(std::size_t i) const noexcept {
|
|
1767
1811
|
candidate_t const* top_ordered = top_.data();
|
|
1768
|
-
|
|
1812
|
+
candidate_t candidate = top_ordered[i];
|
|
1813
|
+
node_t node = index_.node_with_id_(candidate.id);
|
|
1814
|
+
return {member_cref_t{node.label(), node.vector_view(), candidate.id}, candidate.distance};
|
|
1769
1815
|
}
|
|
1770
1816
|
inline std::size_t dump_to(label_t* labels, distance_t* distances) const noexcept {
|
|
1771
1817
|
for (std::size_t i = 0; i != count; ++i) {
|
|
1772
1818
|
match_t result = operator[](i);
|
|
1773
|
-
labels[i] = result.
|
|
1819
|
+
labels[i] = result.member.label;
|
|
1774
1820
|
distances[i] = result.distance;
|
|
1775
1821
|
}
|
|
1776
1822
|
return count;
|
|
@@ -1778,7 +1824,7 @@ class index_gt {
|
|
|
1778
1824
|
inline std::size_t dump_to(label_t* labels) const noexcept {
|
|
1779
1825
|
for (std::size_t i = 0; i != count; ++i) {
|
|
1780
1826
|
match_t result = operator[](i);
|
|
1781
|
-
labels[i] = result.
|
|
1827
|
+
labels[i] = result.member.label;
|
|
1782
1828
|
}
|
|
1783
1829
|
return count;
|
|
1784
1830
|
}
|
|
@@ -1849,6 +1895,7 @@ class index_gt {
|
|
|
1849
1895
|
// TODO: Handle out of memory conditions
|
|
1850
1896
|
search_to_insert_(closest_id, vector, level, config.expansion, context);
|
|
1851
1897
|
closest_id = connect_new_node_(new_id, level, context);
|
|
1898
|
+
reconnect_neighbor_nodes_(new_id, level, context);
|
|
1852
1899
|
}
|
|
1853
1900
|
|
|
1854
1901
|
// Normalize stats
|
|
@@ -1863,15 +1910,23 @@ class index_gt {
|
|
|
1863
1910
|
return result;
|
|
1864
1911
|
}
|
|
1865
1912
|
|
|
1913
|
+
struct dummy_predicate_t {
|
|
1914
|
+
constexpr bool operator()(match_t const&) const noexcept { return true; }
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1866
1917
|
/**
|
|
1867
1918
|
* @brief Searches for the closest elements to the given ::query. Thread-safe.
|
|
1868
1919
|
*
|
|
1869
1920
|
* @param[in] query Contiguous range of scalars forming a vector view.
|
|
1870
1921
|
* @param[in] wanted The upper bound for the number of results to return.
|
|
1871
1922
|
* @param[in] config Configuration options for this specific operation.
|
|
1923
|
+
* @param[in] predicate Optional filtering predicate for `member_cref_t`.
|
|
1872
1924
|
* @return Smart object referencing temporary memory. Valid until next `search()` or `add()`.
|
|
1873
1925
|
*/
|
|
1874
|
-
|
|
1926
|
+
template <typename predicate_at = dummy_predicate_t>
|
|
1927
|
+
search_result_t search( //
|
|
1928
|
+
vector_view_t query, std::size_t wanted, search_config_t config = {},
|
|
1929
|
+
predicate_at&& predicate = dummy_predicate_t{}) const noexcept {
|
|
1875
1930
|
|
|
1876
1931
|
context_t& context = contexts_[config.thread];
|
|
1877
1932
|
top_candidates_t& top = context.top_candidates;
|
|
@@ -1886,7 +1941,7 @@ class index_gt {
|
|
|
1886
1941
|
if (config.exact) {
|
|
1887
1942
|
if (!top.reserve(wanted))
|
|
1888
1943
|
return result.failed("Out of memory!");
|
|
1889
|
-
search_exact_(query, wanted, context);
|
|
1944
|
+
search_exact_(query, wanted, context, std::forward<predicate_at>(predicate));
|
|
1890
1945
|
} else {
|
|
1891
1946
|
next_candidates_t& next = context.next_candidates;
|
|
1892
1947
|
std::size_t expansion = (std::max)(config.expansion, wanted);
|
|
@@ -1897,7 +1952,7 @@ class index_gt {
|
|
|
1897
1952
|
|
|
1898
1953
|
id_t closest_id = search_for_one_(entry_id_, query, max_level_, 0, context);
|
|
1899
1954
|
// For bottom layer we need a more optimized procedure
|
|
1900
|
-
if (!search_to_find_in_base_(closest_id, query, expansion, context))
|
|
1955
|
+
if (!search_to_find_in_base_(closest_id, query, expansion, context, std::forward<predicate_at>(predicate)))
|
|
1901
1956
|
return result.failed("Out of memory!");
|
|
1902
1957
|
}
|
|
1903
1958
|
|
|
@@ -1911,9 +1966,10 @@ class index_gt {
|
|
|
1911
1966
|
return result;
|
|
1912
1967
|
}
|
|
1913
1968
|
|
|
1969
|
+
template <typename predicate_at = dummy_predicate_t>
|
|
1914
1970
|
search_result_t search_around( //
|
|
1915
1971
|
id_t hint, vector_view_t query, std::size_t wanted, //
|
|
1916
|
-
search_config_t config = {}) const noexcept {
|
|
1972
|
+
search_config_t config = {}, predicate_at&& predicate = dummy_predicate_t{}) const noexcept {
|
|
1917
1973
|
|
|
1918
1974
|
context_t& context = contexts_[config.thread];
|
|
1919
1975
|
top_candidates_t& top = context.top_candidates;
|
|
@@ -1933,7 +1989,7 @@ class index_gt {
|
|
|
1933
1989
|
result.measurements = context.measurements_count;
|
|
1934
1990
|
result.cycles = context.iteration_cycles;
|
|
1935
1991
|
|
|
1936
|
-
search_to_find_in_base_(hint, query, expansion, context);
|
|
1992
|
+
search_to_find_in_base_(hint, query, expansion, context, std::forward<predicate_at>(predicate));
|
|
1937
1993
|
top.sort_ascending();
|
|
1938
1994
|
top.shrink(wanted);
|
|
1939
1995
|
|
|
@@ -1944,6 +2000,10 @@ class index_gt {
|
|
|
1944
2000
|
return result;
|
|
1945
2001
|
}
|
|
1946
2002
|
|
|
2003
|
+
#pragma endregion
|
|
2004
|
+
|
|
2005
|
+
#pragma region Metadata
|
|
2006
|
+
|
|
1947
2007
|
struct stats_t {
|
|
1948
2008
|
std::size_t nodes;
|
|
1949
2009
|
std::size_t edges;
|
|
@@ -1958,7 +2018,7 @@ class index_gt {
|
|
|
1958
2018
|
node_t node = node_with_id_(i);
|
|
1959
2019
|
std::size_t max_edges = node.level() * config_.connectivity + base_level_multiple_() * config_.connectivity;
|
|
1960
2020
|
std::size_t edges = 0;
|
|
1961
|
-
for (level_t level = 0; level
|
|
2021
|
+
for (level_t level = 0; level <= node.level(); ++level)
|
|
1962
2022
|
edges += neighbors_(node, level).size();
|
|
1963
2023
|
|
|
1964
2024
|
result.allocated_bytes += node_bytes_(node);
|
|
@@ -2007,6 +2067,8 @@ class index_gt {
|
|
|
2007
2067
|
return total;
|
|
2008
2068
|
}
|
|
2009
2069
|
|
|
2070
|
+
std::size_t memory_usage_per_node(dim_t dim, level_t level) const noexcept { return node_bytes_(dim, level); }
|
|
2071
|
+
|
|
2010
2072
|
void change_metric(metric_t const& m) noexcept {
|
|
2011
2073
|
metric_ = m;
|
|
2012
2074
|
for (std::size_t i = 0; i != limits_.threads(); ++i)
|
|
@@ -2285,7 +2347,6 @@ class index_gt {
|
|
|
2285
2347
|
std::size_t node_vector_bytes = dim * sizeof(scalar_t);
|
|
2286
2348
|
nodes_[i] = node_t{tape, (scalar_t*)(tape + node_bytes - node_vector_bytes)};
|
|
2287
2349
|
progress += node_bytes;
|
|
2288
|
-
max_level_ = (std::max)(max_level_, level);
|
|
2289
2350
|
}
|
|
2290
2351
|
|
|
2291
2352
|
return {};
|
|
@@ -2396,7 +2457,6 @@ class index_gt {
|
|
|
2396
2457
|
|
|
2397
2458
|
node_t new_node = node_with_id_(new_id);
|
|
2398
2459
|
top_candidates_t& top = context.top_candidates;
|
|
2399
|
-
std::size_t const connectivity_max = level ? config_.connectivity : pre_.connectivity_max_base;
|
|
2400
2460
|
|
|
2401
2461
|
// Outgoing links from `new_id`:
|
|
2402
2462
|
neighbors_ref_t new_neighbors = neighbors_(new_node, level);
|
|
@@ -2411,7 +2471,17 @@ class index_gt {
|
|
|
2411
2471
|
}
|
|
2412
2472
|
}
|
|
2413
2473
|
|
|
2474
|
+
return new_neighbors[0];
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
void reconnect_neighbor_nodes_(id_t new_id, level_t level, context_t& context) usearch_noexcept_m {
|
|
2478
|
+
|
|
2479
|
+
node_t new_node = node_with_id_(new_id);
|
|
2480
|
+
top_candidates_t& top = context.top_candidates;
|
|
2481
|
+
neighbors_ref_t new_neighbors = neighbors_(new_node, level);
|
|
2482
|
+
|
|
2414
2483
|
// Reverse links from the neighbors:
|
|
2484
|
+
std::size_t const connectivity_max = level ? config_.connectivity : pre_.connectivity_max_base;
|
|
2415
2485
|
for (id_t close_id : new_neighbors) {
|
|
2416
2486
|
node_t close_node = node_with_id_(close_id);
|
|
2417
2487
|
node_lock_t close_lock = node_lock_(close_id);
|
|
@@ -2441,8 +2511,6 @@ class index_gt {
|
|
|
2441
2511
|
for (std::size_t idx = 0; idx != top_view.size(); idx++)
|
|
2442
2512
|
close_header.push_back(top_view[idx].id);
|
|
2443
2513
|
}
|
|
2444
|
-
|
|
2445
|
-
return new_neighbors[0];
|
|
2446
2514
|
}
|
|
2447
2515
|
|
|
2448
2516
|
level_t choose_random_level_(std::default_random_engine& level_generator) const noexcept {
|
|
@@ -2538,8 +2606,10 @@ class index_gt {
|
|
|
2538
2606
|
* Doesn't lock any nodes, assuming read-only simultaneous access.
|
|
2539
2607
|
* @return `true` if procedure succeeded, `false` if run out of memory.
|
|
2540
2608
|
*/
|
|
2609
|
+
template <typename predicate_at>
|
|
2541
2610
|
bool search_to_find_in_base_( //
|
|
2542
|
-
id_t start_id, vector_view_t query, std::size_t expansion, context_t& context
|
|
2611
|
+
id_t start_id, vector_view_t query, std::size_t expansion, context_t& context,
|
|
2612
|
+
predicate_at&& predicate) const noexcept {
|
|
2543
2613
|
|
|
2544
2614
|
visits_bitset_t& visits = context.visits;
|
|
2545
2615
|
next_candidates_t& next = context.next_candidates; // pop min, push
|
|
@@ -2573,14 +2643,19 @@ class index_gt {
|
|
|
2573
2643
|
continue;
|
|
2574
2644
|
|
|
2575
2645
|
visits.set(successor_id);
|
|
2576
|
-
|
|
2646
|
+
node_t successor = node_with_id_(successor_id);
|
|
2647
|
+
distance_t successor_dist = context.measure(query, successor);
|
|
2577
2648
|
|
|
2578
2649
|
if (top.size() < top_limit || successor_dist < radius) {
|
|
2579
2650
|
// This can substantially grow our priority queue:
|
|
2580
2651
|
next.insert({-successor_dist, successor_id});
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2652
|
+
if (predicate( //
|
|
2653
|
+
match_t{member_cref_t{successor.label(), successor.vector_view(), successor_id},
|
|
2654
|
+
successor_dist})) {
|
|
2655
|
+
// This will automatically evict poor matches:
|
|
2656
|
+
top.insert({successor_dist, successor_id}, top_limit);
|
|
2657
|
+
radius = top.top().distance;
|
|
2658
|
+
}
|
|
2584
2659
|
}
|
|
2585
2660
|
}
|
|
2586
2661
|
}
|
|
@@ -2591,12 +2666,21 @@ class index_gt {
|
|
|
2591
2666
|
/**
|
|
2592
2667
|
* @brief Iterates through all managed vectors, without actually touching the index.
|
|
2593
2668
|
*/
|
|
2594
|
-
|
|
2669
|
+
template <typename predicate_at>
|
|
2670
|
+
void search_exact_( //
|
|
2671
|
+
vector_view_t query, std::size_t count, context_t& context, //
|
|
2672
|
+
predicate_at&& predicate) const noexcept {
|
|
2673
|
+
|
|
2595
2674
|
top_candidates_t& top = context.top_candidates;
|
|
2596
2675
|
top.clear();
|
|
2597
2676
|
top.reserve(count);
|
|
2598
|
-
for (std::size_t i = 0; i != size(); ++i)
|
|
2599
|
-
|
|
2677
|
+
for (std::size_t i = 0; i != size(); ++i) {
|
|
2678
|
+
id_t id = static_cast<id_t>(i);
|
|
2679
|
+
node_t node = node_with_id_(i);
|
|
2680
|
+
distance_t distance = context.measure(query, node);
|
|
2681
|
+
if (predicate(match_t{member_cref_t{node.label(), node.vector_view(), id}, distance}))
|
|
2682
|
+
top.insert(candidate_t{distance, id}, count);
|
|
2683
|
+
}
|
|
2600
2684
|
}
|
|
2601
2685
|
|
|
2602
2686
|
void prefetch_neighbors_(neighbors_ref_t, visits_bitset_t const&) const noexcept {}
|
|
@@ -295,6 +295,8 @@ class f8_bits_t {
|
|
|
295
295
|
inline explicit operator std::int32_t() const noexcept { return int8_; }
|
|
296
296
|
inline explicit operator std::int64_t() const noexcept { return int8_; }
|
|
297
297
|
|
|
298
|
+
inline f8_bits_t(f16_t v)
|
|
299
|
+
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
|
|
298
300
|
inline f8_bits_t(float v)
|
|
299
301
|
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
|
|
300
302
|
inline f8_bits_t(double v)
|