usearch 0.9.7 → 0.11.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.
Files changed (41) hide show
  1. package/.github/workflows/release.yml +1 -1
  2. package/.gitmodules +3 -0
  3. package/.vscode/settings.json +2 -0
  4. package/Cargo.toml +1 -1
  5. package/Package.swift +1 -0
  6. package/README.md +1 -1
  7. package/VERSION +1 -1
  8. package/binding.gyp +4 -3
  9. package/build.gradle +1 -1
  10. package/build.rs +1 -0
  11. package/conanfile.py +1 -1
  12. package/docs/compilation.md +6 -3
  13. package/include/usearch/usearch.hpp +168 -45
  14. package/package.json +1 -1
  15. package/python/lib.cpp +42 -23
  16. package/python/scripts/bench.py +2 -2
  17. package/python/scripts/test.py +1 -1
  18. package/python/usearch/index.py +298 -0
  19. package/robin-map/.codecov.yml +5 -0
  20. package/robin-map/.github/workflows/ci.yml +136 -0
  21. package/robin-map/CMakeLists.txt +86 -0
  22. package/robin-map/LICENSE +21 -0
  23. package/robin-map/README.md +483 -0
  24. package/robin-map/cmake/tsl-robin-mapConfig.cmake.in +9 -0
  25. package/robin-map/doxygen.conf +2483 -0
  26. package/robin-map/include/tsl/robin_growth_policy.h +406 -0
  27. package/robin-map/include/tsl/robin_hash.h +1639 -0
  28. package/robin-map/include/tsl/robin_map.h +807 -0
  29. package/robin-map/include/tsl/robin_set.h +660 -0
  30. package/robin-map/tests/CMakeLists.txt +26 -0
  31. package/robin-map/tests/custom_allocator_tests.cpp +138 -0
  32. package/robin-map/tests/main.cpp +26 -0
  33. package/robin-map/tests/policy_tests.cpp +97 -0
  34. package/robin-map/tests/robin_map_tests.cpp +1451 -0
  35. package/robin-map/tests/robin_set_tests.cpp +164 -0
  36. package/robin-map/tests/utils.h +443 -0
  37. package/robin-map/tsl-robin-map.natvis +78 -0
  38. package/setup.py +4 -2
  39. package/src/CMakeLists.txt +1 -0
  40. package/src/punned.hpp +56 -6
  41. package/src/test.cpp +32 -8
@@ -231,7 +231,7 @@ jobs:
231
231
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
232
232
  with:
233
233
  file: docs.tar.gz
234
- asset_name: docs.tar.gz
234
+ update_latest_release: true
235
235
 
236
236
  deploy_docs_pages:
237
237
  name: Deploy GitHub Pages
package/.gitmodules CHANGED
@@ -4,3 +4,6 @@
4
4
  [submodule "fp16"]
5
5
  path = fp16
6
6
  url = https://github.com/maratyszcza/fp16
7
+ [submodule "robin-map"]
8
+ path = robin-map
9
+ url = https://github.com/tessil/robin-map
@@ -125,10 +125,12 @@
125
125
  "OPENMP",
126
126
  "preprocess",
127
127
  "Quickstart",
128
+ "rtype",
128
129
  "simsimd",
129
130
  "SLOC",
130
131
  "usearch",
131
132
  "usecases"
132
133
  ],
134
+ "autoDocstring.docstringFormat": "sphinx",
133
135
  "java.configuration.updateBuildConfiguration": "interactive"
134
136
  }
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.9.7"
3
+ version = "0.11.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/Package.swift CHANGED
@@ -20,6 +20,7 @@ let package = Package(
20
20
  .headerSearchPath("../include/"),
21
21
  .headerSearchPath("../src/"),
22
22
  .headerSearchPath("../fp16/include/"),
23
+ .headerSearchPath("../robin-map/include/"),
23
24
  .headerSearchPath("../simismd/include/")
24
25
  ]
25
26
  ),
package/README.md CHANGED
@@ -308,7 +308,7 @@ def python_dot(a, b, n, m):
308
308
  c += a_array[i] * b_array[i]
309
309
  return c
310
310
 
311
- index = Index(ndim=ndim, metric_pointer=python_dot.address)
311
+ index = Index(ndim=ndim, metric=python_dot.address)
312
312
  ```
313
313
 
314
314
  [numba]: https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.7
1
+ 0.11.0
package/binding.gyp CHANGED
@@ -10,6 +10,7 @@
10
10
  "include",
11
11
  "src",
12
12
  "fp16/include",
13
+ "robin-map/include",
13
14
  "simsimd/include"
14
15
  ],
15
16
  "dependencies": [
@@ -23,9 +24,9 @@
23
24
  "MACOSX_DEPLOYMENT_TARGET": "10.15"
24
25
  },
25
26
  "msvs_settings": {
26
- "VCCLCompilerTool": {
27
- "ExceptionHandling": 1,
28
- "AdditionalOptions": [ "-std:c++11" ]
27
+ "VCCLCompilerTool": {
28
+ "ExceptionHandling": 1,
29
+ "AdditionalOptions": ["-std:c++11"]
29
30
  }
30
31
  }
31
32
  }
package/build.gradle CHANGED
@@ -45,7 +45,7 @@ model {
45
45
  include "**/*.cpp"
46
46
  }
47
47
  exportedHeaders {
48
- srcDirs "include", "src", "fp16/include", "simsimd/include", "${Jvm.current().javaHome}/include"
48
+ srcDirs "include", "src", "fp16/include", "robin-map/include", "simsimd/include", "${Jvm.current().javaHome}/include"
49
49
  }
50
50
  }
51
51
  }
package/build.rs CHANGED
@@ -7,6 +7,7 @@ fn main() {
7
7
  .include("rust")
8
8
  .include("src")
9
9
  .include("fp16/include")
10
+ .include("robin-map/include")
10
11
  .include("simsimd/include")
11
12
  .compile("usearch");
12
13
 
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.9.7'
7
+ version = '0.11.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'
@@ -63,8 +63,7 @@ Benchmarking:
63
63
  ## Python
64
64
 
65
65
  ```sh
66
- pip install -e .
67
- pytest python/scripts/test.py
66
+ pip install -e . && pytest python/scripts/test.py
68
67
  ```
69
68
 
70
69
  Linting:
@@ -131,7 +130,11 @@ g++ -shared -o USearchJNI.dll cloud_unum_usearch_Index.o -Wl,--add-stdcall-alias
131
130
 
132
131
  # MacOS
133
132
  g++ -std=c++11 -c -fPIC \
134
- -I../../../../include -I../../../../src -I../../../../fp16/include -I../../../../simsimd/include \
133
+ -I../../../../include \
134
+ -I../../../../src \
135
+ -I../../../../fp16/include \
136
+ -I../../../../robin-map/include \
137
+ -I../../../../simsimd/include \
135
138
  -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin cloud_unum_usearch_Index.cpp -o cloud_unum_usearch_Index.o
136
139
  g++ -dynamiclib -o libusearch.dylib cloud_unum_usearch_Index.o -lc
137
140
  # Run linking to that directory
@@ -9,6 +9,18 @@
9
9
  #ifndef UNUM_USEARCH_H
10
10
  #define UNUM_USEARCH_H
11
11
 
12
+ #if !defined(USEARCH_VERSION_MAJOR)
13
+ #define USEARCH_VERSION_MAJOR 0
14
+ #endif
15
+
16
+ #if !defined(USEARCH_VERSION_MINOR)
17
+ #define USEARCH_VERSION_MINOR 0
18
+ #endif
19
+
20
+ #if !defined(USEARCH_VERSION_PATCH)
21
+ #define USEARCH_VERSION_PATCH 0
22
+ #endif
23
+
12
24
  // Inferring C++ version
13
25
  #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
14
26
  #define USEARCH_IS_CPP17
@@ -61,7 +73,7 @@
61
73
  #include <mutex> // `std::unique_lock` - replacement candidate
62
74
  #include <random> // `std::default_random_engine` - replacement candidate
63
75
  #include <stdexcept> // `std::runtime_exception`
64
- #include <utility> // `std::exchange`, `std::pair`
76
+ #include <utility> // `std::pair`
65
77
  #include <vector> // `std::vector`
66
78
 
67
79
  // Prefetching
@@ -125,12 +137,20 @@ inline std::size_t ceil2(std::size_t v) noexcept {
125
137
 
126
138
  template <typename at> void misaligned_store(void* ptr, at v) noexcept { std::memcpy(ptr, &v, sizeof(v)); }
127
139
 
140
+ /// @brief Simply dereferencing misaligned pointers can be dangerous.
128
141
  template <typename at> at misaligned_load(void* ptr) noexcept {
129
142
  at v;
130
143
  std::memcpy(&v, ptr, sizeof(v));
131
144
  return v;
132
145
  }
133
146
 
147
+ /// @brief The `std::exchange` alternative for C++11.
148
+ template <typename at, typename other_at = at> at exchange(at& obj, other_at&& new_value) {
149
+ at old_value = std::move(obj);
150
+ obj = std::forward<other_at>(new_value);
151
+ return old_value;
152
+ }
153
+
134
154
  template <typename at> class misaligned_ref_gt {
135
155
  byte_t* ptr_;
136
156
 
@@ -141,10 +161,8 @@ template <typename at> class misaligned_ref_gt {
141
161
  misaligned_store<at>(ptr_, v);
142
162
  return *this;
143
163
  }
144
- misaligned_ref_gt& operator=(at v) noexcept {
145
- misaligned_store<at>(ptr_, v);
146
- return *this;
147
- }
164
+
165
+ void reset(byte_t* ptr) noexcept { ptr_ = ptr; }
148
166
  };
149
167
 
150
168
  template <typename at> class misaligned_ptr_gt {
@@ -781,6 +799,13 @@ template <typename scalar_at> class span_gt {
781
799
  constexpr std::size_t default_connectivity() { return 16; }
782
800
  constexpr std::size_t default_expansion_add() { return 128; }
783
801
  constexpr std::size_t default_expansion_search() { return 64; }
802
+ constexpr std::size_t default_allocator_entry_bytes() { return 64; }
803
+
804
+ /**
805
+ * @brief The "magic" sequence helps infer the type of the file.
806
+ * USearch indexes start with the "unumusearch" string.
807
+ */
808
+ static constexpr char const* default_magic() { return "unumusearch"; }
784
809
 
785
810
  /**
786
811
  * @brief Configuration settings for the index construction.
@@ -808,6 +833,7 @@ struct config_t {
808
833
  std::size_t max_elements = 0;
809
834
  std::size_t max_threads_add = 1;
810
835
  std::size_t max_threads_search = 1;
836
+ std::size_t vector_alignment = 1;
811
837
 
812
838
  std::size_t max_threads() const noexcept { return (std::max)(max_threads_add, max_threads_search); }
813
839
  std::size_t concurrency() const noexcept { return (std::min)(max_threads_add, max_threads_search); }
@@ -825,6 +851,67 @@ struct search_config_t {
825
851
  bool exact{};
826
852
  };
827
853
 
854
+ using file_header_t = byte_t[64];
855
+
856
+ /**
857
+ * @brief Serialized binary representations of the USearch index start with metadata.
858
+ * Metadata is parsed into a `file_head_t`, containing the USearch package version,
859
+ * and the properties of the index.
860
+ */
861
+ struct file_head_t {
862
+
863
+ using magic_t = char[11];
864
+ using version_major_t = std::uint16_t;
865
+ using version_minor_t = std::uint16_t;
866
+ using version_patch_t = std::uint16_t;
867
+
868
+ enum metric_t : std::uint8_t {
869
+ unknown_k = 0,
870
+ ip_k = 'i',
871
+ cos_k = 'c',
872
+ l2sq_k = 'e',
873
+ jaccard_k = 'j',
874
+ hamming_k = 'h',
875
+ pearson_k = 'p',
876
+ haversine_k = 'h',
877
+ };
878
+
879
+ using connectivity_t = std::uint8_t;
880
+ using max_level_t = std::uint8_t;
881
+ using vector_alignment_t = std::uint8_t;
882
+ using bytes_per_label_t = std::uint16_t;
883
+ using bytes_per_id_t = std::uint16_t;
884
+ using size_t = std::uint64_t;
885
+ using entry_idx_t = std::uint64_t;
886
+
887
+ char const* magic;
888
+ misaligned_ref_gt<version_major_t> version_major;
889
+ misaligned_ref_gt<version_minor_t> version_minor;
890
+ misaligned_ref_gt<version_patch_t> version_patch;
891
+ misaligned_ref_gt<metric_t> metric;
892
+ misaligned_ref_gt<connectivity_t> connectivity;
893
+ misaligned_ref_gt<max_level_t> max_level;
894
+ misaligned_ref_gt<vector_alignment_t> vector_alignment;
895
+ misaligned_ref_gt<bytes_per_label_t> bytes_per_label;
896
+ misaligned_ref_gt<bytes_per_id_t> bytes_per_id;
897
+ misaligned_ref_gt<size_t> size;
898
+ misaligned_ref_gt<entry_idx_t> entry_idx;
899
+
900
+ file_head_t(byte_t* ptr) noexcept
901
+ : magic((char const*)exchange(ptr, ptr + sizeof(magic_t))),
902
+ version_major(exchange(ptr, ptr + sizeof(version_major_t))),
903
+ version_minor(exchange(ptr, ptr + sizeof(version_minor_t))),
904
+ version_patch(exchange(ptr, ptr + sizeof(version_patch_t))), metric(exchange(ptr, ptr + sizeof(metric_t))),
905
+ connectivity(exchange(ptr, ptr + sizeof(connectivity_t))),
906
+ max_level(exchange(ptr, ptr + sizeof(max_level_t))),
907
+ vector_alignment(exchange(ptr, ptr + sizeof(vector_alignment_t))),
908
+ bytes_per_label(exchange(ptr, ptr + sizeof(bytes_per_label_t))),
909
+ bytes_per_id(exchange(ptr, ptr + sizeof(bytes_per_id_t))), size(exchange(ptr, ptr + sizeof(size_t))),
910
+ entry_idx(exchange(ptr, ptr + sizeof(entry_idx_t))) {}
911
+ };
912
+
913
+ static_assert(sizeof(file_header_t) == 64, "File header should be exactly 64 bytes");
914
+
828
915
  /**
829
916
  * @brief Approximate Nearest Neighbors Search index using the
830
917
  * Hierarchical Navigable Small World graph algorithm.
@@ -916,14 +1003,15 @@ class index_gt {
916
1003
  vector_view_t vector;
917
1004
  };
918
1005
 
919
- template <typename ref_at> class member_iterator_gt {
1006
+ template <typename ref_at, typename index_at> class member_iterator_gt {
920
1007
  using ref_t = ref_at;
921
- index_gt* index_{};
1008
+ using index_t = index_at;
1009
+ index_t* index_{};
922
1010
  std::size_t offset_{};
923
1011
 
924
1012
  friend class index_gt;
925
1013
  member_iterator_gt() noexcept {}
926
- member_iterator_gt(index_gt* index, std::size_t offset) noexcept : index_(index), offset_(offset) {}
1014
+ member_iterator_gt(index_t* index, std::size_t offset) noexcept : index_(index), offset_(offset) {}
927
1015
 
928
1016
  public:
929
1017
  using iterator_category = std::random_access_iterator_tag;
@@ -932,7 +1020,7 @@ class index_gt {
932
1020
  using pointer = void;
933
1021
  using reference = ref_t;
934
1022
 
935
- reference operator*() const noexcept { return index_->node_with_id_(offset_).ref(); }
1023
+ reference operator*() const noexcept { return ref_t(index_->node_with_id_(offset_)); }
936
1024
  member_iterator_gt operator++(int) noexcept { return member_iterator_gt(index_, offset_ + 1); }
937
1025
  member_iterator_gt operator--(int) noexcept { return member_iterator_gt(index_, offset_ - 1); }
938
1026
  member_iterator_gt operator+(difference_type d) noexcept { return member_iterator_gt(index_, offset_ + d); }
@@ -962,8 +1050,8 @@ class index_gt {
962
1050
  }
963
1051
  };
964
1052
 
965
- using member_iterator_t = member_iterator_gt<member_ref_t>;
966
- using member_citerator_t = member_iterator_gt<member_cref_t>;
1053
+ using member_iterator_t = member_iterator_gt<member_ref_t, index_gt>;
1054
+ using member_citerator_t = member_iterator_gt<member_cref_t, index_gt const>;
967
1055
 
968
1056
  // STL compatibility:
969
1057
  using value_type = std::pair<label_t, distance_t>;
@@ -1050,8 +1138,8 @@ class index_gt {
1050
1138
 
1051
1139
  operator vector_view_t() const noexcept { return {vector(), dim()}; }
1052
1140
  vector_view_t vector_view() const noexcept { return {vector(), dim()}; }
1053
- member_ref_t ref() noexcept { return {{tape_}, vector_view()}; }
1054
- member_cref_t cref() const noexcept { return {label(), vector_view()}; }
1141
+ explicit operator member_ref_t() noexcept { return {{tape_}, vector_view()}; }
1142
+ explicit operator member_cref_t() const noexcept { return {label(), vector_view()}; }
1055
1143
  };
1056
1144
 
1057
1145
  /**
@@ -1273,7 +1361,7 @@ class index_gt {
1273
1361
  inline std::size_t size() const noexcept { return count; }
1274
1362
  inline search_result_t operator[](std::size_t i) const noexcept {
1275
1363
  candidate_t const* top_ordered = top_.data();
1276
- return {index_.node_with_id_(top_ordered[i].id).cref(), top_ordered[i].distance};
1364
+ return {member_cref_t(index_.node_with_id_(top_ordered[i].id)), top_ordered[i].distance};
1277
1365
  }
1278
1366
  inline std::size_t dump_to(label_t* labels, distance_t* distances) const noexcept {
1279
1367
  for (std::size_t i = 0; i != count; ++i) {
@@ -1416,6 +1504,7 @@ class index_gt {
1416
1504
  std::size_t nodes;
1417
1505
  std::size_t edges;
1418
1506
  std::size_t max_edges;
1507
+ std::size_t allocated_bytes;
1419
1508
  };
1420
1509
 
1421
1510
  stats_t stats() const noexcept {
@@ -1428,6 +1517,7 @@ class index_gt {
1428
1517
  for (level_t level = 0; level != node.level(); ++level)
1429
1518
  edges += neighbors_(node, level).size();
1430
1519
 
1520
+ result.allocated_bytes += node_bytes_(node);
1431
1521
  result.edges += edges;
1432
1522
  result.max_edges += max_edges;
1433
1523
  }
@@ -1437,34 +1527,55 @@ class index_gt {
1437
1527
  stats_t stats(std::size_t level) const noexcept {
1438
1528
  stats_t result{};
1439
1529
  result.nodes = size();
1530
+
1531
+ std::size_t neighbors_bytes = !level ? pre_.neighbors_base_bytes : pre_.neighbors_bytes;
1440
1532
  for (std::size_t i = 0; i != result.nodes; ++i) {
1441
1533
  node_t node = node_with_id_(i);
1442
- if (node.level() >= level)
1443
- result.edges += neighbors_(node, level).size();
1534
+ if (node.level() < level)
1535
+ continue;
1536
+
1537
+ result.edges += neighbors_(node, level).size();
1538
+ result.allocated_bytes += node_head_bytes_() + node_vector_bytes_(node) + neighbors_bytes;
1444
1539
  }
1540
+
1445
1541
  std::size_t max_edges_per_node = level ? config_.connectivity : base_level_multiple_() * config_.connectivity;
1446
1542
  result.max_edges = result.nodes * max_edges_per_node;
1447
1543
  return result;
1448
1544
  }
1449
1545
 
1450
- #pragma endregion
1546
+ /**
1547
+ * @brief A relatively accurate lower bound on the amount of memory consumed by the system.
1548
+ * In practice it's error will be below 10%.
1549
+ */
1550
+ std::size_t memory_usage(std::size_t allocator_entry_bytes = default_allocator_entry_bytes()) const noexcept {
1551
+ std::size_t total = 0;
1552
+ if (viewed_file_descriptor_ == 0) {
1553
+ stats_t s = stats();
1554
+ total += s.allocated_bytes;
1555
+ total += s.nodes * allocator_entry_bytes;
1556
+ }
1451
1557
 
1452
- #pragma region Serialization
1558
+ // Temporary data-structures, proportional to the number of nodes:
1559
+ total += nodes_.capacity() * sizeof(node_t) + allocator_entry_bytes;
1453
1560
 
1454
- private:
1455
- struct serialized_state_t {
1456
- // Check compatibility
1457
- std::uint64_t bytes_per_label{};
1458
- std::uint64_t bytes_per_id{};
1561
+ // Temporary data-structures, proportional to the number of threads:
1562
+ std::size_t per_thread = (std::max)(config_.expansion_search, config_.expansion_add) * sizeof(candidate_t);
1563
+ total += thread_contexts_.capacity() * (sizeof(thread_context_t) + per_thread) + allocator_entry_bytes * 3;
1564
+ return total;
1565
+ }
1459
1566
 
1460
- // Describe state
1461
- std::uint64_t connectivity{};
1462
- std::uint64_t size{};
1463
- std::uint64_t entry_id{};
1464
- std::uint64_t max_level{};
1465
- };
1567
+ void change_expansion_add(std::size_t n) noexcept { config_.expansion_add = n; }
1568
+ void change_expansion_search(std::size_t n) noexcept { config_.expansion_search = n; }
1569
+ void change_metric(metric_t const& m) noexcept(false) {
1570
+ metric_ = m;
1571
+ for (thread_context_t& context : thread_contexts_)
1572
+ context.metric = metric_;
1573
+ }
1574
+
1575
+ #pragma endregion
1576
+
1577
+ #pragma region Serialization
1466
1578
 
1467
- public:
1468
1579
  /**
1469
1580
  * @brief Saves serialized binary index representation to disk,
1470
1581
  * co-locating vectors and neighbors lists.
@@ -1472,15 +1583,25 @@ class index_gt {
1472
1583
  */
1473
1584
  void save(char const* file_path) const noexcept(false) {
1474
1585
 
1475
- serialized_state_t state;
1586
+ file_header_t state_buffer{};
1587
+ std::memset(state_buffer, 0, sizeof(file_header_t));
1588
+ file_head_t state{state_buffer};
1589
+ std::memcpy(state_buffer, default_magic(), std::strlen(default_magic()));
1590
+
1476
1591
  // Check compatibility
1477
- state.bytes_per_label = sizeof(label_t);
1478
- state.bytes_per_id = sizeof(id_t);
1592
+ state.version_major = USEARCH_VERSION_MAJOR;
1593
+ state.version_minor = USEARCH_VERSION_MINOR;
1594
+ state.version_patch = USEARCH_VERSION_PATCH;
1595
+ state.metric = file_head_t::unknown_k;
1596
+
1479
1597
  // Describe state
1480
1598
  state.connectivity = config_.connectivity;
1481
- state.size = size_;
1482
- state.entry_id = entry_id_;
1483
1599
  state.max_level = max_level_;
1600
+ state.vector_alignment = config_.vector_alignment;
1601
+ state.bytes_per_label = sizeof(label_t);
1602
+ state.bytes_per_id = sizeof(id_t);
1603
+ state.size = size_;
1604
+ state.entry_idx = entry_id_;
1484
1605
 
1485
1606
  std::FILE* file = std::fopen(file_path, "w");
1486
1607
  if (!file)
@@ -1495,7 +1616,7 @@ class index_gt {
1495
1616
  };
1496
1617
 
1497
1618
  // Write the header
1498
- write_chunk(&state, sizeof(state));
1619
+ write_chunk(&state_buffer[0], sizeof(file_header_t));
1499
1620
 
1500
1621
  // Serialize nodes one by one
1501
1622
  for (std::size_t i = 0; i != state.size; ++i) {
@@ -1516,7 +1637,7 @@ class index_gt {
1516
1637
  * Available on Linux, MacOS, Windows.
1517
1638
  */
1518
1639
  void load(char const* file_path) noexcept(false) {
1519
- serialized_state_t state;
1640
+ file_header_t state_buffer{};
1520
1641
  std::FILE* file = std::fopen(file_path, "r");
1521
1642
  if (!file)
1522
1643
  throw std::runtime_error(std::strerror(errno));
@@ -1531,7 +1652,8 @@ class index_gt {
1531
1652
 
1532
1653
  // Read the header
1533
1654
  {
1534
- read_chunk(&state, sizeof(state));
1655
+ read_chunk(&state_buffer[0], sizeof(file_header_t));
1656
+ file_head_t state{state_buffer};
1535
1657
  if (state.bytes_per_label != sizeof(label_t)) {
1536
1658
  std::fclose(file);
1537
1659
  throw std::runtime_error("Incompatible label type!");
@@ -1543,15 +1665,16 @@ class index_gt {
1543
1665
 
1544
1666
  config_.connectivity = state.connectivity;
1545
1667
  config_.max_elements = state.size;
1668
+ config_.vector_alignment = state.vector_alignment;
1546
1669
  pre_ = precompute_(config_);
1547
1670
  reserve(state.size);
1548
1671
  size_ = state.size;
1549
1672
  max_level_ = static_cast<level_t>(state.max_level);
1550
- entry_id_ = static_cast<id_t>(state.entry_id);
1673
+ entry_id_ = static_cast<id_t>(state.entry_idx);
1551
1674
  }
1552
1675
 
1553
1676
  // Load nodes one by one
1554
- for (std::size_t i = 0; i != state.size; ++i) {
1677
+ for (std::size_t i = 0; i != size_; ++i) {
1555
1678
  label_t label;
1556
1679
  dim_t dim;
1557
1680
  level_t level;
@@ -1578,7 +1701,6 @@ class index_gt {
1578
1701
  #if defined(USEARCH_IS_WINDOWS)
1579
1702
  throw std::logic_error("Memory-mapping is not yet available for Windows");
1580
1703
  #else
1581
- serialized_state_t state;
1582
1704
  int open_flags = O_RDONLY;
1583
1705
  #if __linux__
1584
1706
  open_flags |= O_NOATIME;
@@ -1602,7 +1724,7 @@ class index_gt {
1602
1724
 
1603
1725
  // Read the header
1604
1726
  {
1605
- std::memcpy(&state, file, sizeof(state));
1727
+ file_head_t state{file};
1606
1728
  if (state.bytes_per_label != sizeof(label_t)) {
1607
1729
  close(descriptor);
1608
1730
  throw std::runtime_error("Incompatible label type!");
@@ -1614,17 +1736,18 @@ class index_gt {
1614
1736
 
1615
1737
  config_.connectivity = state.connectivity;
1616
1738
  config_.max_elements = state.size;
1739
+ config_.vector_alignment = state.vector_alignment;
1617
1740
  config_.max_threads_add = 0;
1618
1741
  pre_ = precompute_(config_);
1619
1742
  reserve(state.size);
1620
1743
  size_ = state.size;
1621
1744
  max_level_ = static_cast<level_t>(state.max_level);
1622
- entry_id_ = static_cast<id_t>(state.entry_id);
1745
+ entry_id_ = static_cast<id_t>(state.entry_idx);
1623
1746
  }
1624
1747
 
1625
1748
  // Locate every node packed into file
1626
- std::size_t progress = sizeof(state);
1627
- for (std::size_t i = 0; i != state.size; ++i) {
1749
+ std::size_t progress = sizeof(file_header_t);
1750
+ for (std::size_t i = 0; i != size_; ++i) {
1628
1751
  byte_t* tape = (byte_t*)(file + progress);
1629
1752
  dim_t dim = misaligned_load<dim_t>(tape + sizeof(label_t));
1630
1753
  level_t level = misaligned_load<level_t>(tape + sizeof(label_t) + sizeof(dim_t));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.9.7",
3
+ "version": "0.11.0",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ashot Vardanian",
6
6
  "license": "Apache 2.0",