usearch 0.4.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.clang-tidy +60 -0
  2. package/.github/workflows/release.yml +42 -16
  3. package/.github/workflows/update_version.sh +1 -0
  4. package/.vscode/launch.json +22 -0
  5. package/.vscode/settings.json +5 -0
  6. package/.vscode/tasks.json +4 -4
  7. package/CITATION.cff +11 -0
  8. package/CMakeLists.txt +108 -95
  9. package/Cargo.toml +1 -1
  10. package/Dockerfile +14 -0
  11. package/README.md +8 -7
  12. package/VERSION +1 -1
  13. package/binding.gyp +2 -2
  14. package/build.rs +2 -2
  15. package/cmake/config.cmake.in +15 -0
  16. package/cmake/pkg-config.pc.in +4 -0
  17. package/cmake/usearchConfigVersion.cmake.in +20 -0
  18. package/conanfile.py +1 -1
  19. package/docs/benchmarks.md +11 -2
  20. package/docs/{Compilation.md → compilation.md} +54 -27
  21. package/include/usearch/usearch.hpp +664 -390
  22. package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +17 -16
  23. package/javascript/lib.cpp +13 -13
  24. package/javascript/test.js +1 -1
  25. package/objc/USearchObjective.mm +46 -29
  26. package/objc/include/USearchObjective.h +18 -26
  27. package/package.json +1 -1
  28. package/pyproject.toml +3 -2
  29. package/python/lib.cpp +179 -120
  30. package/python/scripts/bench.py +188 -0
  31. package/python/scripts/bench_params.py +113 -0
  32. package/python/{test.py → scripts/test.py} +32 -13
  33. package/python/usearch/client.py +100 -14
  34. package/python/usearch/server.py +104 -23
  35. package/rust/lib.cpp +15 -14
  36. package/rust/lib.hpp +5 -5
  37. package/rust/lib.rs +3 -3
  38. package/setup.py +7 -0
  39. package/src/CMakeLists.txt +107 -0
  40. package/src/bench.cpp +45 -15
  41. package/src/{advanced.hpp → punned.hpp} +227 -199
  42. package/src/test.cpp +62 -0
  43. package/swift/Test.swift +1 -1
  44. package/wolfram/lib.cpp +14 -14
  45. package/python/bench.py +0 -146
package/src/test.cpp ADDED
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @brief A trivial test.
3
+ */
4
+ #include <cassert>
5
+
6
+ #include "punned.hpp"
7
+
8
+ using namespace unum::usearch;
9
+ using namespace unum;
10
+
11
+ using point_id_t = std::int64_t;
12
+
13
+ template <typename scalar_at, typename index_at> void test3d(index_at&& index) {
14
+ using span_t = span_gt<scalar_at const>;
15
+ using distance_t = typename index_at::distance_t;
16
+
17
+ scalar_at vec[3] = {10, 20, 15};
18
+
19
+ index.reserve(10);
20
+ index.add(42, span_t{&vec[0], 3ul});
21
+
22
+ point_id_t matched_labels[10] = {0};
23
+ distance_t matched_distances[10] = {0};
24
+ std::size_t matched_count = index.search(span_t{&vec[0], 3ul}, 5).dump_to(matched_labels, matched_distances);
25
+
26
+ assert(matched_count == 1);
27
+ assert(matched_labels[0] == 42);
28
+ assert(std::abs(matched_distances[0]) < 0.01);
29
+
30
+ index.save("tmp.usearch");
31
+
32
+ // Search again over reconstructed index
33
+ index.load("tmp.usearch");
34
+ matched_count = index.search(span_t{&vec[0], 3ul}, 5).dump_to(matched_labels, matched_distances);
35
+ assert(matched_count == 1);
36
+ assert(matched_labels[0] == 42);
37
+ assert(std::abs(matched_distances[0]) < 0.01);
38
+
39
+ // Search again over mapped index
40
+ index.view("tmp.usearch");
41
+ matched_count = index.search(span_t{&vec[0], 3ul}, 5).dump_to(matched_labels, matched_distances);
42
+ assert(matched_count == 1);
43
+ assert(matched_labels[0] == 42);
44
+ assert(std::abs(matched_distances[0]) < 0.01);
45
+ }
46
+
47
+ int main(int, char**) {
48
+
49
+ test3d<float>(index_gt<cos_gt<float>, point_id_t, std::uint32_t, float>{});
50
+ test3d<float>(index_gt<l2sq_gt<float>, point_id_t, std::uint32_t, float>{});
51
+
52
+ test3d<double>(index_gt<cos_gt<double>, point_id_t, std::uint32_t, double>{});
53
+ test3d<double>(index_gt<l2sq_gt<double>, point_id_t, std::uint32_t, double>{});
54
+
55
+ test3d<float>(punned_small_t::cos(3));
56
+ test3d<float>(punned_small_t::l2sq(3));
57
+
58
+ test3d<double>(punned_small_t::cos(3));
59
+ test3d<double>(punned_small_t::l2sq(3));
60
+
61
+ return 0;
62
+ }
package/swift/Test.swift CHANGED
@@ -10,7 +10,7 @@ import USearch
10
10
 
11
11
  @available(iOS 13, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
12
12
  func testUnit() throws {
13
- let index = Index.l2(dimensions: 4, connectivity: 8)
13
+ let index = Index.l2sq(dimensions: 4, connectivity: 8)
14
14
  let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4]
15
15
  let vectorB: [Float32] = [0.4, 0.2, 1.2, 1.1]
16
16
  index.add(label: 42, vector: vectorA[...])
package/wolfram/lib.cpp CHANGED
@@ -1,9 +1,9 @@
1
1
  #include "WolframLibrary.h"
2
- #include "advanced.hpp"
2
+ #include "punned.hpp"
3
3
 
4
4
  using namespace unum::usearch;
5
5
  using distance_t = punned_distance_t;
6
- using native_index_t = auto_index_gt<int>;
6
+ using punned_t = punned_gt<int>;
7
7
  using span_t = span_gt<float>;
8
8
 
9
9
  EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) { return LIBRARY_NO_ERROR; }
@@ -25,10 +25,10 @@ EXTERN_C DLLEXPORT int IndexCreate(WolframLibraryData libData, mint Argc, MArgum
25
25
  config.max_threads_search = 4;
26
26
 
27
27
  accuracy_t accuracy = accuracy_from_name(accuracy_cstr, strlen(accuracy_cstr));
28
- native_index_t index = index_from_name<native_index_t>( //
28
+ punned_t index = index_from_name<punned_t>( //
29
29
  metric_cstr, strlen(metric_cstr), dimensions, accuracy, config);
30
30
 
31
- native_index_t* result_ptr = new native_index_t(std::move(index));
31
+ punned_t* result_ptr = new punned_t(std::move(index));
32
32
  MArgument_setInteger(Res, (long)result_ptr);
33
33
 
34
34
  } catch (...) {
@@ -42,7 +42,7 @@ EXTERN_C DLLEXPORT int IndexCreate(WolframLibraryData libData, mint Argc, MArgum
42
42
 
43
43
  EXTERN_C DLLEXPORT int IndexSave(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
44
44
  char* path_cstr = nullptr;
45
- native_index_t* c_ptr = (native_index_t*)MArgument_getUTF8String(Args[0]);
45
+ punned_t* c_ptr = (punned_t*)MArgument_getUTF8String(Args[0]);
46
46
  try {
47
47
  path_cstr = MArgument_getUTF8String(Args[1]);
48
48
  c_ptr->save(path_cstr);
@@ -55,7 +55,7 @@ EXTERN_C DLLEXPORT int IndexSave(WolframLibraryData libData, mint Argc, MArgumen
55
55
 
56
56
  EXTERN_C DLLEXPORT int IndexLoad(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
57
57
  char* path_cstr = nullptr;
58
- native_index_t* c_ptr = (native_index_t*)MArgument_getUTF8String(Args[0]);
58
+ punned_t* c_ptr = (punned_t*)MArgument_getUTF8String(Args[0]);
59
59
  try {
60
60
  path_cstr = MArgument_getUTF8String(Args[1]);
61
61
  c_ptr->load(path_cstr);
@@ -68,7 +68,7 @@ EXTERN_C DLLEXPORT int IndexLoad(WolframLibraryData libData, mint Argc, MArgumen
68
68
 
69
69
  EXTERN_C DLLEXPORT int IndexView(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
70
70
  char* path_cstr = nullptr;
71
- native_index_t* c_ptr = (native_index_t*)MArgument_getUTF8String(Args[0]);
71
+ punned_t* c_ptr = (punned_t*)MArgument_getUTF8String(Args[0]);
72
72
  try {
73
73
  path_cstr = MArgument_getUTF8String(Args[1]);
74
74
  c_ptr->view(path_cstr);
@@ -80,37 +80,37 @@ EXTERN_C DLLEXPORT int IndexView(WolframLibraryData libData, mint Argc, MArgumen
80
80
  }
81
81
 
82
82
  EXTERN_C DLLEXPORT int IndexDestroy(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
83
- delete (native_index_t*)MArgument_getUTF8String(Args[0]);
83
+ delete (punned_t*)MArgument_getUTF8String(Args[0]);
84
84
  return LIBRARY_NO_ERROR;
85
85
  }
86
86
 
87
87
  EXTERN_C DLLEXPORT int IndexSize(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
88
- size_t res = ((native_index_t*)MArgument_getUTF8String(Args[0]))->size();
88
+ size_t res = ((punned_t*)MArgument_getUTF8String(Args[0]))->size();
89
89
  MArgument_setInteger(Res, res);
90
90
  return LIBRARY_NO_ERROR;
91
91
  }
92
92
 
93
93
  EXTERN_C DLLEXPORT int IndexConnectivity(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
94
- size_t res = ((native_index_t*)MArgument_getUTF8String(Args[0]))->connectivity();
94
+ size_t res = ((punned_t*)MArgument_getUTF8String(Args[0]))->connectivity();
95
95
  MArgument_setInteger(Res, res);
96
96
  return LIBRARY_NO_ERROR;
97
97
  }
98
98
 
99
99
  EXTERN_C DLLEXPORT int IndexDimensions(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
100
- size_t res = ((native_index_t*)MArgument_getUTF8String(Args[0]))->dimensions();
100
+ size_t res = ((punned_t*)MArgument_getUTF8String(Args[0]))->dimensions();
101
101
  MArgument_setInteger(Res, res);
102
102
  return LIBRARY_NO_ERROR;
103
103
  }
104
104
 
105
105
  EXTERN_C DLLEXPORT int IndexCapacity(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
106
- size_t res = ((native_index_t*)MArgument_getUTF8String(Args[0]))->capacity();
106
+ size_t res = ((punned_t*)MArgument_getUTF8String(Args[0]))->capacity();
107
107
  MArgument_setInteger(Res, res);
108
108
  return LIBRARY_NO_ERROR;
109
109
  }
110
110
 
111
111
  EXTERN_C DLLEXPORT int IndexAdd(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
112
112
  char* path_cstr = nullptr;
113
- native_index_t* c_ptr = (native_index_t*)MArgument_getUTF8String(Args[0]);
113
+ punned_t* c_ptr = (punned_t*)MArgument_getUTF8String(Args[0]);
114
114
  float* vector_data = nullptr;
115
115
  try {
116
116
  int label = MArgument_getInteger(Args[1]);
@@ -126,7 +126,7 @@ EXTERN_C DLLEXPORT int IndexAdd(WolframLibraryData libData, mint Argc, MArgument
126
126
  }
127
127
 
128
128
  EXTERN_C DLLEXPORT int IndexSearch(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
129
- native_index_t* c_ptr = (native_index_t*)MArgument_getUTF8String(Args[0]);
129
+ punned_t* c_ptr = (punned_t*)MArgument_getUTF8String(Args[0]);
130
130
  MTensor matches;
131
131
  mint dims[] = {1};
132
132
  int wanted = MArgument_getInteger(Args[2]);
package/python/bench.py DELETED
@@ -1,146 +0,0 @@
1
- import time
2
- import numpy as np
3
- import fire
4
-
5
- import numpy as np
6
- from numba import cfunc, types, carray
7
-
8
- from faiss import IndexHNSWFlat, IndexIVFPQ
9
- from usearch.index import Index
10
- from usearch.io import load_matrix
11
-
12
-
13
- def measure(f) -> float:
14
- a = time.time_ns()
15
- result = f()
16
- b = time.time_ns()
17
- c = b - a
18
- secs = c / (10 ** 9)
19
- print(f'- Took: {secs:.2f} seconds')
20
- return secs, result
21
-
22
-
23
- def bench_faiss(index, vectors: np.array, queries: np.array, neighbors: np.array):
24
- dt, _ = measure(lambda: index.add(vectors))
25
- print(f'- Performance: {vectors.shape[0]/dt:.2f} insertions/s')
26
-
27
- dt, results = measure(lambda: index.search(queries, neighbors.shape[1]))
28
- print(f'- Performance: {queries.shape[0]/dt:.2f} queries/s')
29
-
30
- recall_at_one = 0.0
31
- matches = results[1]
32
- for i in range(queries.shape[0]):
33
- recall_at_one += neighbors[i, 0] in matches[i, :]
34
- recall_at_one /= queries.shape[0]
35
- print(f'- Recall@1: {recall_at_one * 100:.2f} %')
36
-
37
-
38
- def bench_usearch(index, vectors: np.array, queries: np.array, neighbors: np.array):
39
- labels = np.arange(vectors.shape[0], dtype=np.longlong)
40
- assert len(index) == 0
41
- dt, _ = measure(lambda: index.add(labels, vectors))
42
- assert len(index) == vectors.shape[0]
43
- print(f'- Performance: {vectors.shape[0]/dt:.2f} insertions/s')
44
-
45
- dt, results = measure(lambda: index.search(queries, neighbors.shape[1]))
46
- print(f'- Performance: {queries.shape[0]/dt:.2f} queries/s')
47
-
48
- recall_at_one = 0.0
49
- matches = results[0]
50
- for i in range(queries.shape[0]):
51
- recall_at_one += neighbors[i, 0] in matches[i, :]
52
- recall_at_one /= queries.shape[0]
53
- print(f'- Recall@1: {recall_at_one * 100:.2f} %')
54
-
55
- # Showcases how to use Numba to JIT-compile similarity measures for USearch.
56
- # https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
57
-
58
-
59
- signature_f32 = types.float32(
60
- types.CPointer(types.float32),
61
- types.CPointer(types.float32),
62
- types.size_t, types.size_t)
63
-
64
-
65
- @cfunc(signature_f32)
66
- def inner_product_f32(a, b, n, m):
67
- a_array = carray(a, n)
68
- b_array = carray(b, n)
69
- c = types.float32(0)
70
- for i in range(n):
71
- c += a_array[i] * b_array[i]
72
- return types.float32(1 - c)
73
-
74
-
75
- def main(
76
- vectors: str,
77
- queries: str,
78
- neighbors: str,
79
- connectivity: int = 16,
80
- expansion_add: int = 128,
81
- expansion_search: int = 64,
82
- ):
83
-
84
- vectors_mat = load_matrix(vectors)
85
- queries_mat = load_matrix(queries)
86
- neighbors_mat = load_matrix(neighbors)
87
- dim = vectors_mat.shape[1]
88
-
89
- print('USearch: HNSW')
90
- index = Index(
91
- ndim=dim,
92
- expansion_add=expansion_add,
93
- expansion_search=expansion_search,
94
- connectivity=connectivity,
95
- )
96
- bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
97
-
98
- print('USearch: HNSW + Numba JIT')
99
- index = Index(
100
- ndim=dim,
101
- expansion_add=expansion_add,
102
- expansion_search=expansion_search,
103
- connectivity=connectivity,
104
- metric_pointer=inner_product_f32.address,
105
- )
106
- bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
107
-
108
- print('USearch: HNSW + Half Precision')
109
- index = Index(
110
- ndim=dim,
111
- dtype='f16',
112
- expansion_add=expansion_add,
113
- expansion_search=expansion_search,
114
- connectivity=connectivity,
115
- )
116
- bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
117
-
118
- print('USearch: HNSW + Quarter Precision')
119
- index = Index(
120
- ndim=dim,
121
- dtype='f8',
122
- expansion_add=expansion_add,
123
- expansion_search=expansion_search,
124
- connectivity=connectivity,
125
- )
126
- bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
127
-
128
- print('FAISS: HNSW')
129
- index = IndexHNSWFlat(dim, connectivity)
130
- index.hnsw.efSearch = expansion_search
131
- index.hnsw.efConstruction = expansion_add
132
- bench_faiss(index, vectors_mat, queries_mat, neighbors_mat)
133
-
134
- print('FAISS: HNSW + IVFPQ')
135
- nlist = 10000 # Number of inverted lists (number of partitions or cells).
136
- nsegment = 16 # Number of segments for PQ (number of subquantizers).
137
- nbit = 8 # Number of bits to encode each segment.
138
- coarse_quantizer = IndexHNSWFlat(dim, connectivity)
139
- index = IndexIVFPQ(coarse_quantizer, dim, nlist, nsegment, nbit)
140
- index.train(vectors_mat)
141
- index.nprobe = 10
142
- bench_faiss(index, vectors_mat, queries_mat, neighbors_mat)
143
-
144
-
145
- if __name__ == '__main__':
146
- fire.Fire(main)