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 CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.15.0"
3
+ version = "0.16.0"
4
4
  authors = ["Ashot Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
5
  description = "Smaller & Faster Single-File Vector Search Engine from Unum"
6
6
  edition = "2021"
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 = int;
11
- using distance_t = float;
12
- using punned_index_t = index_punned_dense_gt<label_t>;
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
- try {
44
- config_t config;
45
- config.expansion_add = static_cast<std::size_t>(expansion_add);
46
- config.expansion_search = static_cast<std::size_t>(expansion_search);
47
- config.connectivity = static_cast<std::size_t>(connectivity);
48
- config.max_elements = static_cast<std::size_t>(capacity);
49
- config.max_threads_add = std::thread::hardware_concurrency();
50
- config.max_threads_search = std::thread::hardware_concurrency();
51
-
52
- // TODO: Implement safe buffers for error messages, either here on in
53
- // the C++ type-punned implementation.
54
- // static constexpr std::size_t error_limit_k = 128;
55
- // struct safe_punned_index_t {
56
- // punned_index_t punned_index;
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
- char const* usearch_load(punned_index_t* index, char const* path) {
93
- try {
94
- index->load(path);
95
- return NULL;
96
- } catch (std::exception& e) {
97
- char* res = new char[strlen(e.what()) + 1];
98
- strcpy(res, e.what());
99
- return res;
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
- char const* usearch_view(punned_index_t* index, char const* path) {
104
- try {
105
- index->view(path);
106
- return NULL;
107
- } catch (std::exception& e) {
108
- char* res = new char[strlen(e.what()) + 1];
109
- strcpy(res, e.what());
110
- return res;
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
- int usearch_size(punned_index_t* index) { return index->size(); }
115
- int usearch_connectivity(punned_index_t* index) { return index->connectivity(); }
116
- int usearch_dimensions(punned_index_t* index) { return index->dimensions(); }
117
- int usearch_capacity(punned_index_t* index) { return index->capacity(); }
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
- char const* usearch_reserve(punned_index_t* index, int capacity) {
120
- try {
121
- index->reserve(capacity);
122
- return NULL;
123
- } catch (std::exception& e) {
124
- char* res = new char[strlen(e.what()) + 1];
125
- strcpy(res, e.what());
126
- return res;
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
- char const* usearch_add(punned_index_t* index, int label, float* vector) {
131
- // q:: I followed the java example to have try catches everywhere
132
- // but they are kind of useless as most errors are outside of cpp so
133
- // those translate into segfaults and are not caught by the runtime
134
- try {
135
- index->add(label, vector);
136
- } catch (std::exception& e) {
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
- return NULL;
142
- }
143
-
144
- SearchResults usearch_search(punned_index_t* index, float* query, int query_len, int limit) {
145
- // todo:: this could be allocated as golang slice
146
- // to avoid a copy. not sure how it interacts with gc
147
- // that is why doing this now
148
- label_t* matches_data = new label_t[limit];
149
-
150
- // todo:: pass the distances to outside world
151
- distance_t* dist_data = new distance_t[limit];
152
- SearchResults res{0};
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 float (*)(void const*, void const*) usearch_metric_t;
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
- void usearch_add(usearch_index_t, usearch_label_t, float const* vector, usearch_error_t*);
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, float const* query_vector, size_t results_limit, //
68
- usearch_label_t* found_labels, float* found_distances, usearch_error_t*);
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.15.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.reconstruct(42, span_t{&vec_reconstructed[0], 3ul});
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
- void reconstruct(label_t label, b1x8_t* vector) const { return reconstruct_(label, vector, casts_.to_b1x8); }
243
- void reconstruct(label_t label, f8_bits_t* vector) const { return reconstruct_(label, vector, casts_.to_f8); }
244
- void reconstruct(label_t label, f16_t* vector) const { return reconstruct_(label, vector, casts_.to_f16); }
245
- void reconstruct(label_t label, f32_t* vector) const { return reconstruct_(label, vector, casts_.to_f32); }
246
- void reconstruct(label_t label, f64_t* vector) const { return reconstruct_(label, vector, casts_.to_f64); }
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> void reconstruct_(label_t label, scalar_at* reconstructed, cast_t const& cast) const {
372
- id_t id = lookup_id_(label);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ashot Vardanian",
6
6
  "license": "Apache 2.0",
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::array_t<external_at> get_typed_member(index_at const& index, label_t label) {
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.reconstruct(label, (internal_at*)&result_py1d(0));
399
- return result_py;
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::array get_member(index_at const& index, label_t label, scalar_kind_t scalar_kind) {
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)
@@ -79,13 +79,15 @@ def test_index(
79
79
  assert index.ndim == ndim
80
80
  assert index.connectivity == connectivity
81
81
 
82
- limit = 0.7 if numpy_type != np.byte else 70
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
 
package/setup.py CHANGED
@@ -89,8 +89,8 @@ setup(
89
89
  ext_modules=ext_modules,
90
90
  install_requires=[
91
91
  'numpy',
92
- 'ucall',
93
92
  'pandas',
93
+ 'ucall; python_version >= "3.9"',
94
94
  ],
95
95
  extras_require={
96
96
  'jit': ['numba', 'cppyy'],