usearch 0.1.0 → 0.1.5

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.
@@ -55,8 +55,8 @@ jobs:
55
55
  path: ./wheelhouse/*.whl
56
56
 
57
57
 
58
- publish_wheels:
59
- name: Publish Wheels to PyPi
58
+ publish_python:
59
+ name: Publish Python
60
60
  runs-on: ubuntu-20.04
61
61
  needs: build_wheels
62
62
  environment:
@@ -78,6 +78,35 @@ jobs:
78
78
  verbose: true
79
79
  print_hash: true
80
80
 
81
+ publish_javascript:
82
+ name: Publish JavaScript
83
+ runs-on: ubuntu-latest
84
+ steps:
85
+ - uses: actions/checkout@v3
86
+ - run: git submodule update --init --recursive
87
+ - uses: actions/setup-node@v3
88
+ with:
89
+ node-version: 18
90
+ - run: npm install
91
+ - run: npm ci
92
+ - run: npm test
93
+ - uses: JS-DevTools/npm-publish@v2
94
+ with:
95
+ token: ${{ secrets.NPM_TOKEN }}
96
+
97
+ publish_rust:
98
+ name: Publish Rust
99
+ runs-on: ubuntu-latest
100
+ steps:
101
+ - uses: actions/checkout@v3
102
+ - run: git submodule update --init --recursive
103
+ - uses: actions-rs/toolchain@v1
104
+ with:
105
+ toolchain: stable
106
+ override: true
107
+ - uses: katyo/publish-crates@v2
108
+ with:
109
+ registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
81
110
 
82
111
  deploy_docs:
83
112
  name: Deploy Docs
@@ -85,7 +114,7 @@ jobs:
85
114
  name: github-pages
86
115
  url: ${{ steps.deployment.outputs.page_url }}
87
116
  runs-on: ubuntu-22.04
88
- needs: publish_wheels
117
+ needs: publish_python
89
118
  steps:
90
119
  - name: Checkout
91
120
  uses: actions/checkout@v3
@@ -47,10 +47,10 @@ cmake \
47
47
 
48
48
  ```sh
49
49
  pip install -e .
50
- python src/test.py
50
+ python python/test.py
51
51
 
52
52
  # To benchmark:
53
- python src/bench.py \
53
+ python python/bench.py \
54
54
  --vectors datasets/wiki_1M/base.1M.fbin \
55
55
  --queries datasets/wiki_1M/query.public.100K.fbin \
56
56
  --neighbors datasets/wiki_1M/groundtruth.public.100K.ibin \
@@ -61,16 +61,27 @@ python src/bench.py \
61
61
 
62
62
  ```sh
63
63
  npm install
64
- node src/test.js
64
+ node javascript/test.js
65
65
  npm publish
66
66
  ```
67
67
 
68
- ### JavaScript
68
+ ### Rust
69
69
 
70
70
  ```sh
71
- cargo run
71
+ cargo test -p usearch
72
+ cargo publish
72
73
  ```
73
74
 
75
+ ### Java
76
+
77
+ ```sh
78
+ cs java
79
+ javac -h . Index.java
80
+
81
+ ```
82
+
83
+ ### Wolfram
84
+
74
85
  ## Benchmarks
75
86
 
76
87
  ### Datasets
@@ -1,13 +1,17 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.1.0"
3
+ version = "0.1.5"
4
4
  authors = ["Ashot Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
- description = "The Tiny Vector Search Engine from Unum"
5
+ description = "The Mighty Tiny Vector Search Engine with Automatic Quantization and Hardware Acceleration"
6
6
  edition = "2021"
7
7
  license = "Apache-2.0"
8
- publish = false
8
+ publish = true
9
9
  repository = "https://github.com/unum-cloud/usearch"
10
10
 
11
+ [lib]
12
+ name = "usearch"
13
+ path = "rust/lib.rs"
14
+
11
15
  [dependencies]
12
16
  cxx = "1.0"
13
17
 
package/README.md CHANGED
@@ -17,7 +17,7 @@ Compact, yet Powerful<br/>
17
17
  <a href="https://github.com/unum-cloud/usearch"><img height="25" src="https://github.com/unum-cloud/.github/raw/main/assets/github.svg" alt="GitHub"></a>
18
18
  </p>
19
19
 
20
- - [x] Single C++11 header, easily extendible.
20
+ - [x] Single C++11 header implementation, easily extendible.
21
21
  - [x] 4B+ sized space efficient point-clouds with `uint40_t`.
22
22
  - [x] Half-precision support with [`maratyszcza/fp16`](https://github.com/maratyszcza/fp16).
23
23
  - [x] View from disk, without loading into RAM.
@@ -29,14 +29,16 @@ Compact, yet Powerful<br/>
29
29
  - [x] Don't copy vectors if not needed.
30
30
  - [x] Bring your threads.
31
31
  - [x] Multiple vectors per label.
32
- - [x] CPython bindings for Python 3.
33
- - [ ] Node.js bindings for JavaScript.
32
+ - [x] Python bindings: `pip install usearch`
33
+ - [x] JavaScript bindings: `npm install usearch`
34
+ - [x] Rust bindings: `cargo add usearch`
34
35
  - [ ] Wolfram language bindings.
35
36
  - [x] For Linux: GCC, Clang.
36
37
  - [x] For MacOS: Apple Clang.
37
38
  - [ ] For Windows.
39
+ - [ ] Multi-index lookups in Python.
40
+ - [ ] Thread-safe `reserve`.
38
41
  - [ ] Distributed construction.
39
- - [ ] Multi-index lookups.
40
42
 
41
43
  ## Usage
42
44
 
@@ -67,3 +69,9 @@ FetchContent_MakeAvailable(usearch)
67
69
  ### Bring your Threads
68
70
 
69
71
  ## Performance
72
+
73
+
74
+ ## TODO
75
+
76
+ - JavaScript: Allow calling from "worker threads".
77
+ - Rust: Allow passing a custom thread ID.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.5
package/binding.gyp CHANGED
@@ -3,11 +3,12 @@
3
3
  {
4
4
  "target_name": "usearch",
5
5
  "sources": [
6
- "src/javascript.cpp"
6
+ "javascript/lib.cpp"
7
7
  ],
8
8
  "include_dirs": [
9
9
  "<!@(node -p \"require('node-addon-api').include\")",
10
10
  "include",
11
+ "src",
11
12
  "fp16/include",
12
13
  "simsimd/include"
13
14
  ],
package/build.rs CHANGED
@@ -1,14 +1,17 @@
1
1
  fn main() {
2
- cxx_build::bridge("src/main.rs")
3
- .file("src/rust.cpp")
2
+ cxx_build::bridge("rust/lib.rs")
3
+ .file("rust/lib.cpp")
4
4
  .flag_if_supported("-std=c++11")
5
5
  .include("include")
6
+ .include("rust")
6
7
  .include("src")
7
8
  .include("fp16/include")
8
9
  .include("simsimd/include")
9
10
  .compile("usearch");
10
11
 
11
- println!("cargo:rerun-if-changed=src/main.rs");
12
- println!("cargo:rerun-if-changed=src/rust.cpp");
13
- println!("cargo:rerun-if-changed=src/rust.h");
12
+ println!("cargo:rerun-if-changed=rust/lib.rs");
13
+ println!("cargo:rerun-if-changed=rust/lib.cpp");
14
+ println!("cargo:rerun-if-changed=rust/lib.hpp");
15
+ println!("cargo:rerun-if-changed=src/advanced.hpp");
16
+ println!("cargo:rerun-if-changed=include/usearch/usearch.hpp");
14
17
  }
@@ -59,9 +59,9 @@ using f32_t = float;
59
59
  */
60
60
  enum class f16_t : std::uint16_t {};
61
61
 
62
- template <typename at> constexpr at angle_to_radians(at angle) noexcept { return angle * M_PI / 180; }
62
+ template <typename at> at angle_to_radians(at angle) noexcept { return angle * M_PI / 180.f; }
63
63
 
64
- template <typename at> constexpr at square(at value) noexcept { return value * value; }
64
+ template <typename at> at square(at value) noexcept { return value * value; }
65
65
 
66
66
  inline std::size_t ceil2(std::size_t v) noexcept {
67
67
  v--;
@@ -223,22 +223,22 @@ template <typename scalar_at> struct pearson_correlation_gt {
223
223
  template <typename scalar_at> struct haversine_gt {
224
224
  using scalar_t = scalar_at;
225
225
  using result_type = scalar_t;
226
- static_assert(std::is_floating_point<scalar_t>::value, "Latitude and longitude must be floating-node");
226
+ static_assert(!std::is_integral<scalar_t>::value, "Latitude and longitude must be floating-node");
227
227
 
228
228
  inline scalar_t operator()(scalar_t const* a, scalar_t const* b, std::size_t = 2, std::size_t = 2) const noexcept {
229
229
  scalar_t lat_a = a[0], lon_a = a[1];
230
230
  scalar_t lat_b = b[0], lon_b = b[1];
231
231
 
232
- scalar_t lat_delta = angle_to_radians(lat_b - lat_a);
233
- scalar_t lon_delta = angle_to_radians(lon_b - lon_a);
232
+ scalar_t lat_delta = angle_to_radians<scalar_t>(lat_b - lat_a);
233
+ scalar_t lon_delta = angle_to_radians<scalar_t>(lon_b - lon_a);
234
234
 
235
- scalar_t converted_lat_a = angle_to_radians(lat_a);
236
- scalar_t converted_lat_b = angle_to_radians(lat_b);
235
+ scalar_t converted_lat_a = angle_to_radians<scalar_t>(lat_a);
236
+ scalar_t converted_lat_b = angle_to_radians<scalar_t>(lat_b);
237
237
 
238
- scalar_t x = square(std::sin(lat_delta / 2)) +
239
- std::cos(converted_lat_a) * std::cos(converted_lat_b) * square(std::sin(lon_delta / 2));
238
+ scalar_t x = square(std::sin(lat_delta / 2.f)) +
239
+ std::cos(converted_lat_a) * std::cos(converted_lat_b) * square(std::sin(lon_delta / 2.f));
240
240
 
241
- return std::atan2(std::sqrt(x), std::sqrt(1 - x));
241
+ return std::atan2(std::sqrt(x), std::sqrt(1.f - x));
242
242
  }
243
243
  };
244
244
 
@@ -506,7 +506,7 @@ struct config_t {
506
506
  /// @brief Hyper-parameter controlling the quality of indexing.
507
507
  /// Defaults to 40 in FAISS and 200 in hnswlib.
508
508
  /// > It is called `efConstruction` in the paper.
509
- static constexpr std::size_t expansion_construction_default_k = 128;
509
+ static constexpr std::size_t expansion_add_default_k = 128;
510
510
 
511
511
  /// @brief Hyper-parameter controlling the quality of search.
512
512
  /// Defaults to 16 in FAISS and 10 in hnswlib.
@@ -514,7 +514,7 @@ struct config_t {
514
514
  static constexpr std::size_t expansion_search_default_k = 64;
515
515
 
516
516
  std::size_t connectivity = connectivity_default_k;
517
- std::size_t expansion_add = expansion_construction_default_k;
517
+ std::size_t expansion_add = expansion_add_default_k;
518
518
  std::size_t expansion_search = expansion_search_default_k;
519
519
 
520
520
  ///
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "description": "The Mighty Tiny Vector Search Engine with Automatic Quantization and Hardware Acceleration",
5
5
  "author": "Ashot Vardanian",
6
6
  "license": "Apache 2.0",
@@ -15,5 +15,8 @@
15
15
  "dependencies": {
16
16
  "bindings": "~1.2.1",
17
17
  "node-addon-api": "^3.0.0"
18
+ },
19
+ "scripts": {
20
+ "test": "node javascript/test.js"
18
21
  }
19
22
  }
@@ -195,15 +195,15 @@ PYBIND11_MODULE(usearch, m) {
195
195
 
196
196
  auto i = py::class_<native_index_t>(m, "Index");
197
197
 
198
- i.def(py::init(&make_index), //
199
- py::kw_only(), //
200
- py::arg("dim"), //
201
- py::arg("capacity") = 0, //
202
- py::arg("dtype") = std::string("f32"), //
203
- py::arg("metric") = std::string("ip"), //
204
- py::arg("connectivity") = config_t::connectivity_default_k, //
205
- py::arg("expansion_add") = config_t::expansion_construction_default_k, //
206
- py::arg("expansion_search") = config_t::expansion_search_default_k //
198
+ i.def(py::init(&make_index), //
199
+ py::kw_only(), //
200
+ py::arg("dim"), //
201
+ py::arg("capacity") = 0, //
202
+ py::arg("dtype") = std::string("f32"), //
203
+ py::arg("metric") = std::string("ip"), //
204
+ py::arg("connectivity") = config_t::connectivity_default_k, //
205
+ py::arg("expansion_add") = config_t::expansion_add_default_k, //
206
+ py::arg("expansion_search") = config_t::expansion_search_default_k //
207
207
  );
208
208
 
209
209
  i.def( //
package/rust/lib.cpp ADDED
@@ -0,0 +1,72 @@
1
+ #include "lib.hpp"
2
+ #include "usearch/rust/lib.rs.h"
3
+
4
+ using namespace unum::usearch;
5
+ using namespace unum;
6
+
7
+ using native_index_t = typename Index::native_index_t;
8
+
9
+ Index::Index(std::shared_ptr<native_index_t> index) : index_(index) {}
10
+
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);
13
+ }
14
+
15
+ Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const {
16
+ Matches results;
17
+ results.labels.reserve(count);
18
+ results.distances.reserve(count);
19
+ for (size_t i = 0; i != count; ++i)
20
+ 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);
22
+ results.labels.truncate(results.count);
23
+ results.distances.truncate(results.count);
24
+ return results;
25
+ }
26
+
27
+ void Index::add(uint32_t label, rust::Slice<float const> vector) const { return add_in_thread(label, vector, 0); }
28
+ Matches Index::search(rust::Slice<float const> vector, size_t count) const {
29
+ return search_in_thread(vector, count, 0);
30
+ }
31
+
32
+ void Index::reserve(size_t capacity) const { index_->reserve(capacity); }
33
+
34
+ size_t Index::dimensions() const { return index_->dimensions(); }
35
+ size_t Index::connectivity() const { return index_->connectivity(); }
36
+ size_t Index::size() const { return index_->size(); }
37
+ size_t Index::capacity() const { return index_->capacity(); }
38
+
39
+ void Index::save(rust::Str path) const { index_->save(std::string(path).c_str()); }
40
+ void Index::load(rust::Str path) const { index_->load(std::string(path).c_str()); }
41
+ void Index::view(rust::Str path) const { index_->view(std::string(path).c_str()); }
42
+
43
+ accuracy_t accuracy(rust::Str quant) { return accuracy(quant.data(), quant.size()); }
44
+
45
+ std::unique_ptr<Index> wrap(native_index_t&& index) {
46
+ std::shared_ptr<native_index_t> native = std::make_shared<native_index_t>(std::move(index));
47
+ return std::unique_ptr<Index>(new Index(native));
48
+ }
49
+
50
+ config_t config(size_t connectivity, size_t exp_add, size_t exp_search) {
51
+ config_t result;
52
+ result.connectivity = connectivity ? connectivity : config_t::connectivity_default_k;
53
+ result.expansion_add = exp_add ? exp_add : config_t::expansion_add_default_k;
54
+ result.expansion_search = exp_search ? exp_search : config_t::expansion_search_default_k;
55
+ return result;
56
+ }
57
+
58
+ std::unique_ptr<Index> new_ip(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search) {
59
+ return wrap(native_index_t::ip(dims, accuracy(quant), config(connectivity, exp_add, exp_search)));
60
+ }
61
+
62
+ std::unique_ptr<Index> new_l2(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search) {
63
+ return wrap(native_index_t::l2(dims, accuracy(quant), config(connectivity, exp_add, exp_search)));
64
+ }
65
+
66
+ std::unique_ptr<Index> new_cos(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search) {
67
+ return wrap(native_index_t::cos(dims, accuracy(quant), config(connectivity, exp_add, exp_search)));
68
+ }
69
+
70
+ std::unique_ptr<Index> new_haversine(rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search) {
71
+ return wrap(native_index_t::haversine(accuracy(quant), config(connectivity, exp_add, exp_search)));
72
+ }
package/rust/lib.hpp ADDED
@@ -0,0 +1,46 @@
1
+ #pragma once
2
+ #include "rust/cxx.h"
3
+
4
+ #include <memory> // `std::shared_ptr`
5
+
6
+ #include "advanced.hpp"
7
+
8
+ struct Matches;
9
+
10
+ enum class Metric;
11
+
12
+ enum class Accuracy;
13
+
14
+ class Index {
15
+ public:
16
+ using label_t = std::uint32_t;
17
+ using distance_t = unum::usearch::punned_distance_t;
18
+ using native_index_t = unum::usearch::auto_index_gt<label_t>;
19
+
20
+ Index(std::shared_ptr<native_index_t> index);
21
+
22
+ void reserve(size_t) const;
23
+
24
+ void add(uint32_t label, rust::Slice<float const> vector) const;
25
+ void add_in_thread(uint32_t label, rust::Slice<float const> vector, size_t thread) const;
26
+
27
+ Matches search(rust::Slice<float const> vector, size_t count) const;
28
+ Matches search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const;
29
+
30
+ size_t dimensions() const;
31
+ size_t connectivity() const;
32
+ size_t size() const;
33
+ size_t capacity() const;
34
+
35
+ void save(rust::Str path) const;
36
+ void load(rust::Str path) const;
37
+ void view(rust::Str path) const;
38
+
39
+ private:
40
+ std::shared_ptr<native_index_t> index_;
41
+ };
42
+
43
+ std::unique_ptr<Index> new_ip(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search);
44
+ std::unique_ptr<Index> new_l2(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search);
45
+ std::unique_ptr<Index> new_cos(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search);
46
+ std::unique_ptr<Index> new_haversine(rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search);
package/rust/lib.rs ADDED
@@ -0,0 +1,83 @@
1
+ #[cxx::bridge]
2
+ pub mod ffi {
3
+
4
+ // Shared structs with fields visible to both languages.
5
+ struct Matches {
6
+ count: usize,
7
+ labels: Vec<u32>,
8
+ distances: Vec<f32>,
9
+ }
10
+
11
+ // C++ types and signatures exposed to Rust.
12
+ unsafe extern "C++" {
13
+ include!("lib.hpp");
14
+
15
+ type Index;
16
+
17
+ fn new_ip(dimensions: usize, quantization: &str, connectivity: usize, expansion_add: usize, expansion_search: usize) -> Result<UniquePtr<Index>>;
18
+ fn new_l2(dimensions: usize, quantization: &str, connectivity: usize, expansion_add: usize, expansion_search: usize) -> Result<UniquePtr<Index>>;
19
+ fn new_cos(dimensions: usize, quantization: &str, connectivity: usize, expansion_add: usize, expansion_search: usize) -> Result<UniquePtr<Index>>;
20
+ fn new_haversine(quantization: &str, connectivity: usize, expansion_add: usize, expansion_search: usize) -> Result<UniquePtr<Index>>;
21
+
22
+ fn reserve(self: &Index, capacity: usize) -> Result<()>;
23
+
24
+ fn dimensions(self: &Index) -> usize;
25
+ fn connectivity(self: &Index) -> usize;
26
+ fn size(self: &Index) -> usize;
27
+ fn capacity(self: &Index) -> usize;
28
+
29
+ fn add(self: &Index, label: u32, vector: &[f32]) -> Result<()>;
30
+ fn search(self: &Index, query: &[f32], count: usize) -> Result<Matches>;
31
+
32
+ fn add_in_thread(self: &Index, label: u32, vector: &[f32], thread: usize) -> Result<()>;
33
+ fn search_in_thread(self: &Index, query: &[f32], count: usize, thread: usize) -> Result<Matches>;
34
+
35
+ fn save(self: &Index, path: &str) -> Result<()>;
36
+ fn load(self: &Index, path: &str) -> Result<()>;
37
+ fn view(self: &Index, path: &str) -> Result<()>;
38
+ }
39
+ }
40
+
41
+
42
+ #[cfg(test)]
43
+ mod tests {
44
+ use crate::ffi::new_ip;
45
+ use crate::ffi::new_l2;
46
+ use crate::ffi::new_cos;
47
+ use crate::ffi::new_haversine;
48
+
49
+ #[test]
50
+ fn integration() {
51
+
52
+ let quant = "f16";
53
+ let index = new_ip(5, &quant, 0, 0, 0).unwrap();
54
+
55
+ assert!(index.reserve(10).is_ok());
56
+ assert!(index.capacity() >= 10);
57
+ assert!(index.connectivity() != 0);
58
+ assert_eq!(index.dimensions(), 5);
59
+ assert_eq!(index.size(), 0);
60
+
61
+ let first: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
62
+ let second: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
63
+
64
+ assert!(index.add(42, &first).is_ok());
65
+ assert!(index.add(43, &second).is_ok());
66
+ assert_eq!(index.size(), 2);
67
+
68
+ // Read back the tags
69
+ let results = index.search(&first, 10).unwrap();
70
+ assert_eq!(results.count, 2);
71
+
72
+ // Validate serialization
73
+ assert!(index.save("index.rust.usearch").is_ok());
74
+ assert!(index.load("index.rust.usearch").is_ok());
75
+ assert!(index.view("index.rust.usearch").is_ok());
76
+
77
+ // Make sure every function is called at least once
78
+ assert!(new_ip(5, &quant, 0, 0, 0).is_ok());
79
+ assert!(new_l2(5, &quant, 0, 0, 0).is_ok());
80
+ assert!(new_cos(5, &quant, 0, 0, 0).is_ok());
81
+ assert!(new_haversine(&quant, 0, 0, 0).is_ok());
82
+ }
83
+ }
package/setup.py CHANGED
@@ -12,7 +12,7 @@ if sys.platform == 'darwin':
12
12
  ext_modules = [
13
13
  Pybind11Extension(
14
14
  'usearch',
15
- ['src/python.cpp'],
15
+ ['python/lib.cpp'],
16
16
  extra_compile_args=compile_args,
17
17
  ),
18
18
  ]
@@ -58,6 +58,6 @@ setup(
58
58
  'Topic :: Scientific/Engineering :: Artificial Intelligence',
59
59
  ],
60
60
 
61
- include_dirs=['include', 'fp16/include', 'simsimd/include'],
61
+ include_dirs=['include', 'fp16/include', 'simsimd/include', 'src'],
62
62
  ext_modules=ext_modules,
63
63
  )
package/src/advanced.hpp CHANGED
@@ -59,6 +59,14 @@ class f16_converted_t {
59
59
  inline f16_converted_t operator-(f16_converted_t other) const noexcept { return {float(*this) - float(other)}; }
60
60
  inline f16_converted_t operator*(f16_converted_t other) const noexcept { return {float(*this) * float(other)}; }
61
61
  inline f16_converted_t operator/(f16_converted_t other) const noexcept { return {float(*this) / float(other)}; }
62
+ inline f16_converted_t operator+(float other) const noexcept { return {float(*this) + other}; }
63
+ inline f16_converted_t operator-(float other) const noexcept { return {float(*this) - other}; }
64
+ inline f16_converted_t operator*(float other) const noexcept { return {float(*this) * other}; }
65
+ inline f16_converted_t operator/(float other) const noexcept { return {float(*this) / other}; }
66
+ inline f16_converted_t operator+(double other) const noexcept { return {float(*this) + other}; }
67
+ inline f16_converted_t operator-(double other) const noexcept { return {float(*this) - other}; }
68
+ inline f16_converted_t operator*(double other) const noexcept { return {float(*this) * other}; }
69
+ inline f16_converted_t operator/(double other) const noexcept { return {float(*this) / other}; }
62
70
 
63
71
  inline f16_converted_t& operator+=(float v) noexcept {
64
72
  uint16_ = fp16_ieee_from_fp32_value(v + fp16_ieee_to_fp32_value(uint16_));
@@ -164,6 +172,26 @@ enum class accuracy_t {
164
172
  i8q100_k,
165
173
  };
166
174
 
175
+ inline bool str_equals(char const* begin, size_t len, char const* other_begin) noexcept {
176
+ size_t other_len = strlen(other_begin);
177
+ return len == other_len && strncmp(begin, other_begin, len) == 0;
178
+ }
179
+
180
+ inline accuracy_t accuracy(char const* str, size_t len) {
181
+ accuracy_t accuracy;
182
+ if (str_equals(str, len, "f32"))
183
+ accuracy = accuracy_t::f32_k;
184
+ else if (str_equals(str, len, "f64"))
185
+ accuracy = accuracy_t::f64_k;
186
+ else if (str_equals(str, len, "f16"))
187
+ accuracy = accuracy_t::f16_k;
188
+ else if (str_equals(str, len, "i8q100"))
189
+ accuracy = accuracy_t::i8q100_k;
190
+ else
191
+ throw std::runtime_error("Unknown type, choose: f32, f16, f64, i8q100");
192
+ return accuracy;
193
+ }
194
+
167
195
  enum class isa_t {
168
196
  auto_k,
169
197
  neon_k,
@@ -252,11 +280,11 @@ class auto_index_gt {
252
280
  };
253
281
 
254
282
  std::size_t dimensions_ = 0;
283
+ std::size_t casted_vector_bytes_ = 0;
255
284
  accuracy_t accuracy_ = accuracy_t::f32_k;
256
- std::unique_ptr<index_t> index_;
257
-
258
285
  isa_t acceleration_ = isa_t::auto_k;
259
- std::size_t casted_vector_bytes_ = 0;
286
+
287
+ std::unique_ptr<index_t> index_;
260
288
  mutable std::vector<byte_t> cast_buffer_;
261
289
  struct casts_t {
262
290
  cast_t from_i8q100{};
@@ -269,6 +297,13 @@ class auto_index_gt {
269
297
  config_t root_config_;
270
298
 
271
299
  public:
300
+ auto_index_gt() = default;
301
+ auto_index_gt(auto_index_gt&& other)
302
+ : dimensions_(other.dimensions_), casted_vector_bytes_(other.casted_vector_bytes_), accuracy_(other.accuracy_),
303
+ acceleration_(other.acceleration_), index_(std::move(other.index_)),
304
+ cast_buffer_(std::move(other.cast_buffer_)), casts_(std::move(other.casts_)),
305
+ root_metric_(std::move(other.root_metric_)), root_config_(std::move(other.root_config_)) {}
306
+
272
307
  std::size_t dimensions() const noexcept { return dimensions_; }
273
308
  std::size_t connectivity() const noexcept { return index_->connectivity(); }
274
309
  std::size_t size() const noexcept { return index_->size(); }
@@ -292,20 +327,12 @@ class auto_index_gt {
292
327
  std::size_t search(f16_converted_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f16); }
293
328
  std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f32); }
294
329
  std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f64); }
295
- // clang-format on
296
330
 
297
- static auto_index_gt ip(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) {
298
- return make(dimensions, accuracy, ip_metric(dimensions, accuracy), make_casts(accuracy), config);
299
- }
300
-
301
- static auto_index_gt l2(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) {
302
- return make(dimensions, accuracy, l2_metric(dimensions, accuracy), make_casts(accuracy), config);
303
- }
304
-
305
- static auto_index_gt cos(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) {
306
- return {};
307
- }
308
- static auto_index_gt haversine(accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) { return {}; }
331
+ 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); }
332
+ 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); }
333
+ static auto_index_gt cos(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) { return make(dimensions, accuracy, cos_metric(dimensions, accuracy), make_casts(accuracy), config); }
334
+ static auto_index_gt haversine(accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) { return make(2, accuracy, haversine_metric(accuracy), make_casts(accuracy), config); }
335
+ // clang-format on
309
336
 
310
337
  auto_index_gt fork() const {
311
338
  auto_index_gt result;
@@ -339,8 +366,10 @@ class auto_index_gt {
339
366
  }
340
367
 
341
368
  template <typename scalar_at>
342
- std::size_t search(scalar_at const* vector, std::size_t wanted, label_t* matches, distance_t* distances,
343
- std::size_t thread, cast_t const& cast) const {
369
+ std::size_t search( //
370
+ scalar_at const* vector, std::size_t wanted, //
371
+ label_t* matches, distance_t* distances, //
372
+ std::size_t thread, cast_t const& cast) const {
344
373
 
345
374
  byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
346
375
  std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
@@ -412,7 +441,7 @@ class auto_index_gt {
412
441
  if (supports_arm_sve())
413
442
  return {
414
443
  pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
415
- return 1 - simsimd_dot_f32sve(a, b, d);
444
+ return 1 - simsimd_dot_f32sve(a, b, d);
416
445
  }),
417
446
  isa_t::sve_k,
418
447
  };
@@ -461,7 +490,7 @@ class auto_index_gt {
461
490
  }
462
491
  }
463
492
 
464
- static metric_and_meta_t l2_metric(std::size_t dimensions, accuracy_t accuracy) {
493
+ static metric_and_meta_t l2_metric(std::size_t, accuracy_t accuracy) {
465
494
  switch (accuracy) {
466
495
  case accuracy_t::i8q100_k: return {};
467
496
  case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(l2_squared_gt<f16_converted_t>{}), isa_t::auto_k};
@@ -471,7 +500,7 @@ class auto_index_gt {
471
500
  }
472
501
  }
473
502
 
474
- static metric_and_meta_t cos_metric(std::size_t dimensions, accuracy_t accuracy) {
503
+ static metric_and_meta_t cos_metric(std::size_t, accuracy_t accuracy) {
475
504
  switch (accuracy) {
476
505
  case accuracy_t::i8q100_k: return {};
477
506
  case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(cos_gt<f16_converted_t>{}), isa_t::auto_k};
@@ -480,6 +509,16 @@ class auto_index_gt {
480
509
  default: return {};
481
510
  }
482
511
  }
512
+
513
+ static metric_and_meta_t haversine_metric(accuracy_t accuracy) {
514
+ switch (accuracy) {
515
+ case accuracy_t::i8q100_k: return {};
516
+ case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(haversine_gt<f16_converted_t>{}), isa_t::auto_k};
517
+ case accuracy_t::f32_k: return {pun_metric<f32_t>(haversine_gt<f32_t>{}), isa_t::auto_k};
518
+ case accuracy_t::f64_k: return {pun_metric<f64_t>(haversine_gt<f64_t>{}), isa_t::auto_k};
519
+ default: return {};
520
+ }
521
+ }
483
522
  };
484
523
 
485
524
  using auto_index_t = auto_index_gt<std::int64_t, std::uint32_t>;
package/src/bench.cpp CHANGED
@@ -256,7 +256,7 @@ struct args_t {
256
256
  std::string path_neighbors;
257
257
 
258
258
  std::size_t connectivity = config_t::connectivity_default_k;
259
- std::size_t expansion_add = config_t::expansion_construction_default_k;
259
+ std::size_t expansion_add = config_t::expansion_add_default_k;
260
260
  std::size_t expansion_search = config_t::expansion_search_default_k;
261
261
  std::size_t threads = std::thread::hardware_concurrency();
262
262
 
@@ -273,6 +273,19 @@ struct args_t {
273
273
  bool metric_haversine = false;
274
274
  };
275
275
 
276
+ template <typename index_at, typename dataset_at> //
277
+ index_at type_punned_index_for_metric(dataset_at& dataset, args_t const& args, config_t config, accuracy_t accuracy) {
278
+ if (args.metric_ip)
279
+ return index_at::ip(dataset.dimensions(), accuracy, config);
280
+ if (args.metric_l2)
281
+ return index_at::l2(dataset.dimensions(), accuracy, config);
282
+ if (args.metric_cos)
283
+ return index_at::cos(dataset.dimensions(), accuracy, config);
284
+ if (args.metric_haversine)
285
+ return index_at::haversine(accuracy, config);
286
+ return index_at{};
287
+ }
288
+
276
289
  template <typename index_at, typename dataset_at> //
277
290
  void run_type_punned(dataset_at& dataset, args_t const& args, config_t config) {
278
291
 
@@ -282,16 +295,7 @@ void run_type_punned(dataset_at& dataset, args_t const& args, config_t config) {
282
295
  if (args.quantize_i8)
283
296
  accuracy = accuracy_t::i8q100_k;
284
297
 
285
- index_at index;
286
- if (args.metric_ip)
287
- index = index_at::ip(dataset.dimensions(), accuracy, config);
288
- if (args.metric_l2)
289
- index = index_at::l2(dataset.dimensions(), accuracy, config);
290
- if (args.metric_cos)
291
- index = index_at::cos(dataset.dimensions(), accuracy, config);
292
- if (args.metric_haversine)
293
- index = index_at::haversine(accuracy, config);
294
-
298
+ index_at index{type_punned_index_for_metric<index_at>(dataset, args, config, accuracy)};
295
299
  std::printf("- Hardware acceleration: %s\n", isa(index.acceleration()));
296
300
  std::printf("-- Will benchmark in-memory\n");
297
301
 
package/src/main.rs DELETED
@@ -1,42 +0,0 @@
1
- #[cxx::bridge]
2
- mod ffi {
3
-
4
- // Shared structs with fields visible to both languages.
5
- struct Results {
6
- size: usize,
7
- labels: Vec<u32>,
8
- distances: Vec<f32>,
9
- }
10
-
11
- // C++ types and signatures exposed to Rust.
12
- unsafe extern "C++" {
13
- include!("rust.h");
14
-
15
- type Index;
16
-
17
- fn new_index() -> UniquePtr<Index>;
18
-
19
- fn dim(&self) -> u64;
20
- fn connectivity(&self) -> u64;
21
- fn size(&self) -> u64;
22
- fn capacity(&self) -> u64;
23
-
24
- fn add(&self, label: u64, vector: &[f32]);
25
- fn search(&self, query: &[f32], count: u64) -> Results;
26
- }
27
- }
28
-
29
-
30
- fn main() {
31
- let client = ffi::new_index();
32
-
33
- // Upload a blob.
34
- let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
35
- let mut buf = MultiBuf { chunks, pos: 0 };
36
- let blobid = client.add(&mut buf);
37
- println!("blobid = {}", blobid);
38
-
39
- // Read back the tags.
40
- let search = client.search(blobid);
41
- println!("tags = {:?}", search.tags);
42
- }
package/src/rust.cpp DELETED
@@ -1,23 +0,0 @@
1
- #include "rust.h"
2
-
3
- #include "advanced.hpp"
4
-
5
- using namespace unum::usearch;
6
- using namespace unum;
7
-
8
- class Index::impl {
9
- friend Index;
10
- downcasting_index_t native_;
11
- };
12
-
13
- Index::Index() : impl(new class Index::impl) {}
14
-
15
- void Index::add(uint64_t label, rust::Slice<float const> vector) const {}
16
- Results Index::search(rust::Slice<float const> vector, uint64_t count) const { return {}; }
17
-
18
- uint64_t Index::dim() const { return impl->native_.dim(); }
19
- uint64_t Index::connectivity() const { return impl->native_.connectivity(); }
20
- uint64_t Index::size() const { return impl->native_.size(); }
21
- uint64_t Index::capacity() const { return impl->native_.capacity(); }
22
-
23
- std::unique_ptr<Index> new_index() { return std::make_unique<Index>(); }
package/src/rust.h DELETED
@@ -1,22 +0,0 @@
1
- #pragma once
2
- #include "rust/cxx.h"
3
-
4
- struct Results;
5
-
6
- class Index {
7
- public:
8
- Index();
9
- void add(uint64_t label, rust::Slice<float const> vector) const;
10
- Results search(rust::Slice<float const> vector, uint64_t count) const;
11
-
12
- uint64_t dim() const;
13
- uint64_t connectivity() const;
14
- uint64_t size() const;
15
- uint64_t capacity() const;
16
-
17
- private:
18
- class impl;
19
- std::shared_ptr<impl> impl;
20
- };
21
-
22
- std::unique_ptr<Index> new_index();
File without changes
File without changes
File without changes
File without changes