usearch 0.2.4 → 0.4.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.
@@ -15,6 +15,8 @@
15
15
  #define NAPI_CPP_EXCEPTIONS
16
16
  #endif
17
17
 
18
+ #include <new> // `std::bad_alloc`
19
+
18
20
  #include <napi.h>
19
21
  #include <node_api.h>
20
22
 
@@ -38,6 +40,10 @@ class Index : public Napi::ObjectWrap<Index> {
38
40
  Napi::Value GetCapacity(Napi::CallbackInfo const& ctx);
39
41
  Napi::Value GetConnectivity(Napi::CallbackInfo const& ctx);
40
42
 
43
+ void Save(Napi::CallbackInfo const& ctx);
44
+ void Load(Napi::CallbackInfo const& ctx);
45
+ void View(Napi::CallbackInfo const& ctx);
46
+
41
47
  void Add(Napi::CallbackInfo const& ctx);
42
48
  Napi::Value Search(Napi::CallbackInfo const& ctx);
43
49
 
@@ -54,6 +60,9 @@ Napi::Object Index::Init(Napi::Env env, Napi::Object exports) {
54
60
  InstanceMethod("connectivity", &Index::GetConnectivity),
55
61
  InstanceMethod("add", &Index::Add),
56
62
  InstanceMethod("search", &Index::Search),
63
+ InstanceMethod("save", &Index::Save),
64
+ InstanceMethod("load", &Index::Load),
65
+ InstanceMethod("view", &Index::View),
57
66
  });
58
67
 
59
68
  Napi::FunctionReference* constructor = new Napi::FunctionReference();
@@ -150,6 +159,57 @@ Napi::Value Index::GetCapacity(Napi::CallbackInfo const& ctx) {
150
159
  return Napi::Number::New(ctx.Env(), native_->capacity());
151
160
  }
152
161
 
162
+ void Index::Save(Napi::CallbackInfo const& ctx) {
163
+ Napi::Env env = ctx.Env();
164
+
165
+ int length = ctx.Length();
166
+ if (length == 0 || !ctx[0].IsString()) {
167
+ Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
168
+ return;
169
+ }
170
+
171
+ try {
172
+ std::string path = ctx[0].As<Napi::String>();
173
+ native_->save(path.c_str());
174
+ } catch (...) {
175
+ Napi::TypeError::New(env, "Serialization failed").ThrowAsJavaScriptException();
176
+ }
177
+ }
178
+
179
+ void Index::Load(Napi::CallbackInfo const& ctx) {
180
+ Napi::Env env = ctx.Env();
181
+
182
+ int length = ctx.Length();
183
+ if (length == 0 || !ctx[0].IsString()) {
184
+ Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
185
+ return;
186
+ }
187
+
188
+ try {
189
+ std::string path = ctx[0].As<Napi::String>();
190
+ native_->load(path.c_str());
191
+ } catch (...) {
192
+ Napi::TypeError::New(env, "Loading failed").ThrowAsJavaScriptException();
193
+ }
194
+ }
195
+
196
+ void Index::View(Napi::CallbackInfo const& ctx) {
197
+ Napi::Env env = ctx.Env();
198
+
199
+ int length = ctx.Length();
200
+ if (length == 0 || !ctx[0].IsString()) {
201
+ Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
202
+ return;
203
+ }
204
+
205
+ try {
206
+ std::string path = ctx[0].As<Napi::String>();
207
+ native_->view(path.c_str());
208
+ } catch (...) {
209
+ Napi::TypeError::New(env, "Memory-mapping failed").ThrowAsJavaScriptException();
210
+ }
211
+ }
212
+
153
213
  void Index::Add(Napi::CallbackInfo const& ctx) {
154
214
  Napi::Env env = ctx.Env();
155
215
  if (ctx.Length() < 2 || !ctx[0].IsNumber() || !ctx[1].IsTypedArray())
@@ -166,7 +226,14 @@ void Index::Add(Napi::CallbackInfo const& ctx) {
166
226
 
167
227
  if (native_->size() + 1 >= native_->capacity())
168
228
  native_->reserve(ceil2(native_->size() + 1));
169
- native_->add(label, vector);
229
+
230
+ try {
231
+ native_->add(label, vector);
232
+ } catch (std::bad_alloc const&) {
233
+ Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
234
+ } catch (...) {
235
+ Napi::TypeError::New(env, "Insertion failed").ThrowAsJavaScriptException();
236
+ }
170
237
  }
171
238
 
172
239
  Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
@@ -190,13 +257,20 @@ Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
190
257
 
191
258
  Napi::Uint32Array matches_js = Napi::Uint32Array::New(env, wanted);
192
259
  Napi::Float32Array distances_js = Napi::Float32Array::New(env, wanted);
193
- std::size_t count = native_->search(vector, wanted, matches_js.Data(), distances_js.Data());
194
-
195
- Napi::Object result_js = Napi::Object::New(env);
196
- result_js.Set("labels", matches_js);
197
- result_js.Set("distances", distances_js);
198
- result_js.Set("count", Napi::Number::New(env, count));
199
- return result_js;
260
+ try {
261
+ std::size_t count = native_->search(vector, wanted, matches_js.Data(), distances_js.Data());
262
+ Napi::Object result_js = Napi::Object::New(env);
263
+ result_js.Set("labels", matches_js);
264
+ result_js.Set("distances", distances_js);
265
+ result_js.Set("count", Napi::Number::New(env, count));
266
+ return result_js;
267
+ } catch (std::bad_alloc const&) {
268
+ Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
269
+ return {};
270
+ } catch (...) {
271
+ Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
272
+ return {};
273
+ }
200
274
  }
201
275
 
202
276
  Napi::Object InitAll(Napi::Env env, Napi::Object exports) { return Index::Init(env, exports); }
@@ -107,14 +107,14 @@ using shared_index_t = std::shared_ptr<native_index_t>;
107
107
 
108
108
  - (void)addImprecise:(UInt32)label
109
109
  vector:(void const * _Nonnull)vector {
110
- _native->add(label, (f16_converted_t const *)vector);
110
+ _native->add(label, (f16_bits_t const *)vector);
111
111
  }
112
112
 
113
113
  - (UInt32)searchImprecise:(void const* _Nonnull)vector
114
114
  count:(UInt32)wanted
115
115
  labels:(UInt32* _Nullable)labels
116
116
  distances:(Float32* _Nullable)distances {
117
- std::size_t found = _native->search((f16_converted_t const *)vector, static_cast<std::size_t>(wanted), labels, distances);
117
+ std::size_t found = _native->search((f16_bits_t const *)vector, static_cast<std::size_t>(wanted), labels, distances);
118
118
  return static_cast<UInt32>(found);
119
119
  }
120
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.2.4",
3
+ "version": "0.4.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
@@ -153,13 +153,16 @@ static void add_one_to_index(native_index_t& index, label_t label, py::buffer ve
153
153
  if (index.size() + 1 >= index.capacity())
154
154
  index.reserve(ceil2(index.size() + 1));
155
155
 
156
+ add_config_t config;
157
+ config.store_vector = copy;
158
+
156
159
  // https://docs.python.org/3/library/struct.html#format-characters
157
160
  if (vector_info.format == "e")
158
- index.add(label, reinterpret_cast<f16_bits_t const*>(vector_data), 0, copy);
161
+ index.add(label, reinterpret_cast<f16_bits_t const*>(vector_data), config);
159
162
  else if (vector_info.format == "f")
160
- index.add(label, reinterpret_cast<float const*>(vector_data), 0, copy);
163
+ index.add(label, reinterpret_cast<float const*>(vector_data), config);
161
164
  else if (vector_info.format == "d")
162
- index.add(label, reinterpret_cast<double const*>(vector_data), 0, copy);
165
+ index.add(label, reinterpret_cast<double const*>(vector_data), config);
163
166
  else
164
167
  throw std::invalid_argument("Incompatible scalars in the vector!");
165
168
  }
@@ -198,28 +201,37 @@ static void add_many_to_index( //
198
201
  // https://docs.python.org/3/library/struct.html#format-characters
199
202
  if (vectors_info.format == "e")
200
203
  multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
204
+ add_config_t config;
205
+ config.store_vector = copy;
206
+ config.thread = thread_idx;
201
207
  label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
202
208
  f16_bits_t const* vector =
203
209
  reinterpret_cast<f16_bits_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
204
- index.add(label, vector, thread_idx, copy);
210
+ index.add(label, vector, config);
205
211
  });
206
212
  else if (vectors_info.format == "f")
207
213
  multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
214
+ add_config_t config;
215
+ config.store_vector = copy;
216
+ config.thread = thread_idx;
208
217
  label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
209
218
  float const* vector = reinterpret_cast<float const*>(vectors_data + task_idx * vectors_info.strides[0]);
210
- index.add(label, vector, thread_idx, copy);
219
+ index.add(label, vector, config);
211
220
  });
212
221
  else if (vectors_info.format == "d")
213
222
  multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
223
+ add_config_t config;
224
+ config.store_vector = copy;
225
+ config.thread = thread_idx;
214
226
  label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
215
227
  double const* vector = reinterpret_cast<double const*>(vectors_data + task_idx * vectors_info.strides[0]);
216
- index.add(label, vector, thread_idx, copy);
228
+ index.add(label, vector, config);
217
229
  });
218
230
  else
219
231
  throw std::invalid_argument("Incompatible scalars in the vectors matrix!");
220
232
  }
221
233
 
222
- static py::tuple search_one_in_index(native_index_t& index, py::buffer vector, std::size_t wanted) {
234
+ static py::tuple search_one_in_index(native_index_t& index, py::buffer vector, std::size_t wanted, bool exact) {
223
235
 
224
236
  py::buffer_info vector_info = vector.request();
225
237
  Py_ssize_t vector_dimensions = vector_info.shape[0];
@@ -233,16 +245,19 @@ static py::tuple search_one_in_index(native_index_t& index, py::buffer vector, s
233
245
  auto labels_py1d = labels_py.mutable_unchecked<1>();
234
246
  auto distances_py1d = distances_py.mutable_unchecked<1>();
235
247
 
248
+ search_config_t config;
249
+ config.exact = exact;
250
+
236
251
  // https://docs.python.org/3/library/struct.html#format-characters
237
252
  if (vector_info.format == "e")
238
253
  count = index.search( //
239
- reinterpret_cast<f16_bits_t const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), 0);
254
+ reinterpret_cast<f16_bits_t const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), config);
240
255
  else if (vector_info.format == "f")
241
256
  count = index.search( //
242
- reinterpret_cast<float const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), 0);
257
+ reinterpret_cast<float const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), config);
243
258
  else if (vector_info.format == "d")
244
259
  count = index.search( //
245
- reinterpret_cast<double const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), 0);
260
+ reinterpret_cast<double const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), config);
246
261
  else
247
262
  throw std::invalid_argument("Incompatible scalars in the query vector!");
248
263
 
@@ -265,14 +280,14 @@ static py::tuple search_one_in_index(native_index_t& index, py::buffer vector, s
265
280
  * 2. matrix of distances,
266
281
  * 3. array with match counts.
267
282
  */
268
- static py::tuple search_many_in_index(native_index_t& index, py::buffer vectors, std::size_t wanted) {
283
+ static py::tuple search_many_in_index(native_index_t& index, py::buffer vectors, std::size_t wanted, bool exact) {
269
284
 
270
285
  if (wanted == 0)
271
286
  return py::tuple(3);
272
287
 
273
288
  py::buffer_info vectors_info = vectors.request();
274
289
  if (vectors_info.ndim == 1)
275
- return search_one_in_index(index, vectors, wanted);
290
+ return search_one_in_index(index, vectors, wanted, exact);
276
291
  if (vectors_info.ndim != 2)
277
292
  throw std::invalid_argument("Expects a matrix of vectors to add!");
278
293
 
@@ -292,21 +307,30 @@ static py::tuple search_many_in_index(native_index_t& index, py::buffer vectors,
292
307
  // https://docs.python.org/3/library/struct.html#format-characters
293
308
  if (vectors_info.format == "e")
294
309
  multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
310
+ search_config_t config;
311
+ config.thread = thread_idx;
312
+ config.exact = exact;
295
313
  f16_bits_t const* vector = (f16_bits_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
296
314
  counts_py1d(task_idx) = static_cast<Py_ssize_t>(
297
- index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), thread_idx));
315
+ index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), config));
298
316
  });
299
317
  else if (vectors_info.format == "f")
300
318
  multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
319
+ search_config_t config;
320
+ config.thread = thread_idx;
321
+ config.exact = exact;
301
322
  float const* vector = (float const*)(vectors_data + task_idx * vectors_info.strides[0]);
302
323
  counts_py1d(task_idx) = static_cast<Py_ssize_t>(
303
- index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), thread_idx));
324
+ index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), config));
304
325
  });
305
326
  else if (vectors_info.format == "d")
306
327
  multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
328
+ search_config_t config;
329
+ config.thread = thread_idx;
330
+ config.exact = exact;
307
331
  double const* vector = (double const*)(vectors_data + task_idx * vectors_info.strides[0]);
308
332
  counts_py1d(task_idx) = static_cast<Py_ssize_t>(
309
- index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), thread_idx));
333
+ index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), config));
310
334
  });
311
335
  else
312
336
  throw std::invalid_argument("Incompatible scalars in the query matrix!");
@@ -428,7 +452,8 @@ PYBIND11_MODULE(index, m) {
428
452
  i.def( //
429
453
  "search", &search_many_in_index, //
430
454
  py::arg("query"), //
431
- py::arg("count") = 10 //
455
+ py::arg("count") = 10, //
456
+ py::arg("exact") = false //
432
457
  );
433
458
 
434
459
  i.def("__len__", &native_index_t::size);
@@ -463,7 +488,9 @@ PYBIND11_MODULE(index, m) {
463
488
  index.reserve(ceil2(index.size() + 1));
464
489
  auto proxy = set.unchecked<1>();
465
490
  auto view = set_view_t{proxy.data(0), static_cast<std::size_t>(proxy.shape(0))};
466
- index.add(label, view, 0, copy);
491
+ add_config_t config;
492
+ config.store_vector = copy;
493
+ index.add(label, view, config);
467
494
  }, //
468
495
  py::arg("label"), //
469
496
  py::arg("set"), //
@@ -479,7 +506,7 @@ PYBIND11_MODULE(index, m) {
479
506
  auto view = set_view_t{proxy.data(0), static_cast<std::size_t>(proxy.shape(0))};
480
507
  auto labels_py = py::array_t<label_t>(py_shape_t{static_cast<Py_ssize_t>(count)});
481
508
  auto labels_proxy = labels_py.mutable_unchecked<1>();
482
- auto found = index.search(view, count, &labels_proxy(0), nullptr, 0);
509
+ auto found = index.search(view, count, &labels_proxy(0), nullptr);
483
510
  labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(found)});
484
511
  return labels_py;
485
512
  },
@@ -515,7 +542,7 @@ PYBIND11_MODULE(index, m) {
515
542
  if (index.size() + 1 >= index.capacity())
516
543
  index.reserve(ceil2(index.size() + 1));
517
544
  hash_buffer(index, array);
518
- index.add(label, index.buffer(), 0);
545
+ index.add(label, index.buffer());
519
546
  }, //
520
547
  py::arg("label"), //
521
548
  py::arg("array") //
@@ -529,7 +556,7 @@ PYBIND11_MODULE(index, m) {
529
556
  hash_buffer(index, array);
530
557
  auto labels_py = py::array_t<label_t>(py_shape_t{static_cast<Py_ssize_t>(count)});
531
558
  auto labels_proxy = labels_py.mutable_unchecked<1>();
532
- auto found = index.search(index.buffer(), count, &labels_proxy(0), nullptr, 0);
559
+ auto found = index.search(index.buffer(), count, &labels_proxy(0), nullptr);
533
560
  labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(found)});
534
561
  return labels_py;
535
562
  },
package/rust/lib.cpp CHANGED
@@ -9,7 +9,9 @@ using native_index_t = typename Index::native_index_t;
9
9
  Index::Index(std::shared_ptr<native_index_t> index) : index_(index) {}
10
10
 
11
11
  void Index::add_in_thread(uint32_t label, rust::Slice<float const> vector, size_t thread) const {
12
- index_->add(label, vector.data(), thread, true);
12
+ add_config_t config;
13
+ config.thread = thread;
14
+ index_->add(label, vector.data(), config);
13
15
  }
14
16
 
15
17
  Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const {
@@ -18,7 +20,9 @@ Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, s
18
20
  results.distances.reserve(count);
19
21
  for (size_t i = 0; i != count; ++i)
20
22
  results.labels.push_back(0), results.distances.push_back(0);
21
- results.count = index_->search(vector.data(), count, results.labels.data(), results.distances.data(), thread);
23
+ search_config_t config;
24
+ config.thread = thread;
25
+ results.count = index_->search(vector.data(), count, results.labels.data(), results.distances.data(), config);
22
26
  results.labels.truncate(results.count);
23
27
  results.distances.truncate(results.count);
24
28
  return results;
package/src/advanced.hpp CHANGED
@@ -469,21 +469,21 @@ class auto_index_gt {
469
469
  void reserve(std::size_t capacity) { index_->reserve(capacity); }
470
470
 
471
471
  // clang-format off
472
- void add(label_t label, f16_bits_t const* vector) { return add(label, vector, true, casts_.from_f16); }
473
- void add(label_t label, f32_t const* vector) { return add(label, vector, true, casts_.from_f32); }
474
- void add(label_t label, f64_t const* vector) { return add(label, vector, true, casts_.from_f64); }
472
+ void add(label_t label, f16_bits_t const* vector) { return add(label, vector, casts_.from_f16); }
473
+ void add(label_t label, f32_t const* vector) { return add(label, vector, casts_.from_f32); }
474
+ void add(label_t label, f64_t const* vector) { return add(label, vector, casts_.from_f64); }
475
475
 
476
- void add(label_t label, f16_bits_t const* vector, std::size_t thread, bool copy) { return add(label, vector, thread, copy, casts_.from_f16); }
477
- void add(label_t label, f32_t const* vector, std::size_t thread, bool copy) { return add(label, vector, thread, copy, casts_.from_f32); }
478
- void add(label_t label, f64_t const* vector, std::size_t thread, bool copy) { return add(label, vector, thread, copy, casts_.from_f64); }
476
+ void add(label_t label, f16_bits_t const* vector, add_config_t config) { return add(label, vector, config, casts_.from_f16); }
477
+ void add(label_t label, f32_t const* vector, add_config_t config) { return add(label, vector, config, casts_.from_f32); }
478
+ void add(label_t label, f64_t const* vector, add_config_t config) { return add(label, vector, config, casts_.from_f64); }
479
479
 
480
480
  std::size_t search(f16_bits_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances) const { return search(vector, wanted, matches, distances, casts_.from_f16); }
481
481
  std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances) const { return search(vector, wanted, matches, distances, casts_.from_f32); }
482
482
  std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances) const { return search(vector, wanted, matches, distances, casts_.from_f64); }
483
483
 
484
- std::size_t search(f16_bits_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread) const { return search(vector, wanted, matches, distances, thread, casts_.from_f16); }
485
- std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread) const { return search(vector, wanted, matches, distances, thread, casts_.from_f32); }
486
- std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread) const { return search(vector, wanted, matches, distances, thread, casts_.from_f64); }
484
+ std::size_t search(f16_bits_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, search_config_t config) const { return search(vector, wanted, matches, distances, config, casts_.from_f16); }
485
+ std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, search_config_t config) const { return search(vector, wanted, matches, distances, config, casts_.from_f32); }
486
+ std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, search_config_t config) const { return search(vector, wanted, matches, distances, config, casts_.from_f64); }
487
487
 
488
488
  static auto_index_gt ip(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) { return make(dimensions, accuracy, ip_metric(dimensions, accuracy), make_casts(accuracy), config); }
489
489
  static auto_index_gt l2(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) { return make(dimensions, accuracy, l2_metric(dimensions, accuracy), make_casts(accuracy), config); }
@@ -549,38 +549,40 @@ class auto_index_gt {
549
549
  }
550
550
 
551
551
  template <typename scalar_at>
552
- void add(label_t label, scalar_at const* vector, std::size_t thread, bool copy, cast_t const& cast) {
552
+ void add(label_t label, scalar_at const* vector, add_config_t config, cast_t const& cast) {
553
553
  byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
554
554
  std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
555
555
 
556
- byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * thread;
556
+ byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
557
557
  bool casted = cast(vector_data, casted_vector_bytes_, casted_data);
558
558
  if (casted)
559
- vector_data = casted_data, vector_bytes = casted_vector_bytes_, copy = true;
559
+ vector_data = casted_data, vector_bytes = casted_vector_bytes_, config.store_vector = true;
560
560
 
561
- index_->add(label, {vector_data, vector_bytes}, thread, copy);
561
+ index_->add(label, {vector_data, vector_bytes}, config);
562
562
  }
563
563
 
564
564
  template <typename scalar_at>
565
565
  std::size_t search( //
566
566
  scalar_at const* vector, std::size_t wanted, //
567
567
  label_t* matches, distance_t* distances, //
568
- std::size_t thread, cast_t const& cast) const {
568
+ search_config_t config, cast_t const& cast) const {
569
569
 
570
570
  byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
571
571
  std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
572
572
 
573
- byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * thread;
573
+ byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
574
574
  bool casted = cast(vector_data, casted_vector_bytes_, casted_data);
575
575
  if (casted)
576
576
  vector_data = casted_data, vector_bytes = casted_vector_bytes_;
577
577
 
578
- return index_->search({vector_data, vector_bytes}, wanted, matches, distances, thread);
578
+ return index_->search({vector_data, vector_bytes}, wanted, matches, distances, config);
579
579
  }
580
580
 
581
- template <typename scalar_at> void add(label_t label, scalar_at const* vector, bool copy, cast_t const& cast) {
581
+ template <typename scalar_at> void add(label_t label, scalar_at const* vector, cast_t const& cast) {
582
582
  thread_lock_t lock = thread_lock();
583
- return add(label, vector, lock.thread_id, copy, cast);
583
+ add_config_t config;
584
+ config.thread = lock.thread_id;
585
+ return add(label, vector, config, cast);
584
586
  }
585
587
 
586
588
  template <typename scalar_at>
@@ -589,7 +591,9 @@ class auto_index_gt {
589
591
  label_t* matches, distance_t* distances, //
590
592
  cast_t const& cast) const {
591
593
  thread_lock_t lock = thread_lock();
592
- return search(vector, wanted, matches, distances, lock.thread_id, cast);
594
+ search_config_t config;
595
+ config.thread = lock.thread_id;
596
+ return search(vector, wanted, matches, distances, config, cast);
593
597
  }
594
598
 
595
599
  static auto_index_gt make( //
package/src/bench.cpp CHANGED
@@ -43,6 +43,7 @@ using namespace unum::usearch;
43
43
  using namespace unum;
44
44
 
45
45
  using vector_id_t = std::uint32_t;
46
+ using vector_view_t = span_gt<float const>;
46
47
 
47
48
  template <typename element_at>
48
49
  std::size_t offset_of(element_at const* begin, element_at const* end, element_at v) noexcept {
@@ -271,12 +272,14 @@ struct running_stats_printer_t {
271
272
  template <typename index_at, typename vector_id_at, typename real_at>
272
273
  void index_many(index_at& native, std::size_t n, vector_id_at const* ids, real_at const* vectors, std::size_t dims) {
273
274
 
274
- using span_t = span_gt<float const>;
275
275
  running_stats_printer_t printer{n, "Indexing"};
276
276
 
277
277
  #pragma omp parallel for schedule(static, 32)
278
278
  for (std::size_t i = 0; i < n; ++i) {
279
- native.add(ids[i], span_t{vectors + dims * i, dims}, omp_get_thread_num(), true);
279
+ add_config_t config;
280
+ config.thread = omp_get_thread_num();
281
+ config.store_vector = true;
282
+ native.add(ids[i], vector_view_t{vectors + dims * i, dims}, config);
280
283
  printer.progress++;
281
284
  if (omp_get_thread_num() == 0)
282
285
  printer.refresh();
@@ -284,18 +287,20 @@ void index_many(index_at& native, std::size_t n, vector_id_at const* ids, real_a
284
287
  }
285
288
 
286
289
  template <typename index_at, typename vector_id_at, typename real_at>
287
- void search_many(index_at& native, std::size_t n, real_at const* vectors, std::size_t dims, std::size_t wanted,
288
- vector_id_at* ids, real_at* distances) {
290
+ void search_many( //
291
+ index_at& native, std::size_t n, real_at const* vectors, std::size_t dims, std::size_t wanted, vector_id_at* ids,
292
+ real_at* distances) {
289
293
 
290
- using span_t = span_gt<float const>;
291
294
  running_stats_printer_t printer{n, "Search"};
292
295
 
293
296
  #pragma omp parallel for schedule(static, 32)
294
297
  for (std::size_t i = 0; i < n; ++i) {
295
- native.search( //
296
- span_t{vectors + dims * i, dims}, wanted, //
297
- ids + wanted * i, distances + wanted * i, //
298
- omp_get_thread_num());
298
+ search_config_t config;
299
+ config.thread = omp_get_thread_num();
300
+ native.search( //
301
+ vector_view_t{vectors + dims * i, dims}, wanted, //
302
+ ids + wanted * i, distances + wanted * i, //
303
+ config);
299
304
  printer.progress++;
300
305
  if (omp_get_thread_num() == 0)
301
306
  printer.refresh();
@@ -5,6 +5,7 @@
5
5
  // Created by Ashot Vardanian on 5/11/23.
6
6
  //
7
7
 
8
+ @available(iOS 13, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
8
9
  extension Index {
9
10
 
10
11
  public func add(label: UInt32, vector: ArraySlice<Float32>) -> () {
package/swift/Test.swift CHANGED
@@ -8,6 +8,7 @@
8
8
  import Foundation
9
9
  import USearch
10
10
 
11
+ @available(iOS 13, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
11
12
  func testUnit() throws {
12
13
  let index = Index.l2(dimensions: 4, connectivity: 8)
13
14
  let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4]
File without changes