usearch 2.2.1 → 2.3.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.
@@ -219,7 +219,7 @@ jobs:
219
219
  publish_hashes:
220
220
  name: Add hashes to release notes
221
221
  runs-on: ubuntu-latest
222
- needs: [versioning, create_linux_deb_package, create_windows_dll_library, create_macos_library]
222
+ needs: [versioning, create_linux_deb_package, create_windows_dll_library, create_macos_library, deploy_docs_pages]
223
223
  steps:
224
224
  - name: Get last release ID
225
225
  id: last_release
@@ -266,6 +266,7 @@ jobs:
266
266
  * ${{ steps.hashes.outputs.asset_name_6 }} : `${{ steps.hashes.outputs.asset_hash_6 }}`
267
267
  * ${{ steps.hashes.outputs.asset_name_7 }} : `${{ steps.hashes.outputs.asset_hash_7 }}`
268
268
  * ${{ steps.hashes.outputs.asset_name_8 }} : `${{ steps.hashes.outputs.asset_hash_8 }}`
269
+ * ${{ steps.hashes.outputs.asset_name_9 }} : `${{ steps.hashes.outputs.asset_hash_9 }}`
269
270
 
270
271
 
271
272
  build_wheels:
package/.releaserc CHANGED
@@ -77,10 +77,14 @@
77
77
  "conanfile.py",
78
78
  "package.json",
79
79
  "Cargo.toml",
80
- "wasmer.toml"
80
+ "wasmer.toml",
81
+ "./include/usearch/index.hpp",
82
+ "README.md",
83
+ "./csharp/nuget/nuget-package.props",
84
+ "CITATION.cff"
81
85
  ],
82
86
  "message": "Build: Released ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
83
87
  }
84
88
  ]
85
89
  ]
86
- }
90
+ }
package/CITATION.cff CHANGED
@@ -5,7 +5,7 @@ authors:
5
5
  given-names: "Ash"
6
6
  orcid: "https://orcid.org/0000-0002-4882-1815"
7
7
  title: "USearch by Unum Cloud"
8
- version: 0.13.0
8
+ version: 2.3.1
9
9
  doi: 10.5281/zenodo.7949416
10
10
  date-released: 2022-06-12
11
11
  url: "https://github.com/unum-cloud/usearch"
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "2.2.1"
3
+ version = "2.3.1"
4
4
  authors = ["Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
5
  description = "Smaller & Faster Single-File Vector Search Engine from Unum"
6
6
  edition = "2021"
package/README.md CHANGED
@@ -392,7 +392,7 @@ doi = {10.5281/zenodo.7949416},
392
392
  author = {Vardanian, Ash},
393
393
  title = {{USearch by Unum Cloud}},
394
394
  url = {https://github.com/unum-cloud/usearch},
395
- version = {1.0.0},
395
+ version = {2.3.1},
396
396
  year = {2022},
397
397
  month = jun,
398
398
  }
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 = '2.2.1'
7
+ version = '2.3.1'
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/bench.cpp CHANGED
@@ -372,6 +372,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
372
372
  executor, [&](std::size_t progress, std::size_t total) {
373
373
  if (progress % 1000 == 0)
374
374
  printer.print(progress, total);
375
+ return true;
375
376
  });
376
377
  join_attempts = result.visited_members;
377
378
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <Project>
3
3
  <PropertyGroup>
4
- <Version Condition="'$(Version)' == ''">0.0.2</Version>
4
+ <Version Condition="'$(Version)' == ''">2.3.1</Version>
5
5
 
6
6
  <Authors>Unum</Authors>
7
7
  <Company>Unum</Company>
@@ -9,9 +9,9 @@
9
9
  #ifndef UNUM_USEARCH_HPP
10
10
  #define UNUM_USEARCH_HPP
11
11
 
12
- #define USEARCH_VERSION_MAJOR 0
13
- #define USEARCH_VERSION_MINOR 0
14
- #define USEARCH_VERSION_PATCH 0
12
+ #define USEARCH_VERSION_MAJOR 2
13
+ #define USEARCH_VERSION_MINOR 3
14
+ #define USEARCH_VERSION_PATCH 1
15
15
 
16
16
  // Inferring C++ version
17
17
  // https://stackoverflow.com/a/61552074
@@ -1212,9 +1212,10 @@ struct dummy_callback_t {
1212
1212
  *
1213
1213
  * This is particularly helpful when handling long-running tasks, like serialization,
1214
1214
  * saving, and loading from disk, or index-level joins.
1215
+ * The reporter checks return value to continue or stop the process, `false` means need to stop.
1215
1216
  */
1216
1217
  struct dummy_progress_t {
1217
- inline void operator()(std::size_t, std::size_t) const noexcept {}
1218
+ inline bool operator()(std::size_t /*processed*/, std::size_t /*total*/) const noexcept { return true; }
1218
1219
  };
1219
1220
 
1220
1221
  /**
@@ -2690,6 +2691,10 @@ class index_gt {
2690
2691
  if (!output(&header, sizeof(header)))
2691
2692
  return result.failed("Failed to serialize the header into stream");
2692
2693
 
2694
+ // Progress status
2695
+ std::size_t processed = 0;
2696
+ std::size_t const total = 2 * header.size;
2697
+
2693
2698
  // Export the number of levels per node
2694
2699
  // That is both enough to estimate the overall memory consumption,
2695
2700
  // and to be able to estimate the offsets of every entry in the file.
@@ -2697,15 +2702,18 @@ class index_gt {
2697
2702
  node_t node = node_at_(i);
2698
2703
  level_t level = node.level();
2699
2704
  if (!output(&level, sizeof(level)))
2700
- return result.failed("Failed to serialize nodes levels into stream");
2705
+ return result.failed("Failed to serialize into stream");
2706
+ if (!progress(++processed, total))
2707
+ return result.failed("Terminated by user");
2701
2708
  }
2702
2709
 
2703
2710
  // After that dump the nodes themselves
2704
2711
  for (std::size_t i = 0; i != header.size; ++i) {
2705
2712
  span_bytes_t node_bytes = node_bytes_(node_at_(i));
2706
2713
  if (!output(node_bytes.data(), node_bytes.size()))
2707
- return result.failed("Failed to serialize nodes into stream");
2708
- progress(i, header.size);
2714
+ return result.failed("Failed to serialize into stream");
2715
+ if (!progress(++processed, total))
2716
+ return result.failed("Terminated by user");
2709
2717
  }
2710
2718
 
2711
2719
  return {};
@@ -2763,7 +2771,8 @@ class index_gt {
2763
2771
  return result.failed("Failed to pull nodes from the stream");
2764
2772
  }
2765
2773
  nodes_[i] = node_t{node_bytes.data()};
2766
- progress(i, header.size);
2774
+ if (!progress(i + 1, header.size))
2775
+ return result.failed("Terminated by user");
2767
2776
  }
2768
2777
  return {};
2769
2778
  }
@@ -2936,7 +2945,8 @@ class index_gt {
2936
2945
  // Rapidly address all the nodes
2937
2946
  for (std::size_t i = 0; i != header.size; ++i) {
2938
2947
  nodes_[i] = node_t{(byte_t*)file.data() + offsets[i]};
2939
- progress(i, header.size);
2948
+ if (!progress(i + 1, header.size))
2949
+ return result.failed("Terminated by user");
2940
2950
  }
2941
2951
  viewed_file_ = std::move(file);
2942
2952
  return {};
@@ -2986,8 +2996,13 @@ class index_gt {
2986
2996
  using slot_level_allocator_t = typename dynamic_allocator_traits_t::template rebind_alloc<slot_level_t>;
2987
2997
  buffer_gt<slot_level_t, slot_level_allocator_t> slots_and_levels(size());
2988
2998
 
2999
+ // Progress status
3000
+ std::atomic<bool> do_tasks{true};
3001
+ std::atomic<std::size_t> processed{0};
3002
+ std::size_t const total = 3 * slots_and_levels.size();
3003
+
2989
3004
  // For every bottom level node, determine its parent cluster
2990
- executor.fixed(slots_and_levels.size(), [&](std::size_t thread_idx, std::size_t old_slot) {
3005
+ executor.dynamic(slots_and_levels.size(), [&](std::size_t thread_idx, std::size_t old_slot) {
2991
3006
  context_t& context = contexts_[thread_idx];
2992
3007
  std::size_t cluster = search_for_one_( //
2993
3008
  values[citerator_at(old_slot)], //
@@ -2997,7 +3012,13 @@ class index_gt {
2997
3012
  static_cast<compressed_slot_t>(old_slot), //
2998
3013
  static_cast<compressed_slot_t>(cluster), //
2999
3014
  node_at_(old_slot).level()};
3015
+ ++processed;
3016
+ if (thread_idx == 0)
3017
+ do_tasks = progress(processed.load(), total);
3018
+ return do_tasks.load();
3000
3019
  });
3020
+ if (!do_tasks.load())
3021
+ return;
3001
3022
 
3002
3023
  // Where the actual permutation happens:
3003
3024
  std::sort(slots_and_levels.begin(), slots_and_levels.end(), [](slot_level_t const& a, slot_level_t const& b) {
@@ -3027,8 +3048,8 @@ class index_gt {
3027
3048
  neighbor = static_cast<compressed_slot_t>(old_slot_to_new[compressed_slot_t(neighbor)]);
3028
3049
 
3029
3050
  reordered_nodes[new_slot] = new_node;
3030
-
3031
- progress(new_slot, slots_and_levels.size());
3051
+ if (!progress(++processed, total))
3052
+ return;
3032
3053
  }
3033
3054
 
3034
3055
  for (std::size_t new_slot = 0; new_slot != slots_and_levels.size(); ++new_slot) {
@@ -3036,6 +3057,8 @@ class index_gt {
3036
3057
  slot_transition(node_at_(old_slot).ckey(), //
3037
3058
  static_cast<compressed_slot_t>(old_slot), //
3038
3059
  static_cast<compressed_slot_t>(new_slot));
3060
+ if (!progress(++processed, total))
3061
+ return;
3039
3062
  }
3040
3063
 
3041
3064
  nodes_ = std::move(reordered_nodes);
@@ -3064,9 +3087,13 @@ class index_gt {
3064
3087
  executor_at&& executor = executor_at{}, //
3065
3088
  progress_at&& progress = progress_at{}) noexcept {
3066
3089
 
3090
+ // Progress status
3091
+ std::atomic<bool> do_tasks{true};
3092
+ std::atomic<std::size_t> processed{0};
3093
+
3067
3094
  // Erase all the incoming links
3068
3095
  std::size_t nodes_count = size();
3069
- executor.fixed(nodes_count, [&](std::size_t, std::size_t node_idx) {
3096
+ executor.dynamic(nodes_count, [&](std::size_t thread_idx, std::size_t node_idx) {
3070
3097
  node_t node = node_at_(node_idx);
3071
3098
  for (level_t level = 0; level <= node.level(); ++level) {
3072
3099
  neighbors_ref_t neighbors = neighbors_(node, level);
@@ -3079,8 +3106,14 @@ class index_gt {
3079
3106
  neighbors.push_back(neighbor_slot);
3080
3107
  }
3081
3108
  }
3082
- progress(node_idx, nodes_count);
3109
+ ++processed;
3110
+ if (thread_idx == 0)
3111
+ do_tasks = progress(processed.load(), nodes_count);
3112
+ return do_tasks.load();
3083
3113
  });
3114
+
3115
+ // At the end report the latest numbers, because the reporter thread may be finished earlier
3116
+ progress(processed.load(), nodes_count);
3084
3117
  }
3085
3118
 
3086
3119
  private:
@@ -3710,7 +3743,10 @@ static join_result_t join( //
3710
3743
  passed_rounds = ++rounds;
3711
3744
  total_rounds = passed_rounds + free_men.size();
3712
3745
  }
3713
- progress(passed_rounds, total_rounds);
3746
+ if (thread_idx == 0 && !progress(passed_rounds, total_rounds)) {
3747
+ atomic_error.store("Terminated by user");
3748
+ break;
3749
+ }
3714
3750
  while (men_locks.atomic_set(free_man_slot))
3715
3751
  ;
3716
3752
 
@@ -767,7 +767,9 @@ class index_dense_gt {
767
767
  * @brief Saves serialized binary index representation to a stream.
768
768
  */
769
769
  template <typename output_callback_at, typename progress_at = dummy_progress_t>
770
- serialization_result_t save_to_stream(output_callback_at&& output, serialization_config_t config = {}) const {
770
+ serialization_result_t save_to_stream(output_callback_at&& output, //
771
+ serialization_config_t config = {}, //
772
+ progress_at&& progress = {}) const {
771
773
 
772
774
  serialization_result_t result;
773
775
  std::uint64_t matrix_rows = 0;
@@ -831,7 +833,7 @@ class index_dense_gt {
831
833
  }
832
834
 
833
835
  // Save the actual proximity graph
834
- return typed_->save_to_stream(std::forward<output_callback_at>(output));
836
+ return typed_->save_to_stream(std::forward<output_callback_at>(output), std::forward<progress_at>(progress));
835
837
  }
836
838
 
837
839
  /**
@@ -853,8 +855,10 @@ class index_dense_gt {
853
855
  * @param[in] config Configuration parameters for imports.
854
856
  * @return Outcome descriptor explicitly convertible to boolean.
855
857
  */
856
- template <typename input_callback_at>
857
- serialization_result_t load_from_stream(input_callback_at&& input, serialization_config_t config = {}) {
858
+ template <typename input_callback_at, typename progress_at = dummy_progress_t>
859
+ serialization_result_t load_from_stream(input_callback_at&& input, //
860
+ serialization_config_t config = {}, //
861
+ progress_at&& progress = {}) {
858
862
 
859
863
  // Discard all previous memory allocations of `vectors_tape_allocator_`
860
864
  reset();
@@ -915,7 +919,7 @@ class index_dense_gt {
915
919
  }
916
920
 
917
921
  // Pull the actual proximity graph
918
- result = typed_->load_from_stream(std::forward<input_callback_at>(input));
922
+ result = typed_->load_from_stream(std::forward<input_callback_at>(input), std::forward<progress_at>(progress));
919
923
  if (!result)
920
924
  return result;
921
925
  if (typed_->size() != static_cast<std::size_t>(matrix_rows))
@@ -931,7 +935,10 @@ class index_dense_gt {
931
935
  * @param[in] config Configuration parameters for imports.
932
936
  * @return Outcome descriptor explicitly convertible to boolean.
933
937
  */
934
- serialization_result_t view(memory_mapped_file_t file, std::size_t offset = 0, serialization_config_t config = {}) {
938
+ template <typename progress_at = dummy_progress_t>
939
+ serialization_result_t view(memory_mapped_file_t file, //
940
+ std::size_t offset = 0, serialization_config_t config = {}, //
941
+ progress_at&& progress = {}) {
935
942
 
936
943
  // Discard all previous memory allocations of `vectors_tape_allocator_`
937
944
  reset();
@@ -997,7 +1004,7 @@ class index_dense_gt {
997
1004
  }
998
1005
 
999
1006
  // Pull the actual proximity graph
1000
- result = typed_->view(std::move(file), offset);
1007
+ result = typed_->view(std::move(file), offset, std::forward<progress_at>(progress));
1001
1008
  if (!result)
1002
1009
  return result;
1003
1010
  if (typed_->size() != static_cast<std::size_t>(matrix_rows))
@@ -1019,7 +1026,9 @@ class index_dense_gt {
1019
1026
  * @param[in] config Configuration parameters for exports.
1020
1027
  * @return Outcome descriptor explicitly convertible to boolean.
1021
1028
  */
1022
- serialization_result_t save(output_file_t file, serialization_config_t config = {}) const {
1029
+ template <typename progress_at = dummy_progress_t>
1030
+ serialization_result_t save(output_file_t file, serialization_config_t config = {},
1031
+ progress_at&& progress = {}) const {
1023
1032
 
1024
1033
  serialization_result_t io_result = file.open_if_not();
1025
1034
  if (!io_result)
@@ -1030,7 +1039,7 @@ class index_dense_gt {
1030
1039
  io_result = file.write(buffer, length);
1031
1040
  return !!io_result;
1032
1041
  },
1033
- config);
1042
+ config, std::forward<progress_at>(progress));
1034
1043
 
1035
1044
  if (!stream_result)
1036
1045
  return stream_result;
@@ -1041,8 +1050,11 @@ class index_dense_gt {
1041
1050
  * @brief Memory-maps the serialized binary index representation from disk,
1042
1051
  * @b without copying data into RAM, and fetching it on-demand.
1043
1052
  */
1044
- serialization_result_t save(memory_mapped_file_t file, std::size_t offset = 0,
1045
- serialization_config_t config = {}) const {
1053
+ template <typename progress_at = dummy_progress_t>
1054
+ serialization_result_t save(memory_mapped_file_t file, //
1055
+ std::size_t offset = 0, //
1056
+ serialization_config_t config = {}, //
1057
+ progress_at&& progress = {}) const {
1046
1058
 
1047
1059
  serialization_result_t io_result = file.open_if_not();
1048
1060
  if (!io_result)
@@ -1056,7 +1068,7 @@ class index_dense_gt {
1056
1068
  offset += length;
1057
1069
  return true;
1058
1070
  },
1059
- config);
1071
+ config, std::forward<progress_at>(progress));
1060
1072
 
1061
1073
  return stream_result;
1062
1074
  }
@@ -1067,7 +1079,8 @@ class index_dense_gt {
1067
1079
  * @param[in] config Configuration parameters for imports.
1068
1080
  * @return Outcome descriptor explicitly convertible to boolean.
1069
1081
  */
1070
- serialization_result_t load(input_file_t file, serialization_config_t config = {}) {
1082
+ template <typename progress_at = dummy_progress_t>
1083
+ serialization_result_t load(input_file_t file, serialization_config_t config = {}, progress_at&& progress = {}) {
1071
1084
 
1072
1085
  serialization_result_t io_result = file.open_if_not();
1073
1086
  if (!io_result)
@@ -1078,7 +1091,7 @@ class index_dense_gt {
1078
1091
  io_result = file.read(buffer, length);
1079
1092
  return !!io_result;
1080
1093
  },
1081
- config);
1094
+ config, std::forward<progress_at>(progress));
1082
1095
 
1083
1096
  if (!stream_result)
1084
1097
  return stream_result;
@@ -1089,7 +1102,11 @@ class index_dense_gt {
1089
1102
  * @brief Memory-maps the serialized binary index representation from disk,
1090
1103
  * @b without copying data into RAM, and fetching it on-demand.
1091
1104
  */
1092
- serialization_result_t load(memory_mapped_file_t file, std::size_t offset = 0, serialization_config_t config = {}) {
1105
+ template <typename progress_at = dummy_progress_t>
1106
+ serialization_result_t load(memory_mapped_file_t file, //
1107
+ std::size_t offset = 0, //
1108
+ serialization_config_t config = {}, //
1109
+ progress_at&& progress = {}) {
1093
1110
 
1094
1111
  serialization_result_t io_result = file.open_if_not();
1095
1112
  if (!io_result)
@@ -1103,17 +1120,23 @@ class index_dense_gt {
1103
1120
  offset += length;
1104
1121
  return true;
1105
1122
  },
1106
- config);
1123
+ config, std::forward<progress_at>(progress));
1107
1124
 
1108
1125
  return stream_result;
1109
1126
  }
1110
1127
 
1111
- serialization_result_t save(char const* file_path, serialization_config_t config = {}) const {
1112
- return save(output_file_t(file_path), config);
1128
+ template <typename progress_at = dummy_progress_t>
1129
+ serialization_result_t save(char const* file_path, //
1130
+ serialization_config_t config = {}, //
1131
+ progress_at&& progress = {}) const {
1132
+ return save(output_file_t(file_path), config, std::forward<progress_at>(progress));
1113
1133
  }
1114
1134
 
1115
- serialization_result_t load(char const* file_path, serialization_config_t config = {}) {
1116
- return load(input_file_t(file_path), config);
1135
+ template <typename progress_at = dummy_progress_t>
1136
+ serialization_result_t load(char const* file_path, //
1137
+ serialization_config_t config = {}, //
1138
+ progress_at&& progress = {}) {
1139
+ return load(input_file_t(file_path), config, std::forward<progress_at>(progress));
1117
1140
  }
1118
1141
 
1119
1142
  /**
@@ -41,7 +41,15 @@
41
41
  #endif
42
42
 
43
43
  #if USEARCH_USE_SIMSIMD
44
- #if __linux__
44
+ #if defined(USEARCH_DEFINED_LINUX)
45
+ #define SIMSIMD_TARGET_X86_AVX2 1
46
+ #define SIMSIMD_TARGET_X86_AVX512 1
47
+ #define SIMSIMD_TARGET_ARM_NEON 1
48
+ #define SIMSIMD_TARGET_ARM_SVE 1
49
+ #include <simsimd/simsimd.h>
50
+ #elif defined(USEARCH_DEFINED_APPLE)
51
+ #define SIMSIMD_TARGET_X86_AVX2 1
52
+ #define SIMSIMD_TARGET_ARM_NEON 1
45
53
  #include <simsimd/simsimd.h>
46
54
  #endif
47
55
  #endif
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.2.1",
3
+ "version": "2.3.1",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian",
6
6
  "license": "Apache 2.0",