usearch 0.22.1 → 0.22.3

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.
@@ -299,7 +299,7 @@ jobs:
299
299
  run: |
300
300
  curl https://get.wasmer.io -sSfL | sh
301
301
  source ~/.wasmer/wasmer.sh
302
- wasmer login --token "${{ secrets.WASMER_TOKEN }}"
302
+ wasmer login "${{ secrets.WASMER_TOKEN }}"
303
303
  wasmer publish
304
304
 
305
305
 
@@ -343,6 +343,7 @@ jobs:
343
343
  name: github-pages
344
344
  url: ${{ steps.deployment.outputs.page_url }}
345
345
  runs-on: ubuntu-22.04
346
+ if: ${{ always() }}
346
347
  needs: build_docs
347
348
  steps:
348
349
  - uses: robinraju/release-downloader@v1.8
@@ -364,6 +365,7 @@ jobs:
364
365
  deploy_docs_vercel:
365
366
  name: Deploy Vercel
366
367
  runs-on: ubuntu-22.04
368
+ if: ${{ always() }}
367
369
  needs: build_docs
368
370
  steps:
369
371
  - name: Notify Vercel
package/.releaserc CHANGED
@@ -69,9 +69,10 @@
69
69
  "conanfile.py",
70
70
  "package.json",
71
71
  "Cargo.toml",
72
+ "wasmer.toml",
72
73
  ],
73
74
  "message": "Build: Released ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
74
75
  }
75
76
  ]
76
77
  ]
77
- }
78
+ }
@@ -13,7 +13,11 @@
13
13
  "environment": [
14
14
  {
15
15
  "name": "ASAN_OPTIONS",
16
- "value": "detect_leaks=0:atexit=1:strict_init_order=1:strict_string_checks=1"
16
+ "value": "detect_leaks=0:atexit=1:strict_init_order=1:strict_string_checks=1:abort_on_error=0"
17
+ },
18
+ {
19
+ "name": "LSAN_OPTIONS",
20
+ "value": "verbosity=1:log_threads=1"
17
21
  }
18
22
  ],
19
23
  "stopAtEntry": false,
@@ -100,7 +100,9 @@
100
100
  "version": "cpp",
101
101
  "future": "cpp",
102
102
  "__verbose_abort": "cpp",
103
- "shared_mutex": "cpp"
103
+ "shared_mutex": "cpp",
104
+ "ranges": "cpp",
105
+ "span": "cpp"
104
106
  },
105
107
  "cSpell.words": [
106
108
  "allclose",
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.22.1"
3
+ version = "0.22.3"
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/README.md CHANGED
@@ -39,16 +39,15 @@ Linux • MacOS • Windows • Docker • WebAssembly
39
39
  - ✅ Benchmark-topping performance.
40
40
  - ✅ Simple and extensible [single C++11 header][usearch-header] implementation.
41
41
  - ✅ SIMD-optimized and [user-defined metrics](#user-defined-functions) with JIT-compilation.
42
- - ✅ Variable dimensionality vectors for unique applications, including GIS and Chess.
43
- - ✅ Advanced bitwise Tanimoto and Sorensen coefficients ideal for Genomics and Chemistry.
44
- - ✅ On-the-fly support for [half-precision `f16` and quarter-precision `f8`](#memory-efficiency-downcasting-and-quantization), compatible with any hardware.
42
+ - ✅ Variable dimensionality vectors for unique applications, including search over compressed data.
43
+ - ✅ Bitwise Tanimoto and Sorensen coefficients for [Genomics and Chemistry applications](#usearch--rdkit--molecular-search).
44
+ - ✅ Hardware-agmostic `f16` & `f8` - [half-precision & quarter-precision support](#memory-efficiency-downcasting-and-quantization).
45
45
  - ✅ [View large indexes from disk](#disk-based-indexes) without loading into RAM.
46
46
  - ✅ Space-efficient point-clouds with `uint40_t`, accommodating 4B+ size.
47
- - ✅ Optimized for concurrent threading, like OpenMP or C++23 executors with bring your threads.
48
- - ✅ Support for multiple vectors per label.
47
+ - ✅ Compatible with OpenMP and custom "executors", for fine-grained control over CPU utilization.
48
+ - ✅ Supports multiple vectors per label.
49
49
  - ✅ On-the-fly deletions.
50
- - ✅ Utilize [USearch and UForm Transformers for Semantic Search](#usearch--ai--multi-modal-semantic-search).
51
- - ✅ Combine [USearch and RDKit for Molecule Search](#usearch--rdkit--molecular-search).
50
+ - ✅ [Semantic Search](#usearch--ai--multi-modal-semantic-search) and [Joins](#joins).
52
51
 
53
52
  [usearch-header]: https://github.com/unum-cloud/usearch/blob/main/include/usearch/index.hpp
54
53
  [obscure-use-cases]: https://ashvardanian.com/posts/abusing-vector-search
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.22.1'
7
+ version = '0.22.3'
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
@@ -329,7 +329,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
329
329
  using label_t = typename index_at::label_t;
330
330
  using distance_t = typename index_at::distance_t;
331
331
  using join_result_t = typename index_at::join_result_t;
332
- constexpr std::size_t missing_label = std::numeric_limits<label_t>::max();
332
+ constexpr label_t missing_label = std::numeric_limits<label_t>::max();
333
333
 
334
334
  std::printf("\n");
335
335
  std::printf("------------\n");
@@ -383,7 +383,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
383
383
  // Evaluate join quality
384
384
  std::size_t recall_join = 0, unmatched_count = 0;
385
385
  for (std::size_t i = 0; i != index.size(); ++i) {
386
- recall_join += man_to_woman[i] == i;
386
+ recall_join += man_to_woman[i] == static_cast<label_t>(i);
387
387
  unmatched_count += man_to_woman[i] == missing_label;
388
388
  }
389
389
  std::printf("Recall Joins %.2f %%\n", recall_join * 100.f / index.size());
package/cpp/test.cpp CHANGED
@@ -67,7 +67,8 @@ template <typename scalar_at, typename index_at> void test3d(index_at&& index) {
67
67
  expect(std::abs(matched_distances[0]) < 0.01);
68
68
 
69
69
  // Search again over mapped index
70
- index_metadata("tmp.usearch");
70
+ file_head_result_t head = index_metadata("tmp.usearch");
71
+ expect(head.size == 3);
71
72
  index.view("tmp.usearch");
72
73
  matched_count = index.search(view_t{&vec[0], 3ul}, 5).dump_to(matched_labels, matched_distances);
73
74
  expect(matched_count == 3);
@@ -144,6 +145,8 @@ int main(int, char**) {
144
145
  static_assert(!std::is_same<index_gt<ip_gt<>>::value_type, std::true_type>());
145
146
  static_assert(!std::is_same<index_gt<cos_gt<>>::value_type, std::true_type>());
146
147
  static_assert(!std::is_same<index_gt<l2sq_gt<>>::value_type, std::true_type>());
148
+
149
+ static_assert(has_reset<memory_mapping_allocator_t>());
147
150
  // static_assert(!std::is_same<index_gt<hamming_gt<>>::value_type, std::true_type>());
148
151
  // static_assert(!std::is_same<index_gt<tanimoto_gt<>>::value_type, std::true_type>());
149
152
  // static_assert(!std::is_same<index_gt<sorensen_gt<>>::value_type, std::true_type>());
@@ -1,12 +1,13 @@
1
1
  # Compiling USearch
2
2
 
3
+ This document describes the workflow for advanced USearch users and enthusiasts willing to contribute to the project.
3
4
  Before building the first time, please pull submodules.
4
5
 
5
6
  ```sh
6
7
  git submodule update --init --recursive
7
8
  ```
8
9
 
9
- ## C++
10
+ ## C++11
10
11
 
11
12
  Linux:
12
13
 
@@ -49,6 +50,12 @@ cppcheck --enable=all --suppress=cstyleCast --suppress=unusedFunction \
49
50
  include/index_punned_helpers.hpp
50
51
  ```
51
52
 
53
+ Testing:
54
+
55
+ ```sh
56
+ cmake -DCMAKE_CXX_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_BUILD_TYPE=Debug -B ./build_debug && make -C ./build_debug && ./build_debug/test
57
+ ```
58
+
52
59
  Benchmarking:
53
60
 
54
61
  ```sh
@@ -65,7 +72,7 @@ Benchmarking:
65
72
  --cos
66
73
  ```
67
74
 
68
- ## Python
75
+ ## Python 3
69
76
 
70
77
  Use PyTest to validate the build.
71
78
  The `-s` option will disable capturing the logs.
@@ -110,11 +117,26 @@ python python/scripts/bench.py connectivity ...
110
117
 
111
118
  ## JavaScript
112
119
 
120
+ Node.JS:
121
+
113
122
  ```sh
114
123
  npm install && node javascript/test.js
115
124
  npm publish
116
125
  ```
117
126
 
127
+ WebAssembly:
128
+
129
+ ```sh
130
+ emcmake cmake -B ./build -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -s TOTAL_MEMORY=64MB" && emmake make -C ./build
131
+ node ./build/test.js
132
+ ```
133
+
134
+ If you don't yet have `emcmake` installed:
135
+
136
+ ```sh
137
+ git clone https://github.com/emscripten-core/emsdk.git && ./emsdk/emsdk install latest && ./emsdk/emsdk activate latest && source ./emsdk/emsdk_env.sh
138
+ ```
139
+
118
140
  ## Rust
119
141
 
120
142
  ```sh
@@ -176,9 +198,9 @@ swift build
176
198
  swift test -v
177
199
  ```
178
200
 
179
- ## C
201
+ ## C99
180
202
 
181
- ### Linux
203
+ Linux:
182
204
 
183
205
  ```sh
184
206
  g++ -shared -fPIC lib.cpp -I ../include/ -I ../fp16/include/ -o libusearch.so
@@ -205,10 +227,10 @@ docker buildx create --use &&
205
227
  --push .
206
228
  ```
207
229
 
208
- ---
230
+ ## Sub-Modules
209
231
 
210
232
  Extending metrics in SimSIMD:
211
233
 
212
- ```
234
+ ```sh
213
235
  git push --set-upstream https://github.com/ashvardanian/simsimd.git HEAD:main
214
- ```
236
+ ```
@@ -1,6 +1,6 @@
1
- =======
2
- C++ SDK
3
- =======
1
+ =====================
2
+ USearch for C++
3
+ =====================
4
4
 
5
5
 
6
6
  .. mdinclude:: ../../cpp/README.md
@@ -1,7 +1,17 @@
1
1
  API Reference
2
2
  ===============
3
3
 
4
- ===============
5
- usearch
6
- ===============
4
+ =================================
5
+ usearch/index.hpp
6
+ =================================
7
7
  .. doxygenfile:: index.hpp
8
+
9
+ =================================
10
+ usearch/index_punned_helpers.hpp
11
+ =================================
12
+ .. doxygenfile:: index_punned_helpers.hpp
13
+
14
+ =================================
15
+ usearch/index_punned_dense.hpp
16
+ =================================
17
+ .. doxygenfile:: index_punned_dense.hpp
package/docs/index.rst CHANGED
@@ -3,27 +3,27 @@ Overview
3
3
  ==========
4
4
  .. mdinclude:: ../README.md
5
5
 
6
- .. toctree::
7
- :hidden:
8
- :caption: �
9
-
10
- benchmarks
11
- compilation
12
-
13
6
  .. toctree::
14
7
  :hidden:
15
8
  :caption: �
16
9
 
17
10
  cpp/index
18
11
  python/index
12
+ rust/index
19
13
  javascript/index
20
14
  java/index
21
- rust/index
22
15
  c/index
23
16
  swift/index
24
17
  golang/index
25
18
  wolfram/index
26
19
 
20
+ .. toctree::
21
+ :hidden:
22
+ :caption: �
23
+
24
+ compilation
25
+ benchmarks
26
+
27
27
  .. toctree::
28
28
  :hidden:
29
29
  :caption: �
@@ -2,6 +2,6 @@ API Reference
2
2
  ===============
3
3
 
4
4
  ===============
5
- usearch
5
+ Index.java
6
6
  ===============
7
7
  .. doxygenfile:: Index.java
@@ -1451,9 +1451,32 @@ struct dummy_label_to_label_mapping_t {
1451
1451
  template <typename label_at> member_ref_t operator[](label_at&&) const noexcept { return {}; }
1452
1452
  };
1453
1453
 
1454
+ template <typename, typename at> struct has_reset_gt {
1455
+ static_assert(std::integral_constant<at, false>::value, "Second template parameter needs to be of function type.");
1456
+ };
1457
+
1458
+ template <typename check_at, typename return_at, typename... args_at>
1459
+ struct has_reset_gt<check_at, return_at(args_at...)> {
1460
+ private:
1461
+ template <typename at>
1462
+ static constexpr auto check(at*) ->
1463
+ typename std::is_same<decltype(std::declval<at>().reset(std::declval<args_at>()...)), return_at>::type;
1464
+ template <typename> static constexpr std::false_type check(...);
1465
+
1466
+ typedef decltype(check<check_at>(0)) type;
1467
+
1468
+ public:
1469
+ static constexpr bool value = type::value;
1470
+ };
1471
+
1472
+ /**
1473
+ * @brief Checks if a certain class has a member function called `reset`.
1474
+ */
1475
+ template <typename at> constexpr bool has_reset() { return has_reset_gt<at, void()>::value; }
1476
+
1454
1477
  /**
1455
1478
  * @brief Approximate Nearest Neighbors Search index using the
1456
- * Hierarchical Navigable Small World graph algorithm.
1479
+ * Hierarchical Navigable Small World @b (HNSW) graphs algorithm.
1457
1480
  *
1458
1481
  * @section Features
1459
1482
  * - Thread-safe for concurrent construction.
@@ -1473,15 +1496,22 @@ struct dummy_label_to_label_mapping_t {
1473
1496
  * The smallest unsigned integer type to address indexed elements.
1474
1497
  * Can be a built-in `uint32_t`, `uint64_t`, or our custom `uint40_t`.
1475
1498
  *
1476
- * @tparam allocator_at
1477
- * Dynamic memory allocator.
1499
+ * @tparam dynamic_allocator_at
1500
+ * Dynamic memory allocator for temporary buffers, visits indicators, and
1501
+ * priority queues, needed during construction and traversals of graphs.
1502
+ * The allocated buffers may be uninitialized.
1503
+ *
1504
+ * @tparam tape_allocator_at
1505
+ * Potentially different memory allocator for primary allocations of nodes and vectors.
1506
+ * It would never `deallocate` separate entries, and would only free all the space at once.
1507
+ * The allocated buffers may be uninitialized.
1478
1508
  *
1479
1509
  * @section Usage
1480
1510
  *
1481
1511
  * @subsection Exceptions
1482
1512
  *
1483
1513
  * None of the methods throw exceptions in the "Release" compilation mode.
1484
- * It may only `throw` if your memory ::allocator_at or ::metric_at isn't
1514
+ * It may only `throw` if your memory ::dynamic_allocator_at or ::metric_at isn't
1485
1515
  * safe to copy.
1486
1516
  *
1487
1517
  * @section Implementation Details
@@ -1513,18 +1543,18 @@ struct dummy_label_to_label_mapping_t {
1513
1543
  * vectors. That, however, is solved by `index_punned_dense_gt`, which also adds automatic casting.
1514
1544
  *
1515
1545
  */
1516
- template <typename metric_at = ip_gt<float>, //
1517
- typename label_at = std::int64_t, //
1518
- typename id_at = std::uint32_t, //
1519
- typename allocator_at = std::allocator<char>, //
1520
- typename point_allocator_at = allocator_at> //
1546
+ template <typename metric_at = ip_gt<float>, //
1547
+ typename label_at = std::int64_t, //
1548
+ typename id_at = std::uint32_t, //
1549
+ typename dynamic_allocator_at = std::allocator<char>, //
1550
+ typename tape_allocator_at = dynamic_allocator_at> //
1521
1551
  class index_gt {
1522
1552
  public:
1523
1553
  using metric_t = metric_at;
1524
1554
  using label_t = label_at;
1525
1555
  using id_t = id_at;
1526
- using allocator_t = allocator_at;
1527
- using point_allocator_t = point_allocator_at;
1556
+ using dynamic_allocator_t = dynamic_allocator_at;
1557
+ using tape_allocator_t = tape_allocator_at;
1528
1558
  static_assert(sizeof(label_t) >= sizeof(id_t), "Having tiny labels doesn't make sense.");
1529
1559
 
1530
1560
  using scalar_t = typename metric_t::scalar_t;
@@ -1601,7 +1631,7 @@ class index_gt {
1601
1631
 
1602
1632
  // STL compatibility:
1603
1633
  using value_type = std::pair<label_t, distance_t>;
1604
- using allocator_type = allocator_t;
1634
+ using allocator_type = dynamic_allocator_t;
1605
1635
  using size_type = std::size_t;
1606
1636
  using difference_type = std::ptrdiff_t;
1607
1637
  using reference = member_ref_t;
@@ -1618,13 +1648,14 @@ class index_gt {
1618
1648
  using dim_t = std::uint32_t;
1619
1649
  using level_t = std::int32_t;
1620
1650
 
1621
- using allocator_traits_t = std::allocator_traits<allocator_t>;
1622
- using byte_t = typename allocator_t::value_type;
1623
- static_assert(sizeof(byte_t) == 1, "Allocator must allocate separate addressable bytes");
1651
+ using allocator_traits_t = std::allocator_traits<dynamic_allocator_t>;
1652
+ using byte_t = typename dynamic_allocator_t::value_type;
1653
+ static_assert(sizeof(byte_t) == 1, //
1654
+ "Primary allocator must allocate separate addressable bytes");
1624
1655
 
1625
- using point_allocator_traits_t = std::allocator_traits<point_allocator_t>;
1626
- static_assert(sizeof(typename point_allocator_traits_t::value_type) == 1,
1627
- "Allocator must allocate separate addressable bytes");
1656
+ using tape_allocator_traits_t = std::allocator_traits<tape_allocator_t>;
1657
+ static_assert(sizeof(typename tape_allocator_traits_t::value_type) == 1, //
1658
+ "Tape allocator must allocate separate addressable bytes");
1628
1659
 
1629
1660
  /**
1630
1661
  * @brief How much larger (number of neighbors per node) will
@@ -1637,7 +1668,7 @@ class index_gt {
1637
1668
  */
1638
1669
  static constexpr std::size_t node_head_bytes_() { return sizeof(label_t) + sizeof(dim_t) + sizeof(level_t); }
1639
1670
 
1640
- using visits_bitset_t = visits_bitset_gt<allocator_t>;
1671
+ using visits_bitset_t = visits_bitset_gt<dynamic_allocator_t>;
1641
1672
 
1642
1673
  struct precomputed_constants_t {
1643
1674
  double inverse_log_connectivity{};
@@ -1688,8 +1719,6 @@ class index_gt {
1688
1719
 
1689
1720
  operator vector_view_t() const noexcept { return {vector(), dim()}; }
1690
1721
  vector_view_t vector_view() const noexcept { return {vector(), dim()}; }
1691
- explicit operator member_ref_t() noexcept { return {{tape_}, vector_view()}; }
1692
- explicit operator member_cref_t() const noexcept { return {label(), vector_view()}; }
1693
1722
  explicit operator bool() const noexcept { return tape_; }
1694
1723
  };
1695
1724
 
@@ -1743,8 +1772,8 @@ class index_gt {
1743
1772
  index_config_t config_{};
1744
1773
  index_limits_t limits_{};
1745
1774
  metric_t metric_{};
1746
- mutable allocator_t allocator_{};
1747
- point_allocator_t point_allocator_{};
1775
+ mutable dynamic_allocator_t dynamic_allocator_{};
1776
+ tape_allocator_t tape_allocator_{};
1748
1777
  precomputed_constants_t pre_{};
1749
1778
  viewed_file_t viewed_file_{};
1750
1779
 
@@ -1757,11 +1786,11 @@ class index_gt {
1757
1786
  level_t max_level_{};
1758
1787
  id_t entry_id_{};
1759
1788
 
1760
- using node_allocator_t = typename allocator_traits_t::template rebind_alloc<node_t>;
1789
+ using nodes_allocator_t = typename allocator_traits_t::template rebind_alloc<node_t>;
1761
1790
  node_t* nodes_{};
1762
1791
  mutable visits_bitset_t nodes_mutexes_{};
1763
1792
 
1764
- using context_allocator_t = typename allocator_traits_t::template rebind_alloc<context_t>;
1793
+ using contexts_allocator_t = typename allocator_traits_t::template rebind_alloc<context_t>;
1765
1794
  context_t* contexts_{};
1766
1795
 
1767
1796
  public:
@@ -1777,16 +1806,16 @@ class index_gt {
1777
1806
  * @section Exceptions
1778
1807
  * Doesn't throw, unless the ::metric's and ::allocators's throw on copy-construction.
1779
1808
  */
1780
- explicit index_gt(index_config_t config = {}, metric_t metric = {}, allocator_t allocator = {},
1781
- point_allocator_t point_allocator = {}) noexcept
1782
- : config_(config), limits_(0, 0), metric_(metric), allocator_(std::move(allocator)),
1783
- point_allocator_(std::move(point_allocator)), pre_(precompute_(config)), size_(0u), max_level_(-1),
1809
+ explicit index_gt(index_config_t config = {}, metric_t metric = {}, dynamic_allocator_t allocator = {},
1810
+ tape_allocator_t tape_allocator = {}) noexcept
1811
+ : config_(config), limits_(0, 0), metric_(metric), dynamic_allocator_(std::move(allocator)),
1812
+ tape_allocator_(std::move(tape_allocator)), pre_(precompute_(config)), size_(0u), max_level_(-1),
1784
1813
  entry_id_(0u), nodes_(nullptr), nodes_mutexes_(), contexts_(nullptr) {}
1785
1814
 
1786
1815
  /**
1787
1816
  * @brief Clones the structure with the same hyper-parameters, but without contents.
1788
1817
  */
1789
- index_gt fork() noexcept { return index_gt{config_, metric_, allocator_}; }
1818
+ index_gt fork() noexcept { return index_gt{config_, metric_, dynamic_allocator_}; }
1790
1819
 
1791
1820
  ~index_gt() noexcept { reset(); }
1792
1821
 
@@ -1811,7 +1840,7 @@ class index_gt {
1811
1840
  copy_result_t copy(copy_config_t /*config*/ = {}) const noexcept {
1812
1841
  copy_result_t result;
1813
1842
  index_gt& other = result.index;
1814
- other = index_gt(config_, metric_, allocator_, point_allocator_);
1843
+ other = index_gt(config_, metric_, dynamic_allocator_, tape_allocator_);
1815
1844
  if (!other.reserve(limits_))
1816
1845
  return result.failed("Failed to reserve the contexts");
1817
1846
 
@@ -1833,8 +1862,16 @@ class index_gt {
1833
1862
  member_iterator_t begin() noexcept { return {this, 0}; }
1834
1863
  member_iterator_t end() noexcept { return {this, size()}; }
1835
1864
 
1836
- member_cref_t at(std::size_t i) const noexcept { return member_cref_t(node_with_id_(i)); }
1837
- member_ref_t at(std::size_t i) noexcept { return member_ref_t(node_with_id_(i)); }
1865
+ member_ref_t at(std::size_t i) noexcept {
1866
+ return {nodes_[i].label(), nodes_[i].vector_view(), static_cast<id_t>(i)};
1867
+ }
1868
+
1869
+ member_cref_t at(std::size_t i) const noexcept {
1870
+ return {nodes_[i].label(), nodes_[i].vector_view(), static_cast<id_t>(i)};
1871
+ }
1872
+
1873
+ dynamic_allocator_t const& dynamic_allocator() const noexcept { return dynamic_allocator_; }
1874
+ tape_allocator_t const& tape_allocator() const noexcept { return tape_allocator_; }
1838
1875
 
1839
1876
  #pragma region Adjusting Configuration
1840
1877
 
@@ -1844,9 +1881,12 @@ class index_gt {
1844
1881
  * Will keep the number of available threads/contexts the same as it was.
1845
1882
  */
1846
1883
  void clear() noexcept {
1847
- std::size_t n = size_;
1848
- for (std::size_t i = 0; i != n; ++i)
1849
- node_free_(i);
1884
+ if (!has_reset<tape_allocator_t>()) {
1885
+ std::size_t n = size_;
1886
+ for (std::size_t i = 0; i != n; ++i)
1887
+ node_free_(i);
1888
+ } else
1889
+ tape_allocator_.deallocate(nullptr, 0);
1850
1890
  size_ = 0;
1851
1891
  max_level_ = -1;
1852
1892
  entry_id_ = 0u;
@@ -1861,11 +1901,11 @@ class index_gt {
1861
1901
  clear();
1862
1902
 
1863
1903
  if (nodes_)
1864
- node_allocator_t{}.deallocate(exchange(nodes_, nullptr), limits_.members);
1904
+ nodes_allocator_t{}.deallocate(exchange(nodes_, nullptr), limits_.members);
1865
1905
  if (contexts_) {
1866
1906
  for (std::size_t i = 0; i != limits_.threads(); ++i)
1867
1907
  contexts_[i].~context_t();
1868
- context_allocator_t{}.deallocate(exchange(contexts_, nullptr), limits_.threads());
1908
+ contexts_allocator_t{}.deallocate(exchange(contexts_, nullptr), limits_.threads());
1869
1909
  }
1870
1910
  limits_ = index_limits_t{0, 0};
1871
1911
  capacity_ = 0;
@@ -1879,8 +1919,8 @@ class index_gt {
1879
1919
  std::swap(config_, other.config_);
1880
1920
  std::swap(limits_, other.limits_);
1881
1921
  std::swap(metric_, other.metric_);
1882
- std::swap(allocator_, other.allocator_);
1883
- std::swap(point_allocator_, other.point_allocator_);
1922
+ std::swap(dynamic_allocator_, other.dynamic_allocator_);
1923
+ std::swap(tape_allocator_, other.tape_allocator_);
1884
1924
  std::swap(pre_, other.pre_);
1885
1925
  std::swap(viewed_file_, other.viewed_file_);
1886
1926
  std::swap(max_level_, other.max_level_);
@@ -1912,13 +1952,13 @@ class index_gt {
1912
1952
  if (!nodes_mutexes_.resize(limits.members))
1913
1953
  return false;
1914
1954
 
1915
- node_allocator_t node_allocator;
1955
+ nodes_allocator_t node_allocator;
1916
1956
  node_t* new_nodes = node_allocator.allocate(limits.members);
1917
1957
  if (!new_nodes)
1918
1958
  return false;
1919
1959
 
1920
1960
  std::size_t limits_threads = limits.threads();
1921
- context_allocator_t context_allocator;
1961
+ contexts_allocator_t context_allocator;
1922
1962
  context_t* new_contexts = context_allocator.allocate(limits_threads);
1923
1963
  if (!new_contexts) {
1924
1964
  node_allocator.deallocate(new_nodes, limits.members);
@@ -2408,6 +2448,10 @@ class index_gt {
2408
2448
  template <typename progress_at = dummy_progress_t>
2409
2449
  serialization_result_t load(char const* file_path, progress_at&& progress = {}) noexcept {
2410
2450
 
2451
+ // Remove previously stored objects
2452
+ reset();
2453
+
2454
+ // Start parsing the files
2411
2455
  serialization_result_t result;
2412
2456
  file_header_t state_buffer{};
2413
2457
  std::FILE* file = std::fopen(file_path, "rb");
@@ -2493,6 +2537,11 @@ class index_gt {
2493
2537
  */
2494
2538
  template <typename progress_at = dummy_progress_t>
2495
2539
  serialization_result_t view(char const* file_path, progress_at&& progress = {}) noexcept {
2540
+
2541
+ // Remove previously stored objects
2542
+ reset();
2543
+
2544
+ // Start parsing the files
2496
2545
  serialization_result_t result;
2497
2546
 
2498
2547
  #if defined(USEARCH_DEFINED_WINDOWS)
@@ -2671,7 +2720,7 @@ class index_gt {
2671
2720
  progress_at&& progress = progress_at{}) noexcept {
2672
2721
 
2673
2722
  // Erase all the incoming links
2674
- executor.execute_bulk(size(), [&](std::size_t node_idx, std::size_t) {
2723
+ executor.execute_bulk(size(), [&](std::size_t, std::size_t node_idx) {
2675
2724
  node_t node = node_with_id_(node_idx);
2676
2725
  for (level_t level = 0; level <= node.level(); ++level) {
2677
2726
  neighbors_ref_t neighbors = neighbors_(node, level);
@@ -2708,7 +2757,7 @@ class index_gt {
2708
2757
 
2709
2758
  using proposals_count_t = std::uint16_t;
2710
2759
  using ids_allocator_t = typename allocator_traits_t::template rebind_alloc<id_t>;
2711
- allocator_t& alloc = men.allocator_;
2760
+ dynamic_allocator_t& alloc = men.dynamic_allocator_;
2712
2761
 
2713
2762
  // Create an atomic queue, as a ring structure, from/to which
2714
2763
  // free men will be added/pulled.
@@ -2911,7 +2960,7 @@ class index_gt {
2911
2960
  std::size_t node_bytes = node_bytes_(dims_to_store, level);
2912
2961
  std::size_t non_vector_bytes = node_bytes - vector_bytes;
2913
2962
 
2914
- byte_t* data = (byte_t*)point_allocator_.allocate(node_bytes);
2963
+ byte_t* data = (byte_t*)tape_allocator_.allocate(node_bytes);
2915
2964
  if (!data)
2916
2965
  return node_bytes_split_t{};
2917
2966
  return {{data, non_vector_bytes}, {data + non_vector_bytes, vector_bytes}};
@@ -2934,7 +2983,7 @@ class index_gt {
2934
2983
 
2935
2984
  node_t node_make_copy_(node_bytes_split_t old_bytes) noexcept {
2936
2985
  if (old_bytes.colocated()) {
2937
- byte_t* data = (byte_t*)point_allocator_.allocate(old_bytes.memory_usage());
2986
+ byte_t* data = (byte_t*)tape_allocator_.allocate(old_bytes.memory_usage());
2938
2987
  std::memcpy(data, old_bytes.tape.data(), old_bytes.memory_usage());
2939
2988
  return node_t{data, reinterpret_cast<scalar_t*>(data + old_bytes.tape.size())};
2940
2989
  } else {
@@ -2953,7 +3002,7 @@ class index_gt {
2953
3002
 
2954
3003
  node_t& node = nodes_[id];
2955
3004
  std::size_t node_bytes = node_bytes_(node) - node_vector_bytes_(node) * !node_bytes_split_(node).colocated();
2956
- point_allocator_.deallocate(node.tape(), node_bytes);
3005
+ tape_allocator_.deallocate(node.tape(), node_bytes);
2957
3006
  node = node_t{};
2958
3007
  }
2959
3008