usearch 2.20.0 → 2.20.2

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/README.md CHANGED
@@ -550,7 +550,7 @@ doi = {10.5281/zenodo.7949416},
550
550
  author = {Vardanian, Ash},
551
551
  title = {{USearch by Unum Cloud}},
552
552
  url = {https://github.com/unum-cloud/usearch},
553
- version = {2.20.0},
553
+ version = {2.20.2},
554
554
  year = {2023},
555
555
  month = oct,
556
556
  }
package/cpp/bench.cpp CHANGED
@@ -76,7 +76,7 @@ struct alignas(32) persisted_matrix_gt {
76
76
 
77
77
  persisted_matrix_gt(char const* path) noexcept(false) {
78
78
  if (!path || !std::strlen(path))
79
- throw std::invalid_argument("The file path is empty");
79
+ return; // Empty path results in default-constructed matrix
80
80
  #if defined(USEARCH_DEFINED_WINDOWS)
81
81
 
82
82
  HANDLE file_handle =
@@ -147,13 +147,14 @@ struct persisted_dataset_gt {
147
147
  persisted_matrix_gt<scalar_t> vectors_;
148
148
  persisted_matrix_gt<scalar_t> queries_;
149
149
  persisted_matrix_gt<compressed_slot_t> neighborhoods_;
150
+ std::vector<default_key_t> vector_ids_;
150
151
  std::vector<compressed_slot_t> neighborhoods_iota_{};
151
152
  std::size_t vectors_to_skip_{};
152
153
  std::size_t vectors_to_take_{};
153
154
 
154
155
  persisted_dataset_gt(char const* path_vectors, std::size_t vectors_to_skip = 0,
155
156
  std::size_t vectors_to_take = 0) noexcept(false)
156
- : vectors_(path_vectors), queries_(), neighborhoods_(), vectors_to_skip_(vectors_to_skip),
157
+ : vectors_(path_vectors), queries_(), neighborhoods_(), vector_ids_(), vectors_to_skip_(vectors_to_skip),
157
158
  vectors_to_take_(vectors_to_take) {
158
159
  neighborhoods_iota_.resize(vectors_.rows);
159
160
  std::iota(neighborhoods_iota_.begin(), neighborhoods_iota_.end(), 0);
@@ -161,8 +162,8 @@ struct persisted_dataset_gt {
161
162
 
162
163
  persisted_dataset_gt(char const* path_vectors, char const* path_queries, char const* path_neighbors,
163
164
  std::size_t vectors_to_skip = 0, std::size_t vectors_to_take = 0) noexcept(false)
164
- : vectors_(path_vectors), queries_(path_queries), neighborhoods_(path_neighbors), neighborhoods_iota_(),
165
- vectors_to_skip_(vectors_to_skip), vectors_to_take_(vectors_to_take) {
165
+ : vectors_(path_vectors), queries_(path_queries), neighborhoods_(path_neighbors), vector_ids_(),
166
+ neighborhoods_iota_(), vectors_to_skip_(vectors_to_skip), vectors_to_take_(vectors_to_take) {
166
167
 
167
168
  if (vectors_.cols != queries_.cols)
168
169
  throw std::invalid_argument("Contents and queries have different dimensionality");
@@ -170,7 +171,45 @@ struct persisted_dataset_gt {
170
171
  throw std::invalid_argument("Number of ground-truth neighborhoods doesn't match number of queries");
171
172
  }
172
173
 
174
+ persisted_dataset_gt(char const* path_vectors, char const* path_queries, char const* path_neighbors,
175
+ char const* path_ids, std::size_t vectors_to_skip = 0,
176
+ std::size_t vectors_to_take = 0) noexcept(false)
177
+ : vectors_(path_vectors), queries_(path_queries), neighborhoods_(path_neighbors), vector_ids_(),
178
+ neighborhoods_iota_(), vectors_to_skip_(vectors_to_skip), vectors_to_take_(vectors_to_take) {
179
+
180
+ // Handle self-search case (no queries/neighbors)
181
+ if (!queries_.scalars && !neighborhoods_.scalars) {
182
+ neighborhoods_iota_.resize(vectors_.rows);
183
+ std::iota(neighborhoods_iota_.begin(), neighborhoods_iota_.end(), 0);
184
+ } else {
185
+ if (vectors_.cols != queries_.cols)
186
+ throw std::invalid_argument("Contents and queries have different dimensionality");
187
+ if (queries_.rows != neighborhoods_.rows)
188
+ throw std::invalid_argument("Number of ground-truth neighborhoods doesn't match number of queries");
189
+ }
190
+
191
+ // Load IDs file if provided
192
+ if (path_ids && std::strlen(path_ids)) {
193
+ persisted_matrix_gt<std::int32_t> ids_matrix(path_ids);
194
+ if (ids_matrix.rows != vectors_.rows)
195
+ throw std::invalid_argument("Number of vector IDs doesn't match number of vectors");
196
+ if (ids_matrix.cols != 1)
197
+ throw std::invalid_argument("Vector IDs file should have exactly 1 column");
198
+
199
+ // Convert and copy IDs into memory
200
+ vector_ids_.resize(ids_matrix.rows);
201
+ for (std::size_t i = 0; i < ids_matrix.rows; ++i)
202
+ vector_ids_[i] = static_cast<default_key_t>(*ids_matrix.row(i));
203
+ }
204
+ }
205
+
173
206
  bool search_itself() const noexcept { return vectors_count() && !queries_.rows; }
207
+ bool has_vector_ids() const noexcept { return !vector_ids_.empty(); }
208
+
209
+ default_key_t vector_id(std::size_t i) const noexcept {
210
+ return has_vector_ids() ? vector_ids_[i + vectors_to_skip_]
211
+ : static_cast<default_key_t>(i + vectors_to_skip_);
212
+ }
174
213
 
175
214
  std::size_t dimensions() const noexcept { return vectors_.cols; }
176
215
  std::size_t queries_count() const noexcept { return search_itself() ? vectors_count() : queries_.rows; }
@@ -214,6 +253,7 @@ struct in_memory_dataset_gt {
214
253
  std::size_t vectors_count() const noexcept { return vectors_count_; }
215
254
  std::size_t queries_count() const noexcept { return vectors_count(); }
216
255
  std::size_t neighborhood_size() const noexcept { return 1; }
256
+ default_key_t vector_id(std::size_t i) const noexcept { return static_cast<default_key_t>(i); }
217
257
  scalar_t const* vector(std::size_t i) const noexcept { return vectors_.data() + i * dimensions_; }
218
258
  scalar_t const* query(std::size_t i) const noexcept { return queries_.data() + i * dimensions_; }
219
259
  compressed_slot_t const* neighborhood(std::size_t i) const noexcept {
@@ -299,8 +339,7 @@ void index_many(index_at& index, std::size_t n, vector_id_at const* ids, scalar_
299
339
  #if USEARCH_USE_OPENMP
300
340
  config.thread = omp_get_thread_num();
301
341
  #endif
302
- span_gt<scalar_at const> vector{vectors + dims * i, dims};
303
- index.add(ids[i], vector, config.thread);
342
+ index.add(ids[i], vectors + dims * i, config.thread);
304
343
  printer.progress++;
305
344
  if (config.thread == 0)
306
345
  printer.refresh();
@@ -347,7 +386,8 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
347
386
  if (construct) {
348
387
  // Perform insertions, evaluate speed
349
388
  std::vector<default_key_t> ids(dataset.vectors_count());
350
- std::iota(ids.begin(), ids.end(), 0);
389
+ for (std::size_t i = 0; i < dataset.vectors_count(); ++i)
390
+ ids[i] = static_cast<default_key_t>(dataset.vector_id(i));
351
391
  index_many(index, dataset.vectors_count(), ids.data(), dataset.vector(0), dataset.dimensions());
352
392
  }
353
393
 
@@ -433,7 +473,7 @@ void handler(int sig) {
433
473
  name = "<unknown>";
434
474
  }
435
475
  DWORD bytes_written;
436
- WriteFile(STDERR_FILENO, name, strlen(name), &bytes_written, NULL);
476
+ WriteFile(STDERR_FILENO, name, std::strlen(name), &bytes_written, NULL);
437
477
  WriteFile(STDERR_FILENO, "\n", 1, &bytes_written, NULL);
438
478
  }
439
479
  free(symbol);
@@ -454,6 +494,7 @@ struct args_t {
454
494
  std::string path_vectors;
455
495
  std::string path_queries;
456
496
  std::string path_neighbors;
497
+ std::string path_ids;
457
498
  std::string path_output = "last.usearch";
458
499
 
459
500
  std::size_t connectivity = default_connectivity();
@@ -557,17 +598,9 @@ void run_typed(dataset_at& dataset, args_t const& args, index_config_t config, i
557
598
 
558
599
  template <typename dataset_scalar_at> void bench_with_args(args_t const& args) {
559
600
  using dataset_t = persisted_dataset_gt<dataset_scalar_at, compressed_slot_t>;
560
- dataset_t dataset = args.path_queries.empty() && args.path_neighbors.empty()
561
- ? dataset_t(args.path_vectors.c_str(), //
562
- args.vectors_to_skip, //
563
- args.vectors_to_take //
564
- )
565
- : dataset_t(args.path_vectors.c_str(), //
566
- args.path_queries.c_str(), //
567
- args.path_neighbors.c_str(), //
568
- args.vectors_to_skip, //
569
- args.vectors_to_take //
570
- );
601
+
602
+ dataset_t dataset(args.path_vectors.c_str(), args.path_queries.c_str(), args.path_neighbors.c_str(),
603
+ args.path_ids.c_str(), args.vectors_to_skip, args.vectors_to_take);
571
604
  std::printf("-- Dimensions: %zu\n", dataset.dimensions());
572
605
  std::printf("-- Vectors count: %zu\n", dataset.vectors_count());
573
606
  std::printf("-- Queries count: %zu\n", dataset.queries_count());
@@ -602,9 +635,12 @@ int main(int argc, char** argv) {
602
635
 
603
636
  auto args = args_t{};
604
637
  auto cli = ( //
605
- (option("--vectors") & value("path", args.path_vectors)).doc(".[fhbd]bin file path to construct the index"),
606
- (option("--queries") & value("path", args.path_queries)).doc(".[fhbd]bin file path to query the index"),
607
- (option("--neighbors") & value("path", args.path_neighbors)).doc(".ibin file path with ground truth"),
638
+ (option("--vectors") & value("path", args.path_vectors))
639
+ .doc(".[fhbd]bin, .i8bin, .f32bin file path to construct the index"),
640
+ (option("--queries") & value("path", args.path_queries))
641
+ .doc(".[fhbd]bin, .i8bin, .f32bin file path to query the index"),
642
+ (option("--neighbors") & value("path", args.path_neighbors)).doc(".ibin, .i32bin file path with ground truth"),
643
+ (option("--ids") & value("path", args.path_ids)).doc(".i32bin file path with vector IDs (optional)"),
608
644
  (option("-o", "--output") & value("path", args.path_output)).doc(".usearch output file path"),
609
645
  (option("-b", "--big").set(args.big)).doc("Will switch to uint40_t for neighbors lists with over 4B entries"),
610
646
  (option("-j", "--threads") & value("integer", args.threads)).doc("Uses all available cores by default"),
@@ -665,6 +701,10 @@ int main(int argc, char** argv) {
665
701
  bench_with_args<f16_t>(args);
666
702
  else if (ends_with(args.path_vectors, ".bbin"))
667
703
  bench_with_args<b1x8_t>(args);
704
+ else if (ends_with(args.path_vectors, ".i8bin"))
705
+ bench_with_args<i8_t>(args);
706
+ else if (ends_with(args.path_vectors, ".f32bin"))
707
+ bench_with_args<f32_t>(args);
668
708
  else
669
709
  throw std::runtime_error("Unknown input file path");
670
710
 
@@ -9,7 +9,7 @@
9
9
 
10
10
  #define USEARCH_VERSION_MAJOR 2
11
11
  #define USEARCH_VERSION_MINOR 20
12
- #define USEARCH_VERSION_PATCH 0
12
+ #define USEARCH_VERSION_PATCH 2
13
13
 
14
14
  // Inferring C++ version
15
15
  // https://stackoverflow.com/a/61552074
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.20.0",
3
+ "version": "2.20.2",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian (https://ashvardanian.com/)",
6
6
  "license": "Apache 2.0",
Binary file
Binary file