usearch 0.15.0 → 0.16.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/c/lib.cpp +114 -111
- package/c/usearch.h +15 -7
- package/conanfile.py +1 -1
- package/cpp/test.cpp +1 -1
- package/include/usearch/index_punned_dense.hpp +15 -7
- package/package.json +1 -1
- package/python/lib.cpp +5 -4
- package/python/scripts/test.py +4 -2
- package/setup.py +1 -1
package/Cargo.toml
CHANGED
package/c/lib.cpp
CHANGED
|
@@ -7,9 +7,13 @@ extern "C" {
|
|
|
7
7
|
using namespace unum::usearch;
|
|
8
8
|
using namespace unum;
|
|
9
9
|
|
|
10
|
-
using label_t =
|
|
11
|
-
using distance_t =
|
|
12
|
-
using
|
|
10
|
+
using label_t = usearch_label_t;
|
|
11
|
+
using distance_t = usearch_distance_t;
|
|
12
|
+
using index_t = index_punned_dense_gt<label_t>;
|
|
13
|
+
using add_result_t = index_t::add_result_t;
|
|
14
|
+
using search_result_t = index_t::search_result_t;
|
|
15
|
+
using seerialization_result_t = index_t::seerialization_result_t;
|
|
16
|
+
|
|
13
17
|
using span_t = span_gt<float>;
|
|
14
18
|
|
|
15
19
|
metric_kind_t to_native_metric(usearch_metric_kind_t kind) {
|
|
@@ -40,125 +44,124 @@ scalar_kind_t to_native_scalar(usearch_scalar_kind_t kind) {
|
|
|
40
44
|
|
|
41
45
|
void usearch_init(usearch_init_options_t* options, usearch_index_t* index, usearch_error_t* error) {
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// char error_buffers[1];
|
|
58
|
-
// };
|
|
59
|
-
// std::size_t bytes_for_error_messages = error_limit_k * config.max_threads();
|
|
60
|
-
// std::size_t bytes_to_alloc = sizeof(punned_index_t) + bytes_for_error_messages;
|
|
61
|
-
// safe_punned_index_t* result = (safe_punned_index_t*)std::malloc(bytes_to_alloc);
|
|
62
|
-
|
|
63
|
-
scalar_kind_t accuracy = scalar_kind_from_name(accuracy_str, accuracy_len);
|
|
64
|
-
metric_kind_t metric_kind = metric_from_name(metric_str, metric_len);
|
|
65
|
-
punned_index_t index = make_punned<punned_index_t>( //
|
|
66
|
-
metric_kind, static_cast<std::size_t>(dimensions), accuracy, config);
|
|
67
|
-
|
|
68
|
-
punned_index_t* result_ptr = new punned_index_t(std::move(index));
|
|
69
|
-
return result_ptr;
|
|
70
|
-
} catch (std::exception const&) {
|
|
71
|
-
}
|
|
72
|
-
return NULL;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
void usearch_free(punned_index_t* index) { delete index; }
|
|
76
|
-
|
|
77
|
-
char const* usearch_save(punned_index_t* index, char const* path) {
|
|
78
|
-
try {
|
|
79
|
-
index->save(path);
|
|
80
|
-
return NULL;
|
|
81
|
-
} catch (std::exception& e) {
|
|
82
|
-
// q:: added these to make sure I do not pass a potentially dangling e.what()
|
|
83
|
-
// though I realize allocation below may fail and I am not checking for it.
|
|
84
|
-
// what can I do there? Golang side tries to free whatever string is passed from
|
|
85
|
-
// so passing a compile time constant "double fault" string is not an option
|
|
86
|
-
char* res = new char[strlen(e.what()) + 1];
|
|
87
|
-
strcpy(res, e.what());
|
|
88
|
-
return res;
|
|
89
|
-
}
|
|
47
|
+
config_t config;
|
|
48
|
+
config.expansion_add = static_cast<std::size_t>(expansion_add);
|
|
49
|
+
config.expansion_search = static_cast<std::size_t>(expansion_search);
|
|
50
|
+
config.connectivity = static_cast<std::size_t>(connectivity);
|
|
51
|
+
config.max_elements = static_cast<std::size_t>(capacity);
|
|
52
|
+
config.max_threads_add = std::thread::hardware_concurrency();
|
|
53
|
+
config.max_threads_search = std::thread::hardware_concurrency();
|
|
54
|
+
|
|
55
|
+
scalar_kind_t accuracy = scalar_kind_from_name(accuracy_str, accuracy_len);
|
|
56
|
+
metric_kind_t metric_kind = metric_from_name(metric_str, metric_len);
|
|
57
|
+
index_t index = index_t::make(metric_kind, static_cast<std::size_t>(dimensions), accuracy, config);
|
|
58
|
+
|
|
59
|
+
index_t* result_ptr = new index_t(std::move(index));
|
|
60
|
+
return result_ptr;
|
|
90
61
|
}
|
|
91
62
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
63
|
+
void usearch_free(index_t* index) { delete index; }
|
|
64
|
+
|
|
65
|
+
void usearch_save(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
66
|
+
serialization_result_t result = reinterpret_cast<index_t*>(index)->save(path);
|
|
67
|
+
if (!result)
|
|
68
|
+
*error = result.error.what();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
void usearch_load(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
72
|
+
serialization_result_t result = reinterpret_cast<index_t*>(index)->load(path);
|
|
73
|
+
if (!result)
|
|
74
|
+
*error = result.error.what();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void usearch_view(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
78
|
+
serialization_result_t result = reinterpret_cast<index_t*>(index)->view(path);
|
|
79
|
+
if (!result)
|
|
80
|
+
*error = result.error.what();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
size_t usearch_size(usearch_index_t index, usearch_error_t*) { //
|
|
84
|
+
return reinterpret_cast<index_t*>(index)->size();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
size_t usearch_capacity(usearch_index_t index, usearch_error_t*) {
|
|
88
|
+
return reinterpret_cast<index_t*>(index)->capacity();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
size_t usearch_dimensions(usearch_index_t index, usearch_error_t*) {
|
|
92
|
+
return reinterpret_cast<index_t*>(index)->dimensions();
|
|
101
93
|
}
|
|
102
94
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
95
|
+
size_t usearch_connectivity(usearch_index_t index, usearch_error_t*) {
|
|
96
|
+
return reinterpret_cast<index_t*>(index)->connectivity();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t*) {
|
|
100
|
+
// TODO: Consider returning the new capacity.
|
|
101
|
+
reinterpret_cast<index_t*>(index)->reserve(capacity);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
add_result_t add_(index_t* index, usearch_label_t label, void const* vector, scalar_kind_t kind) {
|
|
105
|
+
switch (kind) {
|
|
106
|
+
case scalar_kind_t::f32_k: return index->add(label, (f32_t const*)vector);
|
|
107
|
+
case scalar_kind_t::f64_k: return index->add(label, (f64_t const*)vector);
|
|
108
|
+
case scalar_kind_t::f16_k: return index->add(label, (f16_t const*)vector);
|
|
109
|
+
case scalar_kind_t::f8_k: return index->add(label, (f8_bits_t const*)vector);
|
|
110
|
+
case scalar_kind_t::b1x8_k: return index->add(label, (b1x8_t const*)vector);
|
|
111
|
+
default: return add_result_t{}.failed("Unknown scalar kind!");
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
void usearch_add( //
|
|
116
|
+
usearch_index_t index, usearch_label_t label, void const* vector, usearch_scalar_kind_t kind, //
|
|
117
|
+
usearch_error_t* error) {
|
|
118
|
+
add_result_t result = add_(reinterpret_cast<index_t*>(index), label, vector, to_native_scalar(kind));
|
|
119
|
+
if (!result)
|
|
120
|
+
*error = result.error.what();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
bool usearch_contains(usearch_index_t index, usearch_label_t label, usearch_error_t*) {
|
|
124
|
+
reinterpret_cast<index_t*>(index)->contains(label);
|
|
125
|
+
}
|
|
118
126
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
search_result_t search_(index_t* index, void const* vector, scalar_kind_t kind, size_t n) {
|
|
128
|
+
switch (kind) {
|
|
129
|
+
case scalar_kind_t::f32_k: return index->search((f32_t const*)vector, n);
|
|
130
|
+
case scalar_kind_t::f64_k: return index->search((f64_t const*)vector, n);
|
|
131
|
+
case scalar_kind_t::f16_k: return index->search((f16_t const*)vector, n);
|
|
132
|
+
case scalar_kind_t::f8_k: return index->search((f8_bits_t const*)vector, n);
|
|
133
|
+
case scalar_kind_t::b1x8_k: return index->search((b1x8_t const*)vector, n);
|
|
134
|
+
default: return search_result_t{}.failed("Unknown scalar kind!");
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
char* res = new char[strlen(e.what()) + 1];
|
|
138
|
-
strcpy(res, e.what());
|
|
139
|
-
return res;
|
|
138
|
+
size_t usearch_search( //
|
|
139
|
+
usearch_index_t index, void const* vector, usearch_scalar_kind_t kind, size_t results_limit, //
|
|
140
|
+
usearch_label_t* found_labels, usearch_distance_t* found_distances, usearch_error_t* error) {
|
|
141
|
+
search_result_t result = search_(reinterpret_cast<index_t*>(index), vector, to_native_scalar(kind), results_limit);
|
|
142
|
+
if (!result) {
|
|
143
|
+
*error = result.error.what();
|
|
144
|
+
return 0;
|
|
140
145
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
span_t vector_span = span_t{query, static_cast<std::size_t>(query_len)};
|
|
155
|
-
res.Len = index->search(vector_span, static_cast<std::size_t>(limit)).dump_to(matches_data, dist_data);
|
|
156
|
-
} catch (std::exception& e) {
|
|
157
|
-
res.Error = new char[strlen(e.what()) + 1];
|
|
158
|
-
strcpy(res.Error, e.what());
|
|
159
|
-
// res.Error = res;
|
|
146
|
+
|
|
147
|
+
return result.dump_to(found_labels, found_distances);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
bool get_(index_t* index, void const* vector, scalar_kind_t kind) {
|
|
151
|
+
switch (kind) {
|
|
152
|
+
case scalar_kind_t::f32_k: return index->get((f32_t const*)vector, n);
|
|
153
|
+
case scalar_kind_t::f64_k: return index->get((f64_t const*)vector, n);
|
|
154
|
+
case scalar_kind_t::f16_k: return index->get((f16_t const*)vector, n);
|
|
155
|
+
case scalar_kind_t::f8_k: return index->get((f8_bits_t const*)vector, n);
|
|
156
|
+
case scalar_kind_t::b1x8_k: return index->get((b1x8_t const*)vector, n);
|
|
157
|
+
default: return search_result_t{}.failed("Unknown scalar kind!");
|
|
160
158
|
}
|
|
161
|
-
res.Labels = matches_data;
|
|
162
|
-
res.Distances = dist_data;
|
|
163
|
-
return res;
|
|
164
159
|
}
|
|
160
|
+
|
|
161
|
+
bool usearch_get( //
|
|
162
|
+
usearch_index_t index, usearch_label_t label, //
|
|
163
|
+
void* vector, usearch_scalar_kind_t kind, usearch_error_t*) {
|
|
164
|
+
return search_(reinterpret_cast<index_t*>(index), vector, to_native_scalar(kind));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t*) {}
|
package/c/usearch.h
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
typedef void* usearch_index_t;
|
|
7
7
|
typedef uint32_t usearch_label_t;
|
|
8
|
+
typedef float usearch_distance_t;
|
|
8
9
|
typedef char const* usearch_error_t;
|
|
9
10
|
|
|
10
|
-
typedef
|
|
11
|
+
typedef usearch_distance_t (*)(void const*, void const*) usearch_metric_t;
|
|
11
12
|
|
|
12
13
|
typedef enum usearch_metric_kind_t {
|
|
13
14
|
usearch_metric_ip_k = 0,
|
|
@@ -57,18 +58,25 @@ size_t usearch_dimensions(usearch_index_t, usearch_error_t*);
|
|
|
57
58
|
size_t usearch_connectivity(usearch_index_t, usearch_error_t*);
|
|
58
59
|
|
|
59
60
|
void usearch_reserve(usearch_index_t, size_t capacity, usearch_error_t*);
|
|
60
|
-
|
|
61
|
+
|
|
62
|
+
void usearch_add( //
|
|
63
|
+
usearch_index_t, usearch_label_t, void const* vector, usearch_scalar_kind_t vector_kind, //
|
|
64
|
+
usearch_error_t*);
|
|
65
|
+
|
|
66
|
+
bool usearch_contains(usearch_index_t, usearch_label_t, usearch_error_t*);
|
|
61
67
|
|
|
62
68
|
/**
|
|
63
69
|
* @brief Performs k-Approximate Nearest Neighbors Search.
|
|
64
70
|
* @return Number of found matches.
|
|
65
71
|
*/
|
|
66
|
-
size_t usearch_search(
|
|
67
|
-
usearch_index_t,
|
|
68
|
-
usearch_label_t* found_labels,
|
|
72
|
+
size_t usearch_search( //
|
|
73
|
+
usearch_index_t, void const* query_vector, usearch_scalar_kind_t query_kind, size_t results_limit, //
|
|
74
|
+
usearch_label_t* found_labels, usearch_distance_t* found_distances, usearch_error_t*);
|
|
75
|
+
|
|
76
|
+
bool usearch_get( //
|
|
77
|
+
usearch_index_t, usearch_label_t, //
|
|
78
|
+
void* vector, usearch_scalar_kind_t vector_kind, usearch_error_t*);
|
|
69
79
|
|
|
70
|
-
bool usearch_contains(usearch_index_t, usearch_label_t, usearch_error_t*);
|
|
71
|
-
void usearch_get(usearch_index_t, usearch_label_t, float* vector, usearch_error_t*);
|
|
72
80
|
void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t*);
|
|
73
81
|
|
|
74
82
|
#endif
|
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.16.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
|
@@ -90,7 +90,7 @@ template <typename scalar_at, typename index_at> void test3d_punned(index_at&& i
|
|
|
90
90
|
|
|
91
91
|
// Reconstruct
|
|
92
92
|
scalar_t vec_reconstructed[3] = {0, 0, 0};
|
|
93
|
-
index.
|
|
93
|
+
index.get(42, span_t{&vec_reconstructed[0], 3ul});
|
|
94
94
|
expect(vec_reconstructed[0] == vec[0]);
|
|
95
95
|
expect(vec_reconstructed[1] == vec[1]);
|
|
96
96
|
expect(vec_reconstructed[2] == vec[2]);
|
|
@@ -239,11 +239,11 @@ class index_punned_dense_gt {
|
|
|
239
239
|
search_result_t search_around(label_t hint, f32_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f32); }
|
|
240
240
|
search_result_t search_around(label_t hint, f64_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f64); }
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
242
|
+
bool get(label_t label, b1x8_t* vector) const { return get_(label, vector, casts_.to_b1x8); }
|
|
243
|
+
bool get(label_t label, f8_bits_t* vector) const { return get_(label, vector, casts_.to_f8); }
|
|
244
|
+
bool get(label_t label, f16_t* vector) const { return get_(label, vector, casts_.to_f16); }
|
|
245
|
+
bool get(label_t label, f32_t* vector) const { return get_(label, vector, casts_.to_f32); }
|
|
246
|
+
bool get(label_t label, f64_t* vector) const { return get_(label, vector, casts_.to_f64); }
|
|
247
247
|
// clang-format on
|
|
248
248
|
|
|
249
249
|
static index_punned_dense_gt make( //
|
|
@@ -368,14 +368,22 @@ class index_punned_dense_gt {
|
|
|
368
368
|
return lookup_table_.at(label);
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
-
template <typename scalar_at>
|
|
372
|
-
id_t id
|
|
371
|
+
template <typename scalar_at> bool get_(label_t label, scalar_at* reconstructed, cast_t const& cast) const {
|
|
372
|
+
id_t id;
|
|
373
|
+
{
|
|
374
|
+
shared_lock_t lock(lookup_table_mutex_);
|
|
375
|
+
auto it = lookup_table_.find(label);
|
|
376
|
+
if (it == lookup_table_.end())
|
|
377
|
+
return false;
|
|
378
|
+
id = it->second;
|
|
379
|
+
}
|
|
373
380
|
member_citerator_t iterator = typed_->cbegin() + id;
|
|
374
381
|
member_cref_t member = *iterator;
|
|
375
382
|
byte_t const* casted_vector = reinterpret_cast<byte_t const*>(member.vector.data());
|
|
376
383
|
bool casted = cast(casted_vector, casted_vector_bytes_, (byte_t*)reconstructed);
|
|
377
384
|
if (!casted)
|
|
378
385
|
std::memcpy(reconstructed, casted_vector, casted_vector_bytes_);
|
|
386
|
+
return true;
|
|
379
387
|
}
|
|
380
388
|
|
|
381
389
|
template <typename scalar_at> add_result_t add_(label_t label, scalar_at const* vector, cast_t const& cast) {
|
package/package.json
CHANGED
package/python/lib.cpp
CHANGED
|
@@ -392,14 +392,15 @@ template <typename index_at> void view_index(index_at& index, std::string const&
|
|
|
392
392
|
// clang-format on
|
|
393
393
|
|
|
394
394
|
template <typename internal_at, typename external_at = internal_at, typename index_at = void>
|
|
395
|
-
py::
|
|
395
|
+
py::object get_typed_member(index_at const& index, label_t label) {
|
|
396
396
|
py::array_t<external_at> result_py(static_cast<Py_ssize_t>(index.scalar_words()));
|
|
397
397
|
auto result_py1d = result_py.template mutable_unchecked<1>();
|
|
398
|
-
index.
|
|
399
|
-
|
|
398
|
+
if (!index.get(label, (internal_at*)&result_py1d(0)))
|
|
399
|
+
return py::none();
|
|
400
|
+
return py::object(result_py);
|
|
400
401
|
}
|
|
401
402
|
|
|
402
|
-
template <typename index_at> py::
|
|
403
|
+
template <typename index_at> py::object get_member(index_at const& index, label_t label, scalar_kind_t scalar_kind) {
|
|
403
404
|
if (scalar_kind == scalar_kind_t::f32_k)
|
|
404
405
|
return get_typed_member<f32_t>(index, label);
|
|
405
406
|
else if (scalar_kind == scalar_kind_t::f64_k)
|
package/python/scripts/test.py
CHANGED
|
@@ -79,13 +79,15 @@ def test_index(
|
|
|
79
79
|
assert index.ndim == ndim
|
|
80
80
|
assert index.connectivity == connectivity
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
vector = np.random.uniform(0, limit, (index.ndim)).astype(numpy_type)
|
|
82
|
+
vector = random_vectors(count=1, ndim=ndim, dtype=numpy_type).flatten()
|
|
84
83
|
|
|
85
84
|
index.add(42, vector)
|
|
86
85
|
|
|
87
86
|
assert 42 in index
|
|
88
87
|
assert 42 in index.labels
|
|
88
|
+
assert 43 not in index
|
|
89
|
+
assert index[42] is not None
|
|
90
|
+
assert index[43] is None
|
|
89
91
|
if numpy_type != np.byte:
|
|
90
92
|
assert np.allclose(index[42], vector, atol=0.1)
|
|
91
93
|
|