usearch 0.20.0 → 0.22.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/c/lib.cpp CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  #include <usearch/index_punned_dense.hpp>
4
4
 
5
+ #ifndef USEARCH_EXPORT
6
+ #define USEARCH_EXPORT
7
+ #endif
8
+
5
9
  extern "C" {
6
10
  #include "usearch.h"
7
11
  }
@@ -88,7 +92,7 @@ index_punned_dense_metric_t udf(metric_kind_t kind, usearch_metric_t raw_ptr) {
88
92
 
89
93
  extern "C" {
90
94
 
91
- usearch_index_t usearch_init(usearch_init_options_t* options, usearch_error_t* error) {
95
+ USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, usearch_error_t* error) {
92
96
 
93
97
  assert(options && error);
94
98
 
@@ -108,48 +112,48 @@ usearch_index_t usearch_init(usearch_init_options_t* options, usearch_error_t* e
108
112
  return result_ptr;
109
113
  }
110
114
 
111
- void usearch_free(usearch_index_t index, usearch_error_t*) { delete reinterpret_cast<index_t*>(index); }
115
+ USEARCH_EXPORT void usearch_free(usearch_index_t index, usearch_error_t*) { delete reinterpret_cast<index_t*>(index); }
112
116
 
113
- void usearch_save(usearch_index_t index, char const* path, usearch_error_t* error) {
117
+ USEARCH_EXPORT void usearch_save(usearch_index_t index, char const* path, usearch_error_t* error) {
114
118
  serialization_result_t result = reinterpret_cast<index_t*>(index)->save(path);
115
119
  if (!result)
116
120
  *error = result.error.what();
117
121
  }
118
122
 
119
- void usearch_load(usearch_index_t index, char const* path, usearch_error_t* error) {
123
+ USEARCH_EXPORT void usearch_load(usearch_index_t index, char const* path, usearch_error_t* error) {
120
124
  serialization_result_t result = reinterpret_cast<index_t*>(index)->load(path);
121
125
  if (!result)
122
126
  *error = result.error.what();
123
127
  }
124
128
 
125
- void usearch_view(usearch_index_t index, char const* path, usearch_error_t* error) {
129
+ USEARCH_EXPORT void usearch_view(usearch_index_t index, char const* path, usearch_error_t* error) {
126
130
  serialization_result_t result = reinterpret_cast<index_t*>(index)->view(path);
127
131
  if (!result)
128
132
  *error = result.error.what();
129
133
  }
130
134
 
131
- size_t usearch_size(usearch_index_t index, usearch_error_t*) { //
135
+ USEARCH_EXPORT size_t usearch_size(usearch_index_t index, usearch_error_t*) { //
132
136
  return reinterpret_cast<index_t*>(index)->size();
133
137
  }
134
138
 
135
- size_t usearch_capacity(usearch_index_t index, usearch_error_t*) {
139
+ USEARCH_EXPORT size_t usearch_capacity(usearch_index_t index, usearch_error_t*) {
136
140
  return reinterpret_cast<index_t*>(index)->capacity();
137
141
  }
138
142
 
139
- size_t usearch_dimensions(usearch_index_t index, usearch_error_t*) {
143
+ USEARCH_EXPORT size_t usearch_dimensions(usearch_index_t index, usearch_error_t*) {
140
144
  return reinterpret_cast<index_t*>(index)->dimensions();
141
145
  }
142
146
 
143
- size_t usearch_connectivity(usearch_index_t index, usearch_error_t*) {
147
+ USEARCH_EXPORT size_t usearch_connectivity(usearch_index_t index, usearch_error_t*) {
144
148
  return reinterpret_cast<index_t*>(index)->connectivity();
145
149
  }
146
150
 
147
- void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t*) {
151
+ USEARCH_EXPORT void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t*) {
148
152
  // TODO: Consider returning the new capacity.
149
153
  reinterpret_cast<index_t*>(index)->reserve(capacity);
150
154
  }
151
155
 
152
- void usearch_add( //
156
+ USEARCH_EXPORT void usearch_add( //
153
157
  usearch_index_t index, usearch_label_t label, void const* vector, usearch_scalar_kind_t kind, //
154
158
  usearch_error_t* error) {
155
159
  add_result_t result = add_(reinterpret_cast<index_t*>(index), label, vector, to_native_scalar(kind));
@@ -157,11 +161,11 @@ void usearch_add(
157
161
  *error = result.error.what();
158
162
  }
159
163
 
160
- bool usearch_contains(usearch_index_t index, usearch_label_t label, usearch_error_t*) {
164
+ USEARCH_EXPORT bool usearch_contains(usearch_index_t index, usearch_label_t label, usearch_error_t*) {
161
165
  return reinterpret_cast<index_t*>(index)->contains(label);
162
166
  }
163
167
 
164
- size_t usearch_search( //
168
+ USEARCH_EXPORT size_t usearch_search( //
165
169
  usearch_index_t index, void const* vector, usearch_scalar_kind_t kind, size_t results_limit, //
166
170
  usearch_label_t* found_labels, usearch_distance_t* found_distances, usearch_error_t* error) {
167
171
  search_result_t result = search_(reinterpret_cast<index_t*>(index), vector, to_native_scalar(kind), results_limit);
@@ -173,13 +177,13 @@ size_t usearch_search(
173
177
  return result.dump_to(found_labels, found_distances);
174
178
  }
175
179
 
176
- bool usearch_get( //
180
+ USEARCH_EXPORT bool usearch_get( //
177
181
  usearch_index_t index, usearch_label_t label, //
178
182
  void* vector, usearch_scalar_kind_t kind, usearch_error_t*) {
179
183
  return get_(reinterpret_cast<index_t*>(index), label, vector, to_native_scalar(kind));
180
184
  }
181
185
 
182
- void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t* error) {
186
+ USEARCH_EXPORT void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t* error) {
183
187
  if (error != nullptr)
184
188
  *error = "USearch does not support removal of elements yet.";
185
189
  }
package/c/usearch.h CHANGED
@@ -4,17 +4,20 @@
4
4
  #ifdef __cplusplus
5
5
  extern "C" {
6
6
  #endif
7
+ #ifndef USEARCH_EXPORT
8
+ #define USEARCH_EXPORT
9
+ #endif
7
10
  #include <stdbool.h> // `bool`
8
11
  #include <stdint.h> // `size_t`
9
12
 
10
- typedef void* usearch_index_t;
11
- typedef uint32_t usearch_label_t;
12
- typedef float usearch_distance_t;
13
- typedef char const* usearch_error_t;
13
+ USEARCH_EXPORT typedef void* usearch_index_t;
14
+ USEARCH_EXPORT typedef uint32_t usearch_label_t;
15
+ USEARCH_EXPORT typedef float usearch_distance_t;
16
+ USEARCH_EXPORT typedef char const* usearch_error_t;
14
17
 
15
- typedef usearch_distance_t (*usearch_metric_t)(void const*, void const*);
18
+ USEARCH_EXPORT typedef usearch_distance_t (*usearch_metric_t)(void const*, void const*);
16
19
 
17
- typedef enum usearch_metric_kind_t {
20
+ USEARCH_EXPORT typedef enum usearch_metric_kind_t {
18
21
  usearch_metric_ip_k = 0,
19
22
  usearch_metric_l2sq_k,
20
23
  usearch_metric_cos_k,
@@ -27,7 +30,7 @@ typedef enum usearch_metric_kind_t {
27
30
  usearch_metric_unknown_k,
28
31
  } usearch_metric_kind_t;
29
32
 
30
- typedef enum usearch_scalar_kind_t {
33
+ USEARCH_EXPORT typedef enum usearch_scalar_kind_t {
31
34
  usearch_scalar_f32_k = 0,
32
35
  usearch_scalar_f64_k,
33
36
  usearch_scalar_f16_k,
@@ -36,7 +39,7 @@ typedef enum usearch_scalar_kind_t {
36
39
  usearch_scalar_unknown_k,
37
40
  } usearch_scalar_kind_t;
38
41
 
39
- typedef struct usearch_init_options_t {
42
+ USEARCH_EXPORT typedef struct usearch_init_options_t {
40
43
 
41
44
  usearch_metric_kind_t metric_kind;
42
45
  usearch_metric_t metric;
@@ -48,39 +51,39 @@ typedef struct usearch_init_options_t {
48
51
  size_t expansion_search;
49
52
  } usearch_init_options_t;
50
53
 
51
- usearch_index_t usearch_init(usearch_init_options_t*, usearch_error_t*);
52
- void usearch_free(usearch_index_t, usearch_error_t*);
54
+ USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t*, usearch_error_t*);
55
+ USEARCH_EXPORT void usearch_free(usearch_index_t, usearch_error_t*);
53
56
 
54
- void usearch_save(usearch_index_t, char const* path, usearch_error_t*);
55
- void usearch_load(usearch_index_t, char const* path, usearch_error_t*);
56
- void usearch_view(usearch_index_t, char const* path, usearch_error_t*);
57
+ USEARCH_EXPORT void usearch_save(usearch_index_t, char const* path, usearch_error_t*);
58
+ USEARCH_EXPORT void usearch_load(usearch_index_t, char const* path, usearch_error_t*);
59
+ USEARCH_EXPORT void usearch_view(usearch_index_t, char const* path, usearch_error_t*);
57
60
 
58
- size_t usearch_size(usearch_index_t, usearch_error_t*);
59
- size_t usearch_capacity(usearch_index_t, usearch_error_t*);
60
- size_t usearch_dimensions(usearch_index_t, usearch_error_t*);
61
- size_t usearch_connectivity(usearch_index_t, usearch_error_t*);
61
+ USEARCH_EXPORT size_t usearch_size(usearch_index_t, usearch_error_t*);
62
+ USEARCH_EXPORT size_t usearch_capacity(usearch_index_t, usearch_error_t*);
63
+ USEARCH_EXPORT size_t usearch_dimensions(usearch_index_t, usearch_error_t*);
64
+ USEARCH_EXPORT size_t usearch_connectivity(usearch_index_t, usearch_error_t*);
62
65
 
63
- void usearch_reserve(usearch_index_t, size_t capacity, usearch_error_t*);
66
+ USEARCH_EXPORT void usearch_reserve(usearch_index_t, size_t capacity, usearch_error_t*);
64
67
 
65
- void usearch_add( //
68
+ USEARCH_EXPORT void usearch_add( //
66
69
  usearch_index_t, usearch_label_t, void const* vector, usearch_scalar_kind_t vector_kind, //
67
70
  usearch_error_t*);
68
71
 
69
- bool usearch_contains(usearch_index_t, usearch_label_t, usearch_error_t*);
72
+ USEARCH_EXPORT bool usearch_contains(usearch_index_t, usearch_label_t, usearch_error_t*);
70
73
 
71
74
  /**
72
75
  * @brief Performs k-Approximate Nearest Neighbors Search.
73
76
  * @return Number of found matches.
74
77
  */
75
- size_t usearch_search( //
78
+ USEARCH_EXPORT size_t usearch_search( //
76
79
  usearch_index_t, void const* query_vector, usearch_scalar_kind_t query_kind, size_t results_limit, //
77
80
  usearch_label_t* found_labels, usearch_distance_t* found_distances, usearch_error_t*);
78
81
 
79
- bool usearch_get( //
82
+ USEARCH_EXPORT bool usearch_get( //
80
83
  usearch_index_t, usearch_label_t, //
81
84
  void* vector, usearch_scalar_kind_t vector_kind, usearch_error_t*);
82
85
 
83
- void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t*);
86
+ USEARCH_EXPORT void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t*);
84
87
 
85
88
  #ifdef __cplusplus
86
89
  }
@@ -0,0 +1,8 @@
1
+ include(FetchContent)
2
+ FetchContent_Declare(
3
+ emscripten
4
+ GIT_REPOSITORY https://github.com/emscripten-core/emscripten
5
+ GIT_TAG main
6
+ )
7
+ FetchContent_MakeAvailable(emscripten)
8
+ include_directories(${emscripten_SOURCE_DIR}/include)
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.20.0'
7
+ version = '0.22.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'
@@ -13,7 +13,9 @@ endif()
13
13
  # https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html
14
14
  if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
15
15
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
16
- set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
16
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
17
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -g")
18
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fsanitize=alignment -fsanitize=undefined")
17
19
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wno-conversion -Wno-unknown-pragmas")
18
20
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
19
21
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=1")
@@ -22,7 +24,9 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
22
24
 
23
25
  elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
24
26
  set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
25
- set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
27
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
28
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -g")
29
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fsanitize=alignment -fsanitize=undefined")
26
30
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wfatal-errors")
27
31
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
28
32
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
package/cpp/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # USearch C++ Inteface
2
+
3
+ ## Installation
4
+
5
+ To use in a C++ project simply copy the `include/usearch/index.hpp` header into your project.
6
+ Alternatively fetch it with CMake:
7
+
8
+ ```cmake
9
+ FetchContent_Declare(usearch GIT_REPOSITORY https://github.com/unum-cloud/usearch.git)
10
+ FetchContent_MakeAvailable(usearch)
11
+ ```
12
+
13
+ ## Quickstart
14
+
15
+ Once included, the low-level C++11 interface is as simple as it gets: `reserve()`, `add()`, `search()`, `size()`, `capacity()`, `save()`, `load()`, `view()`.
16
+ This covers 90% of use-cases.
17
+
18
+ ```c++
19
+ using namespace unum::usearch;
20
+
21
+ index_gt<cos_gt<float>> index;
22
+ float vec[3] = {0.1, 0.3, 0.2};
23
+
24
+ index.reserve(10);
25
+ index.add(/* label: */ 42, /* vector: */ {&vec[0], 3});
26
+ auto results = index.search(/* query: */ {&vec[0], 3}, 5 /* neighbors */);
27
+
28
+ for (std::size_t i = 0; i != results.size(); ++i)
29
+ results[i].element.label, results[i].element.vector, results[i].distance;
30
+ ```
31
+
32
+ The `add` is thread-safe for concurrent index construction.
33
+
34
+ ## Serialization
35
+
36
+ ```c++
37
+ index.save("index.usearch");
38
+ index.load("index.usearch"); // Copying from disk
39
+ index.view("index.usearch"); // Memory-mapping from disk
40
+ ```
41
+
42
+ ## User-Defined Metrics in C++
43
+
44
+ For advanced users, more compile-time abstractions are available.
45
+
46
+ ```cpp
47
+ template <typename metric_at = ip_gt<float>, //
48
+ typename label_at = std::size_t, // `uint32_t`, `uuid_t`...
49
+ typename id_at = std::uint32_t, // `uint40_t`, `uint64_t`...
50
+ typename scalar_at = float, // `double`, `half`, `char`...
51
+ typename allocator_at = std::allocator<char>> //
52
+ class index_gt;
53
+ ```
54
+
55
+ You may want to use a custom memory allocator or a rare scalar type, but most often, you would start by defining a custom similarity measure.
56
+ The function object should have the following signature to support different-length vectors.
57
+
58
+ ```cpp
59
+ struct custom_metric_t {
60
+ T operator()(T const* a, T const* b, std::size_t a_length, std::size_t b_length) const;
61
+ };
62
+ ```
63
+
64
+ The following distances are pre-packaged:
65
+
66
+ - `cos_gt<scalar_t>` for "Cosine" or "Angular" distance.
67
+ - `ip_gt<scalar_t>` for "Inner Product" or "Dot Product" distance.
68
+ - `l2sq_gt<scalar_t>` for the squared "L2" or "Euclidean" distance.
69
+ - `jaccard_gt<scalar_t>` for "Jaccard" distance between two ordered sets of unique elements.
70
+ - `hamming_gt<scalar_t>` for "Hamming" distance, as the number of shared bits in hashes.
71
+ - `tanimoto_gt<scalar_t>` for "Tanimoto" coefficient for bit-strings.
72
+ - `sorensen_gt<scalar_t>` for "Dice-Sorensen" coefficient for bit-strings.
73
+ - `pearson_correlation_gt<scalar_t>` for "Pearson" correlation between probability distributions.
74
+ - `haversine_gt<scalar_t>` for "Haversine" or "Great Circle" distance between coordinates used in GIS applications.
75
+
76
+ ## Multi-Threading
77
+
78
+ Most AI, HPC, or Big Data packages use some form of a thread pool.
79
+ Instead of spawning additional threads within USearch, we focus on the thread safety of `add()` function, simplifying resource management.
80
+
81
+ ```cpp
82
+ #pragma omp parallel for
83
+ for (std::size_t i = 0; i < n; ++i)
84
+ native.add(label, span_t{vector, dims}, add_config_t { .thread = omp_get_thread_num() });
85
+ ```
86
+
87
+ During initialization, we allocate enough temporary memory for all the cores on the machine.
88
+ On the call, the user can supply the identifier of the current thread, making this library easy to integrate with OpenMP and similar tools.
package/cpp/bench.cpp CHANGED
@@ -15,7 +15,9 @@
15
15
 
16
16
  #define STDERR_FILENO HANDLE(2)
17
17
  #else
18
- #include <execinfo.h>
18
+ #if defined(__linux__)
19
+ #include <execinfo.h> // `backtrace`
20
+ #endif
19
21
  #include <fcntl.h> // `open`
20
22
  #include <stdlib.h> // `getenv`
21
23
  #include <sys/mman.h> // `mmap`
@@ -24,6 +26,7 @@
24
26
 
25
27
  #include <sys/stat.h> // `stat`
26
28
 
29
+ #include <algorithm>
27
30
  #include <csignal>
28
31
  #include <cstdio>
29
32
  #include <iostream> // `std::cerr`
@@ -32,9 +35,12 @@
32
35
  #include <string> // `std::to_string`
33
36
  #include <thread> // `std::thread::hardware_concurrency()`
34
37
  #include <variant> // `std::monostate`
38
+ #include <vector>
35
39
 
36
40
  #include <clipp.h> // Command Line Interface
37
- #include <omp.h> // `omp_set_num_threads()`
41
+ #if USEARCH_USE_OPENMP
42
+ #include <omp.h> // `omp_set_num_threads()`
43
+ #endif
38
44
 
39
45
  #include <simsimd/simsimd.h>
40
46
 
@@ -43,6 +49,7 @@
43
49
  using namespace unum::usearch;
44
50
  using namespace unum;
45
51
 
52
+ using label_t = std::int64_t;
46
53
  using vector_id_t = std::uint32_t;
47
54
  using vector_view_t = span_gt<float const>;
48
55
 
@@ -243,10 +250,10 @@ struct running_stats_printer_t {
243
250
  std::size_t new_progress = progress.load();
244
251
  if (new_progress - last_printed_progress < step)
245
252
  return;
246
- print(new_progress);
253
+ print(new_progress, total);
247
254
  }
248
255
 
249
- void print(std::size_t progress) {
256
+ void print(std::size_t progress, std::size_t total) {
250
257
 
251
258
  constexpr char bars_k[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||";
252
259
  constexpr std::size_t bars_len_k = 60;
@@ -267,6 +274,7 @@ struct running_stats_printer_t {
267
274
 
268
275
  last_printed_progress = progress;
269
276
  last_printed_time = time_new;
277
+ this->total = total;
270
278
  }
271
279
  };
272
280
 
@@ -275,15 +283,19 @@ void index_many(index_at& native, std::size_t n, vector_id_at const* ids, real_a
275
283
 
276
284
  running_stats_printer_t printer{n, "Indexing"};
277
285
 
286
+ #if USEARCH_USE_OPENMP
278
287
  #pragma omp parallel for schedule(static, 32)
288
+ #endif
279
289
  for (std::size_t i = 0; i < n; ++i) {
280
290
  add_config_t config;
291
+ #if USEARCH_USE_OPENMP
281
292
  config.thread = omp_get_thread_num();
293
+ #endif
282
294
  config.store_vector = true;
283
295
  vector_view_t vector{vectors + dims * i, dims};
284
296
  native.add(ids[i], vector, config);
285
297
  printer.progress++;
286
- if (omp_get_thread_num() == 0)
298
+ if (config.thread == 0)
287
299
  printer.refresh();
288
300
  }
289
301
  }
@@ -296,34 +308,18 @@ void search_many( //
296
308
  std::string name = "Search " + std::to_string(wanted);
297
309
  running_stats_printer_t printer{n, name.c_str()};
298
310
 
311
+ #if USEARCH_USE_OPENMP
299
312
  #pragma omp parallel for schedule(static, 32)
313
+ #endif
300
314
  for (std::size_t i = 0; i < n; ++i) {
301
315
  search_config_t config;
316
+ #if USEARCH_USE_OPENMP
302
317
  config.thread = omp_get_thread_num();
318
+ #endif
303
319
  vector_view_t vector{vectors + dims * i, dims};
304
320
  native.search(vector, wanted, config).dump_to(ids + wanted * i, distances + wanted * i);
305
321
  printer.progress++;
306
- if (omp_get_thread_num() == 0)
307
- printer.refresh();
308
- }
309
- }
310
-
311
- template <typename index_at, typename vector_id_at, typename real_at>
312
- void paginate_many( //
313
- index_at& native, std::size_t n, real_at const* vectors, std::size_t dims, std::size_t wanted,
314
- vector_id_at const* hints) {
315
-
316
- std::string name = "Paginate " + std::to_string(wanted);
317
- running_stats_printer_t printer{n, name.c_str()};
318
-
319
- #pragma omp parallel for schedule(static, 32)
320
- for (std::size_t i = 0; i < n; ++i) {
321
- search_config_t config;
322
- config.thread = omp_get_thread_num();
323
- vector_view_t vector{vectors + dims * i, dims};
324
- native.search_around(hints[i], vector, wanted, config);
325
- printer.progress++;
326
- if (omp_get_thread_num() == 0)
322
+ if (config.thread == 0)
327
323
  printer.refresh();
328
324
  }
329
325
  }
@@ -332,6 +328,8 @@ template <typename dataset_at, typename index_at> //
332
328
  static void single_shot(dataset_at& dataset, index_at& index, bool construct = true) {
333
329
  using label_t = typename index_at::label_t;
334
330
  using distance_t = typename index_at::distance_t;
331
+ using join_result_t = typename index_at::join_result_t;
332
+ constexpr std::size_t missing_label = std::numeric_limits<label_t>::max();
335
333
 
336
334
  std::printf("\n");
337
335
  std::printf("------------\n");
@@ -348,7 +346,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
348
346
  search_many(index, dataset.queries_count(), dataset.query(0), dataset.dimensions(), dataset.neighborhood_size(),
349
347
  found_neighbors.data(), found_distances.data());
350
348
 
351
- // Evaluate quality
349
+ // Evaluate search quality
352
350
  std::size_t recall_at_1 = 0, recall_full = 0;
353
351
  for (std::size_t i = 0; i != dataset.queries_count(); ++i) {
354
352
  auto expected = dataset.neighborhood(i);
@@ -360,13 +358,37 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
360
358
  std::printf("Recall@1 %.2f %%\n", recall_at_1 * 100.f / dataset.queries_count());
361
359
  std::printf("Recall %.2f %%\n", recall_full * 100.f / dataset.queries_count());
362
360
 
363
- // Paginate
364
- std::vector<vector_id_t> hints(dataset.queries_count());
365
- for (std::size_t i = 0; i != hints.size(); ++i)
366
- hints[i] = dataset.neighborhood(i)[0];
367
- paginate_many(index, dataset.queries_count(), dataset.query(0), dataset.dimensions(), 10, hints.data());
368
- paginate_many(index, dataset.queries_count(), dataset.query(0), dataset.dimensions(), 100, hints.data());
369
- paginate_many(index, dataset.queries_count(), dataset.query(0), dataset.dimensions(), 1000, hints.data());
361
+ // Perform joins
362
+ std::vector<label_t> man_to_woman(dataset.vectors_count());
363
+ std::vector<label_t> woman_to_man(dataset.vectors_count());
364
+ std::size_t join_attempts = 0;
365
+ {
366
+ index_at& men = index;
367
+ index_at women = index.copy().index;
368
+ std::fill(man_to_woman.begin(), man_to_woman.end(), missing_label);
369
+ std::fill(woman_to_man.begin(), woman_to_man.end(), missing_label);
370
+ {
371
+ executor_default_t executor(index.limits().threads());
372
+ running_stats_printer_t printer{1, "Join"};
373
+ join_result_t result = index_at::join( //
374
+ men, women, join_config_t{executor.size()}, //
375
+ man_to_woman.data(), woman_to_man.data(), //
376
+ executor, [&](std::size_t progress, std::size_t total) {
377
+ if (progress % 1000 == 0)
378
+ printer.print(progress, total);
379
+ });
380
+ join_attempts = result.cycles;
381
+ }
382
+ }
383
+ // Evaluate join quality
384
+ std::size_t recall_join = 0, unmatched_count = 0;
385
+ for (std::size_t i = 0; i != index.size(); ++i) {
386
+ recall_join += man_to_woman[i] == i;
387
+ unmatched_count += man_to_woman[i] == missing_label;
388
+ }
389
+ std::printf("Recall Joins %.2f %%\n", recall_join * 100.f / index.size());
390
+ std::printf("Unmatched %.2f %% (%zu items)\n", unmatched_count * 100.f / index.size(), unmatched_count);
391
+ std::printf("Proposals %.2f / man (%zu total)\n", join_attempts * 1.f / index.size(), join_attempts);
370
392
 
371
393
  std::printf("------------\n");
372
394
  std::printf("\n");
@@ -379,7 +401,7 @@ void handler(int sig) {
379
401
  // get void*'s for all entries on the stack
380
402
  #if defined(USEARCH_DEFINED_WINDOWS)
381
403
  size = CaptureStackBackTrace(0, 10, array, NULL);
382
- #else
404
+ #elif defined(USEARCH_DEFINED_LINUX)
383
405
  size = backtrace(array, 10);
384
406
  #endif // WINDOWS
385
407
 
@@ -401,7 +423,7 @@ void handler(int sig) {
401
423
  WriteFile(STDERR_FILENO, "\n", 1, &bytes_written, NULL);
402
424
  }
403
425
  free(symbol);
404
- #else
426
+ #elif defined(USEARCH_DEFINED_LINUX)
405
427
  backtrace_symbols_fd(array, size, STDERR_FILENO);
406
428
  #endif // WINDOWS
407
429
 
@@ -491,7 +513,7 @@ void run_punned(dataset_at& dataset, args_t const& args, index_config_t config,
491
513
 
492
514
  std::printf("Will benchmark an on-disk view\n");
493
515
 
494
- index_at index_view = index.fork();
516
+ index_at index_view = index.fork().index;
495
517
  index_view.view(args.path_output.c_str());
496
518
  single_shot(dataset, index_view, false);
497
519
  }
@@ -518,24 +540,24 @@ void run_big_or_small(dataset_at& dataset, args_t const& args, index_config_t co
518
540
  if (args.native) {
519
541
  if (args.metric_cos) {
520
542
  std::printf("-- Metric: Angular\n");
521
- run_typed<index_gt<cos_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config, limits);
543
+ run_typed<index_gt<cos_gt<float>, label_t, neighbor_id_at>>(dataset, args, config, limits);
522
544
  } else if (args.metric_l2) {
523
545
  std::printf("-- Metric: Euclidean\n");
524
- run_typed<index_gt<l2sq_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config, limits);
546
+ run_typed<index_gt<l2sq_gt<float>, label_t, neighbor_id_at>>(dataset, args, config, limits);
525
547
  } else if (args.metric_haversine) {
526
548
  std::printf("-- Metric: Haversine\n");
527
- run_typed<index_gt<haversine_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config, limits);
549
+ run_typed<index_gt<haversine_gt<float>, label_t, neighbor_id_at>>(dataset, args, config, limits);
528
550
  } else {
529
551
  std::printf("-- Metric: Inner Product\n");
530
- run_typed<index_gt<ip_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config, limits);
552
+ run_typed<index_gt<ip_gt<float>, label_t, neighbor_id_at>>(dataset, args, config, limits);
531
553
  }
532
554
  } else
533
- run_punned<index_punned_dense_gt<std::size_t, neighbor_id_at>>(dataset, args, config, limits);
555
+ run_punned<index_punned_dense_gt<label_t, neighbor_id_at>>(dataset, args, config, limits);
534
556
  }
535
557
 
536
558
  void report_alternative_setups() {
537
559
  using set_member_t = std::uint32_t;
538
- using sets_index_t = index_gt<jaccard_gt<set_member_t>, std::size_t, std::uint32_t>;
560
+ using sets_index_t = index_gt<jaccard_gt<set_member_t>, label_t, std::uint32_t>;
539
561
  set_member_t set_a[] = {10, 12, 15};
540
562
  set_member_t set_b[] = {11, 12, 15, 16};
541
563
  sets_index_t index;
@@ -625,12 +647,14 @@ int main(int argc, char** argv) {
625
647
  exit(0);
626
648
  }
627
649
 
650
+ #if USEARCH_USE_OPENMP
628
651
  // Instead of relying on `multithreaded` from "index_punned_dense.hpp" we will use OpenMP
629
652
  // to better estimate statistics between tasks batches, without having to recreate
630
653
  // the threads.
631
654
  omp_set_dynamic(true);
632
655
  omp_set_num_threads(args.threads);
633
656
  std::printf("- OpenMP threads: %d\n", omp_get_max_threads());
657
+ #endif
634
658
 
635
659
  std::printf("- Dataset: \n");
636
660
  std::printf("-- Base vectors path: %s\n", args.path_vectors.c_str());
@@ -658,7 +682,7 @@ int main(int argc, char** argv) {
658
682
  // config.expansion_search = args.expansion_search;
659
683
  index_limits_t limits;
660
684
  limits.threads_add = limits.threads_search = args.threads;
661
- limits.elements = dataset.vectors_count();
685
+ limits.members = dataset.vectors_count();
662
686
 
663
687
  std::printf("- Index: \n");
664
688
  std::printf("-- Connectivity: %zu\n", config.connectivity);
@@ -666,7 +690,11 @@ int main(int argc, char** argv) {
666
690
  // std::printf("-- Expansion @ Search: %zu\n", config.expansion_search);
667
691
 
668
692
  if (args.big)
693
+ #ifdef USEARCH_64BIT_ENV
669
694
  run_big_or_small<uint40_t>(dataset, args, config, limits);
695
+ #else
696
+ std::printf("Error: Don't use 40 bit identifiers in 32bit environment\n");
697
+ #endif
670
698
  else
671
699
  run_big_or_small<std::uint32_t>(dataset, args, config, limits);
672
700
 
package/cpp/test.cpp CHANGED
@@ -84,17 +84,23 @@ template <typename scalar_at, typename index_at> void test3d_punned(index_at&& i
84
84
  using view_t = span_gt<scalar_t const>;
85
85
  using span_t = span_gt<scalar_at>;
86
86
 
87
- scalar_t vec[3] = {10, 20, 15};
87
+ scalar_t vec42[3] = {10, 20, 15};
88
+ scalar_t vec43[3] = {19, 22, 11};
88
89
 
89
90
  index.reserve(10);
90
- index.add(42, view_t{&vec[0], 3ul});
91
+ index.add(42, view_t{&vec42[0], 3ul});
91
92
 
92
93
  // Reconstruct
93
- scalar_t vec_reconstructed[3] = {0, 0, 0};
94
- index.get(42, span_t{&vec_reconstructed[0], 3ul});
95
- expect(vec_reconstructed[0] == vec[0]);
96
- expect(vec_reconstructed[1] == vec[1]);
97
- expect(vec_reconstructed[2] == vec[2]);
94
+ scalar_t vec42_reconstructed[3] = {0, 0, 0};
95
+ index.get(42, span_t{&vec42_reconstructed[0], 3ul});
96
+ expect(vec42_reconstructed[0] == vec42[0]);
97
+ expect(vec42_reconstructed[1] == vec42[1]);
98
+ expect(vec42_reconstructed[2] == vec42[2]);
99
+
100
+ index.add(43, view_t{&vec43[0], 3ul});
101
+ expect(index.size() == 2);
102
+ index.remove(43);
103
+ expect(index.size() == 1);
98
104
  }
99
105
 
100
106
  template <typename index_at> void test_sets(index_at&& index) {