usearch 2.1.1 → 2.1.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.
@@ -74,8 +74,9 @@ jobs:
74
74
  name: Test Python
75
75
  runs-on: ${{ matrix.os }}
76
76
  strategy:
77
+ fail-fast: false
77
78
  matrix:
78
- os: [ubuntu-20.04, macOS-11, windows-2022]
79
+ os: [ubuntu-22.04, macOS-11, windows-2022]
79
80
  python-version: ["3.11"]
80
81
 
81
82
  steps:
@@ -89,8 +90,16 @@ jobs:
89
90
  run: |
90
91
  python -m pip install --upgrade pip
91
92
  pip install pytest numpy
93
+ - name: Build locally on Ubuntu
94
+ run: |
95
+ export CC=/usr/bin/gcc-12
96
+ export CXX=/usr/bin/g++-12
97
+ export LD_LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/12/:$LD_LIBRARY_PATH
98
+ python -m pip install .
99
+ if: ${{ matrix.os == 'ubuntu-22.04' }}
92
100
  - name: Build locally
93
101
  run: python -m pip install .
102
+ if: ${{ matrix.os != 'ubuntu-22.04' }}
94
103
  - name: Test with PyTest
95
104
  run: pytest python/scripts/
96
105
 
@@ -99,8 +108,9 @@ jobs:
99
108
  name: Test Python 3.7
100
109
  runs-on: ${{ matrix.os }}
101
110
  strategy:
111
+ fail-fast: false
102
112
  matrix:
103
- os: [ubuntu-20.04]
113
+ os: [ubuntu-22.04]
104
114
  python-version: ["3.7"]
105
115
 
106
116
  steps:
@@ -118,7 +128,12 @@ jobs:
118
128
  pip install pytest numpy
119
129
 
120
130
  - name: Build locally
121
- run: python -m pip install .
131
+ run: |
132
+ export CC=/usr/bin/gcc-12
133
+ export CXX=/usr/bin/g++-12
134
+ export LD_LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/12/:$LD_LIBRARY_PATH
135
+ python -m pip install .
136
+ if: ${{ matrix.os == 'ubuntu-22.04' }}
122
137
 
123
138
  - name: Test with PyTest
124
139
  run: pytest python/scripts/
@@ -30,7 +30,7 @@ jobs:
30
30
 
31
31
 
32
32
  rebase:
33
- name: Rebase Development Branch
33
+ name: Rebase Dev. Branch
34
34
  needs: versioning
35
35
  runs-on: ubuntu-latest
36
36
  steps:
@@ -41,13 +41,14 @@ jobs:
41
41
 
42
42
  - name: Perform rebase
43
43
  run: |
44
+ git fetch origin main
44
45
  git checkout main-dev
45
- git rebase main
46
+ git rebase origin/main
46
47
 
47
48
  - name: Push changes
48
49
  uses: CasperWA/push-protected@v2
49
50
  with:
50
- token: ${{ secrets.SEMANTIC_REBASE_TOKEN }}
51
+ token: ${{ secrets.SEMANTIC_RELEASE_TOKEN }}
51
52
  branch: main-dev
52
53
  unprotect_reviews: True
53
54
  force: True
@@ -239,12 +240,14 @@ jobs:
239
240
  - name: Calculate source hashes
240
241
  id: hashes
241
242
  run: |
242
- source_zip_path=$(find $GITHUB_WORKSPACE/downloads -name "*.zip")
243
- zip_hash=$(sha256sum "$source_zip_path" | awk '{print $1}')
244
- source_tar_path=$(find $GITHUB_WORKSPACE/downloads -name "*.tar.gz" ! -name "docs.tar.gz")
245
- tar_hash=$(sha256sum "$source_tar_path" | awk '{print $1}')
246
- echo "zip_hash=$zip_hash" >> $GITHUB_OUTPUT
247
- echo "tar_hash=$tar_hash" >> $GITHUB_OUTPUT
243
+ i=1
244
+ cd $GITHUB_WORKSPACE/downloads
245
+ for asset_name in $(ls); do
246
+ asset_hash=$(sha256sum $asset_name | awk '{print $1}')
247
+ echo "asset_name_$i=$asset_name" >> $GITHUB_OUTPUT
248
+ echo "asset_hash_$i=$asset_hash" >> $GITHUB_OUTPUT
249
+ i=$((i+1))
250
+ done
248
251
 
249
252
  - name: Upload hashes to release
250
253
  uses: irongut/EditRelease@v1.2.0
@@ -255,8 +258,14 @@ jobs:
255
258
  replacebody: false
256
259
  body: |
257
260
  ## Hashes
258
- * Source code (zip) : `${{ steps.hashes.outputs.zip_hash }}`
259
- * Source code (tar.gz) : `${{ steps.hashes.outputs.tar_hash }}`
261
+ * ${{ steps.hashes.outputs.asset_name_1 }} : `${{ steps.hashes.outputs.asset_hash_1 }}`
262
+ * ${{ steps.hashes.outputs.asset_name_2 }} : `${{ steps.hashes.outputs.asset_hash_2 }}`
263
+ * ${{ steps.hashes.outputs.asset_name_3 }} : `${{ steps.hashes.outputs.asset_hash_3 }}`
264
+ * ${{ steps.hashes.outputs.asset_name_4 }} : `${{ steps.hashes.outputs.asset_hash_4 }}`
265
+ * ${{ steps.hashes.outputs.asset_name_5 }} : `${{ steps.hashes.outputs.asset_hash_5 }}`
266
+ * ${{ steps.hashes.outputs.asset_name_6 }} : `${{ steps.hashes.outputs.asset_hash_6 }}`
267
+ * ${{ steps.hashes.outputs.asset_name_7 }} : `${{ steps.hashes.outputs.asset_hash_7 }}`
268
+ * ${{ steps.hashes.outputs.asset_name_8 }} : `${{ steps.hashes.outputs.asset_hash_8 }}`
260
269
 
261
270
 
262
271
  build_wheels:
package/CMakeLists.txt CHANGED
@@ -49,11 +49,26 @@ else()
49
49
  target_compile_features(${USEARCH_TARGET_NAME} INTERFACE cxx_std_11)
50
50
  endif()
51
51
 
52
+ # Core compilation settings affecting "index.hpp"
52
53
  target_compile_definitions(
53
54
  ${USEARCH_TARGET_NAME}
54
55
  INTERFACE
55
56
  $<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:USEARCH_USE_OPENMP=0>
56
57
  )
58
+
59
+ # Supplementary compilation settings affecting "index_plugins.hpp"
60
+ target_compile_definitions(
61
+ ${USEARCH_TARGET_NAME}
62
+ INTERFACE
63
+ $<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:USEARCH_USE_NATIVE_F16=0>
64
+ )
65
+
66
+ target_compile_definitions(
67
+ ${USEARCH_TARGET_NAME}
68
+ INTERFACE
69
+ $<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:USEARCH_USE_SIMSIMD=0>
70
+ )
71
+
57
72
  target_include_directories(
58
73
  ${USEARCH_TARGET_NAME}
59
74
  ${USEARCH_SYSTEM_INCLUDE} INTERFACE
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "2.1.1"
3
+ version = "2.1.3"
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
@@ -39,8 +39,8 @@ Linux • MacOS • Windows • iOS • Docker • WebAssembly
39
39
  <a href="https://pypi.org/project/usearch/"> <img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/usearch?label=pypi%20downloads"> </a>
40
40
  <a href="https://www.npmjs.com/package/usearch"> <img alt="npm" src="https://img.shields.io/npm/dy/usearch?label=npm%20dowloads"> </a>
41
41
  <a href="https://crates.io/crates/usearch"> <img alt="Crates.io" src="https://img.shields.io/crates/d/usearch?label=crate%20downloads"> </a>
42
- <a href="https://www.nuget.org/packages/Cloud.Unum.USearch"> <img alt="Nuget" src="https://img.shields.io/nuget/dt/Cloud.Unum.USearch?style=social&label=NuGet"> </a>
43
- <a href="https://hub.docker.com/r/unum/usearch"> <img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/unum/usearch?style=social&label=Docker"> </a>
42
+ <a href="https://www.nuget.org/packages/Cloud.Unum.USearch"> <img alt="Nuget" src="https://img.shields.io/nuget/dt/Cloud.Unum.USearch?label=NuGet"> </a>
43
+ <a href="https://hub.docker.com/r/unum/usearch"> <img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/unum/usearch?label=Docker"> </a>
44
44
  <img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/unum-cloud/usearch">
45
45
  </div>
46
46
 
@@ -83,7 +83,7 @@ USearch is compact and broadly compatible without sacrificing performance, with
83
83
  Base functionality is identical to FAISS, and the interface must be familiar if you have ever investigated Approximate Nearest Neighbors search:
84
84
 
85
85
  ```py
86
- $ pip install usearch numpy
86
+ $ pip install usearch
87
87
 
88
88
  import numpy as np
89
89
  from usearch.index import Index
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.1.1'
7
+ version = '2.1.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/docs/format.md ADDED
@@ -0,0 +1,63 @@
1
+ # USearch Format Specification
2
+
3
+ > ⚠️ **Note**: The format is expected to change in the 3.0 release to support sparse indexes and offer even faster startup times when `view`-ing large indexes from disk.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Dense Indexes](#dense-indexes)
8
+ - [Core](#core)
9
+ - [Fragmented Storage](#fragmented-storage)
10
+
11
+ ## Dense Indexes
12
+
13
+ Dense index files consist of two main parts: [a binary matrix of all vectors](#matrix-blob) and the [index](#index-blob) itself.
14
+
15
+ ### Matrix BLOB
16
+
17
+ The matrix BLOB is optionally prepended to the index file. It starts with the number of `rows` and `columns`, which can be either 32-bit or 64-bit unsigned integers.
18
+
19
+ - The number of `rows` corresponds to the number of vectors in the index.
20
+ - The number of `columns` corresponds to the number of bytes in a single vector.
21
+
22
+ ### Index BLOB
23
+
24
+ The index BLOB is composed of three main sections:
25
+
26
+ 1. **Metadata**: Occupies 64 bytes and contains the following fields:
27
+ 1. 7-byte magic string `usearch` to infer the MIME type of the file.
28
+ 2. 3-byte version number, containing the `major`, `minor`, and `patch` release numbers.
29
+ 3. 1-byte enums for `metric`, `scalar`, `key`, and `compressed_slot` types.
30
+ 4. 8-byte integers for the number of present vectors, number of deleted vectors, and number of dimensions.
31
+ 5. 1-byte flags for multi-vector support.
32
+
33
+ 2. **Levels**: Contains 1-byte integers representing the level each node occupies. This information is used to estimate memory requirements and to enable memory-mapping of the entire file.
34
+
35
+ 3. **Core**: Detailed in the [Core section](#core). This format is consistent across all types of indexes.
36
+
37
+ ## Fragmented Storage
38
+
39
+ The matrix BLOB can be stored separately and is not required to successfully open the file.
40
+
41
+ ## Core
42
+
43
+ The core section of the index is serialized as a sequence of nodes, each representing a point in the index. This section immediately follows the `levels` block in the serialized format.
44
+
45
+ ### Header
46
+
47
+ The core section begins with a header containing the following fields:
48
+
49
+ - `uint64_t size`: Number of nodes in the index.
50
+ - `uint64_t connectivity`: Maximum number of edges for a node.
51
+ - `uint64_t connectivity_base`: Base connectivity value.
52
+ - `uint64_t max_level`: Maximum level in the multi-level graph.
53
+ - `uint64_t entry_slot`: Entry slot for the graph.
54
+
55
+ These fields are serialized in a contiguous block of bytes, in the order listed.
56
+
57
+ ### Levels
58
+
59
+ Following the header, the `levels` block contains a sequence of 1-byte integers. Each integer represents the level of a node in the index. The number of levels is equal to the `size` field in the header.
60
+
61
+ ### Nodes
62
+
63
+ After the `levels` block, the nodes are serialized. Each node is represented as a contiguous block of bytes. The size and format of these blocks are implementation-specific. Nodes are serialized in the same order as their corresponding levels in the `levels` block.
@@ -0,0 +1,5 @@
1
+ ========================
2
+ Format
3
+ ========================
4
+
5
+ .. mdinclude:: format.md
package/docs/index.rst CHANGED
@@ -24,6 +24,7 @@ Overview
24
24
 
25
25
  compilation
26
26
  benchmarks
27
+ format
27
28
 
28
29
  .. toctree::
29
30
  :hidden:
@@ -1272,7 +1272,7 @@ struct dummy_executor_t {
1272
1272
  * @brief An example of what a USearch-compatible key-to-key mapping should look like.
1273
1273
  *
1274
1274
  * This is particularly helpful for "Semantic Joins", where we map entries of one collection
1275
- * to entries of another. In assymetric setups, where A -> B is needed, but B -> A is not,
1275
+ * to entries of another. In asymmetric setups, where A -> B is needed, but B -> A is not,
1276
1276
  * this can be passed to minimize memory usage.
1277
1277
  */
1278
1278
  struct dummy_key_to_key_mapping_t {
@@ -1284,16 +1284,16 @@ struct dummy_key_to_key_mapping_t {
1284
1284
 
1285
1285
  /**
1286
1286
  * @brief Checks if the provided object has a dummy type, emulating an interface,
1287
- * but performaing no real computation.
1287
+ * but performing no real computation.
1288
1288
  */
1289
1289
  template <typename object_at> static constexpr bool is_dummy() {
1290
1290
  using object_t = typename std::remove_all_extents<object_at>::type;
1291
- return std::is_same<object_t, dummy_predicate_t>::value || //
1292
- std::is_same<object_t, dummy_callback_t>::value || //
1293
- std::is_same<object_t, dummy_progress_t>::value || //
1294
- std::is_same<object_t, dummy_prefetch_t>::value || //
1295
- std::is_same<object_t, dummy_executor_t>::value || //
1296
- std::is_same<object_t, dummy_key_to_key_mapping_t>::value;
1291
+ return std::is_same<typename std::decay<object_t>::type, dummy_predicate_t>::value || //
1292
+ std::is_same<typename std::decay<object_t>::type, dummy_callback_t>::value || //
1293
+ std::is_same<typename std::decay<object_t>::type, dummy_progress_t>::value || //
1294
+ std::is_same<typename std::decay<object_t>::type, dummy_prefetch_t>::value || //
1295
+ std::is_same<typename std::decay<object_t>::type, dummy_executor_t>::value || //
1296
+ std::is_same<typename std::decay<object_t>::type, dummy_key_to_key_mapping_t>::value;
1297
1297
  }
1298
1298
 
1299
1299
  template <typename, typename at> struct has_reset_gt {
@@ -1513,6 +1513,8 @@ class memory_mapped_file_t {
1513
1513
  #else
1514
1514
  int descriptor = open(path_, O_RDONLY);
1515
1515
  #endif
1516
+ if (descriptor < 0)
1517
+ return result.failed(std::strerror(errno));
1516
1518
 
1517
1519
  // Estimate the file size
1518
1520
  struct stat file_stat;
@@ -2502,7 +2504,7 @@ class index_gt {
2502
2504
  }
2503
2505
 
2504
2506
  /**
2505
- * @brief Identifies the closest cluster to the gived ::query. Thread-safe.
2507
+ * @brief Identifies the closest cluster to the given ::query. Thread-safe.
2506
2508
  *
2507
2509
  * @param[in] query Content that will be compared against other entries in the index.
2508
2510
  * @param[in] level The index level to target. Higher means lower resolution.
@@ -3325,7 +3327,7 @@ class index_gt {
3325
3327
  visits.clear();
3326
3328
 
3327
3329
  // Optional prefetching
3328
- if (!std::is_same<prefetch_at, dummy_prefetch_t>::value)
3330
+ if (!std::is_same<typename std::decay<prefetch_at>::type, dummy_prefetch_t>::value)
3329
3331
  prefetch(citerator_at(closest_slot), citerator_at(closest_slot + 1));
3330
3332
 
3331
3333
  distance_t closest_dist = context.measure(query, citerator_at(closest_slot), metric);
@@ -3337,7 +3339,7 @@ class index_gt {
3337
3339
  neighbors_ref_t closest_neighbors = neighbors_non_base_(node_at_(closest_slot), level);
3338
3340
 
3339
3341
  // Optional prefetching
3340
- if (!std::is_same<prefetch_at, dummy_prefetch_t>::value) {
3342
+ if (!std::is_same<typename std::decay<prefetch_at>::type, dummy_prefetch_t>::value) {
3341
3343
  candidates_range_t missing_candidates{*this, closest_neighbors, visits};
3342
3344
  prefetch(missing_candidates.begin(), missing_candidates.end());
3343
3345
  }
@@ -3379,7 +3381,7 @@ class index_gt {
3379
3381
  return false;
3380
3382
 
3381
3383
  // Optional prefetching
3382
- if (!std::is_same<prefetch_at, dummy_prefetch_t>::value)
3384
+ if (!std::is_same<typename std::decay<prefetch_at>::type, dummy_prefetch_t>::value)
3383
3385
  prefetch(citerator_at(start_slot), citerator_at(start_slot + 1));
3384
3386
 
3385
3387
  distance_t radius = context.measure(query, citerator_at(start_slot), metric);
@@ -3404,7 +3406,7 @@ class index_gt {
3404
3406
  neighbors_ref_t candidate_neighbors = neighbors_(candidate_ref, level);
3405
3407
 
3406
3408
  // Optional prefetching
3407
- if (!std::is_same<prefetch_at, dummy_prefetch_t>::value) {
3409
+ if (!std::is_same<typename std::decay<prefetch_at>::type, dummy_prefetch_t>::value) {
3408
3410
  candidates_range_t missing_candidates{*this, candidate_neighbors, visits};
3409
3411
  prefetch(missing_candidates.begin(), missing_candidates.end());
3410
3412
  }
@@ -3453,7 +3455,7 @@ class index_gt {
3453
3455
  return false;
3454
3456
 
3455
3457
  // Optional prefetching
3456
- if (!std::is_same<prefetch_at, dummy_prefetch_t>::value)
3458
+ if (!std::is_same<typename std::decay<prefetch_at>::type, dummy_prefetch_t>::value)
3457
3459
  prefetch(citerator_at(start_slot), citerator_at(start_slot + 1));
3458
3460
 
3459
3461
  distance_t radius = context.measure(query, citerator_at(start_slot), metric);
@@ -3473,7 +3475,7 @@ class index_gt {
3473
3475
  neighbors_ref_t candidate_neighbors = neighbors_base_(node_at_(candidate.slot));
3474
3476
 
3475
3477
  // Optional prefetching
3476
- if (!std::is_same<prefetch_at, dummy_prefetch_t>::value) {
3478
+ if (!std::is_same<typename std::decay<prefetch_at>::type, dummy_prefetch_t>::value) {
3477
3479
  candidates_range_t missing_candidates{*this, candidate_neighbors, visits};
3478
3480
  prefetch(missing_candidates.begin(), missing_candidates.end());
3479
3481
  }
@@ -41,6 +41,12 @@
41
41
  #endif
42
42
 
43
43
  #if USEARCH_USE_SIMSIMD
44
+ #if __linux__
45
+ #define SIMSIMD_TARGET_ARM_NEON 1
46
+ #define SIMSIMD_TARGET_X86_AVX2 1
47
+ #define SIMSIMD_TARGET_ARM_SVE 1
48
+ #define SIMSIMD_TARGET_X86_AVX512 1
49
+ #endif
44
50
  #include <simsimd/simsimd.h>
45
51
  #endif
46
52
 
@@ -600,9 +606,7 @@ class aligned_allocator_gt {
600
606
  using size_type = std::size_t;
601
607
  using pointer = element_at*;
602
608
  using const_pointer = element_at const*;
603
- template <typename other_element_at> struct rebind {
604
- using other = aligned_allocator_gt<other_element_at>;
605
- };
609
+ template <typename other_element_at> struct rebind { using other = aligned_allocator_gt<other_element_at>; };
606
610
 
607
611
  constexpr std::size_t alignment() const { return alignment_ak; }
608
612
 
@@ -1384,25 +1388,37 @@ class metric_punned_t {
1384
1388
  // clang-format off
1385
1389
  static metric_punned_t ip_metric_f32_(std::size_t bytes_per_vector) {
1386
1390
  #if USEARCH_USE_SIMSIMD
1387
- if (hardware_supports(isa_kind_t::sve_k)) return {pun_stl_<f32_t>([=](f32_t const* a, f32_t const* b) { return simsimd_dot_f32_sve(a, b, bytes_per_vector / 4); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f32_k, isa_kind_t::sve_k};
1391
+ #if SIMSIMD_TARGET_ARM_NEON
1388
1392
  if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 16 == 0) return {pun_stl_<f32_t>([=](f32_t const* a, f32_t const* b) { return simsimd_dot_f32x4_neon(a, b, bytes_per_vector / 4); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f32_k, isa_kind_t::neon_k};
1393
+ #endif
1394
+ #if SIMSIMD_TARGET_ARM_SVE
1395
+ if (hardware_supports(isa_kind_t::sve_k)) return {pun_stl_<f32_t>([=](f32_t const* a, f32_t const* b) { return simsimd_dot_f32_sve(a, b, bytes_per_vector / 4); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f32_k, isa_kind_t::sve_k};
1396
+ #endif
1397
+ #if SIMSIMD_TARGET_X86_AVX2
1389
1398
  if (hardware_supports(isa_kind_t::avx2_k) && bytes_per_vector % 16 == 0) return {pun_stl_<f32_t>([=](f32_t const* a, f32_t const* b) { return simsimd_dot_f32x4_avx2(a, b, bytes_per_vector / 4); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f32_k, isa_kind_t::avx2_k};
1390
1399
  #endif
1400
+ #endif
1391
1401
  return {to_stl_<metric_ip_gt<f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f32_k, isa_kind_t::auto_k};
1392
1402
  }
1393
1403
 
1394
1404
  static metric_punned_t cos_metric_f16_(std::size_t bytes_per_vector) {
1395
1405
  #if USEARCH_USE_SIMSIMD
1406
+ #if SIMSIMD_TARGET_X86_AVX512
1396
1407
  if (hardware_supports(isa_kind_t::avx512_k) && bytes_per_vector % 32 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_cos_f16x16_avx512(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::avx512_k};
1408
+ #endif
1409
+ #if SIMSIMD_TARGET_ARM_NEON
1397
1410
  if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 8 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_cos_f16x4_neon(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::neon_k};
1398
1411
  #endif
1412
+ #endif
1399
1413
  return {to_stl_<metric_cos_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
1400
1414
  }
1401
1415
 
1402
1416
  static metric_punned_t cos_metric_i8_(std::size_t bytes_per_vector) {
1403
1417
  #if USEARCH_USE_SIMSIMD
1418
+ #if SIMSIMD_TARGET_ARM_NEON
1404
1419
  if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 16 == 0) return {pun_stl_<int8_t>([=](int8_t const* a, int8_t const* b) { return simsimd_cos_i8x16_neon(a, b, bytes_per_vector); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::neon_k};
1405
1420
  #endif
1421
+ #endif
1406
1422
  return {to_stl_<cos_i8_t>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::auto_k};
1407
1423
  }
1408
1424
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian",
6
6
  "license": "Apache 2.0",
@@ -562,12 +562,11 @@ class Index:
562
562
  )
563
563
 
564
564
  self.path = path
565
- if path and os.path.exists(path):
566
- path = os.fspath(path)
565
+ if path is not None:
567
566
  if view:
568
- self._compiled.view(path)
567
+ self.view(path)
569
568
  else:
570
- self._compiled.load(path)
569
+ self.load(path)
571
570
 
572
571
  @staticmethod
573
572
  def metadata(path_or_buffer: PathOrBuffer) -> Optional[dict]:
@@ -919,7 +918,9 @@ class Index:
919
918
  def expansion_search(self, v: int):
920
919
  self._compiled.expansion_search = v
921
920
 
922
- def save(self, path_or_buffer: Union[str, os.PathLike, NoneType] = None) -> Optional[bytes]:
921
+ def save(
922
+ self, path_or_buffer: Union[str, os.PathLike, NoneType] = None
923
+ ) -> Optional[bytes]:
923
924
  path_or_buffer = path_or_buffer if path_or_buffer else self.path
924
925
  if path_or_buffer is None:
925
926
  return self._compiled.save_index_to_buffer()
@@ -937,7 +938,9 @@ class Index:
937
938
  else:
938
939
  self._compiled.load_index_from_path(os.fspath(path_or_buffer))
939
940
 
940
- def view(self, path_or_buffer: Union[str, os.PathLike, bytes, bytearray, NoneType] = None):
941
+ def view(
942
+ self, path_or_buffer: Union[str, os.PathLike, bytes, bytearray, NoneType] = None
943
+ ):
941
944
  path_or_buffer = path_or_buffer if path_or_buffer else self.path
942
945
  if path_or_buffer is None:
943
946
  raise Exception("Define the source")
package/setup.py CHANGED
@@ -7,10 +7,7 @@ from pybind11.setup_helpers import Pybind11Extension
7
7
 
8
8
  compile_args = []
9
9
  link_args = []
10
- macros_args = [
11
- ("USEARCH_USE_NATIVE_F16", "0"),
12
- ("USEARCH_USE_SIMSIMD", "0"),
13
- ]
10
+ macros_args = []
14
11
 
15
12
  if sys.platform == "linux":
16
13
  compile_args.append("-std=c++17")
@@ -18,6 +15,8 @@ if sys.platform == "linux":
18
15
  compile_args.append("-g") # Simplify debugging
19
16
  compile_args.append("-Wno-unknown-pragmas")
20
17
 
18
+ macros_args.append(("USEARCH_USE_NATIVE_F16", "0"))
19
+ macros_args.append(("USEARCH_USE_SIMSIMD", "1"))
21
20
  macros_args.append(("USEARCH_USE_OPENMP", "1"))
22
21
  compile_args.append("-fopenmp")
23
22
  link_args.append("-lgomp")
@@ -66,6 +65,7 @@ setup(
66
65
  package_dir={"usearch": "python/usearch"},
67
66
  description="Smaller & Faster Single-File Vector Search Engine from Unum",
68
67
  author="Ash Vardanian",
68
+ author_email="info@unum.cloud",
69
69
  long_description=long_description,
70
70
  long_description_content_type="text/markdown",
71
71
  license="Apache-2.0",
@@ -21,8 +21,9 @@ jobs:
21
21
  name: Test Python
22
22
  runs-on: ${{ matrix.os }}
23
23
  strategy:
24
+ fail-fast: false
24
25
  matrix:
25
- os: [ubuntu-20.04, macOS-11, windows-2022]
26
+ os: [ubuntu-22.04] # macOS-11, windows-2022
26
27
  python-version: ["3.11"]
27
28
 
28
29
  steps:
@@ -32,22 +33,54 @@ jobs:
32
33
  uses: actions/setup-python@v4
33
34
  with:
34
35
  python-version: ${{ matrix.python-version }}
36
+
37
+ - name: Set up Clang
38
+ run: brew install llvm
39
+ if: ${{ matrix.os == 'macOS-11' }}
40
+
41
+ - name: Set up MinGW
42
+ uses: egor-tensin/setup-mingw@v2
43
+ with:
44
+ platform: x64
45
+ if: ${{ matrix.os == 'windows-2022' }}
46
+
35
47
  - name: Install dependencies
36
48
  run: |
37
49
  python -m pip install --no-cache-dir --upgrade pip
38
50
  pip install --no-cache-dir pytest
39
- - name: Build locally
40
- run: python -m pip install .
51
+
52
+ - name: Build locally on Ubuntu
53
+ run: |
54
+ export CC=/usr/bin/gcc-12
55
+ export CXX=/usr/bin/g++-12
56
+ export LD_LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/12/:$LD_LIBRARY_PATH
57
+ python -m pip install .
58
+ if: ${{ matrix.os == 'ubuntu-22.04' }}
59
+
60
+ - name: Build locally on MacOS
61
+ run: |
62
+ export LDFLAGS="-L/usr/local/opt/llvm/lib"
63
+ export CPPFLAGS="-I/usr/local/opt/llvm/include"
64
+ export CC=/usr/local/opt/llvm/bin/clang
65
+ export CXX=/usr/local/opt/llvm/bin/clang++
66
+ python -m pip install .
67
+ if: ${{ matrix.os == 'macOS-11' }}
68
+
69
+ - name: Build locally on Windows
70
+ run: |
71
+ python -m pip install .
72
+ if: ${{ matrix.os == 'windows-2022' }}
73
+
41
74
  - name: Test with PyTest
42
75
  run: pytest python/test.py
43
-
76
+
44
77
 
45
78
  test_python_37:
46
79
  name: Test Python 3.7
47
80
  runs-on: ${{ matrix.os }}
48
81
  strategy:
49
82
  matrix:
50
- os: [ubuntu-20.04]
83
+ os: [ubuntu-22.04]
51
84
  python-version: ["3.7"]
52
85
 
53
86
  steps:
@@ -65,9 +98,11 @@ jobs:
65
98
  pip install --no-cache-dir pytest
66
99
 
67
100
  - name: Build locally
68
- run: python -m pip install .
69
-
101
+ run: |
102
+ export CC=/usr/bin/gcc-12
103
+ export CXX=/usr/bin/g++-12
104
+ export LD_LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/12/:$LD_LIBRARY_PATH
105
+ python -m pip install .
106
+
70
107
  - name: Test with PyTest
71
108
  run: pytest python/test.py
72
-
73
-
@@ -29,7 +29,7 @@ jobs:
29
29
 
30
30
 
31
31
  rebase:
32
- name: Rebase Development Branch
32
+ name: Rebase Dev. Branch
33
33
  needs: versioning
34
34
  runs-on: ubuntu-latest
35
35
  steps:
@@ -57,7 +57,7 @@ jobs:
57
57
  needs: versioning
58
58
  strategy:
59
59
  matrix:
60
- os: [ubuntu-20.04, macOS-11, windows-2022]
60
+ os: [ubuntu-20.04] # macOS-11, windows-2022
61
61
 
62
62
  steps:
63
63
  - uses: actions/checkout@v3
@@ -66,13 +66,11 @@ jobs:
66
66
  - uses: actions/setup-python@v3
67
67
 
68
68
  - name: Setup Docker
69
- if: matrix.os != 'windows-2022'
70
- uses: crazy-max/ghaction-setup-docker@v1.0.0
71
- with:
72
- version: 23.0.1
73
-
69
+ if: matrix.os == 'ubuntu-20.04'
70
+ uses: docker-practice/actions-setup-docker@master
71
+
74
72
  - name: Setup QEMU
75
- if: matrix.os != 'windows-2022'
73
+ if: matrix.os == 'ubuntu-20.04'
76
74
  uses: docker/setup-qemu-action@v2.1.0
77
75
 
78
76
  - name: Install CIBuildWheel
@@ -92,7 +90,7 @@ jobs:
92
90
  runs-on: ubuntu-20.04
93
91
  environment:
94
92
  name: pypi
95
- url: https://pypi.org/p/stringzilla
93
+ url: https://pypi.org/p/simsimd
96
94
  permissions:
97
95
  id-token: write
98
96
 
@@ -85,13 +85,17 @@
85
85
  "variant": "cpp",
86
86
  "__nullptr": "cpp",
87
87
  "unordered_set": "cpp",
88
- "span": "cpp"
88
+ "span": "cpp",
89
+ "arm_neon.h": "c"
89
90
  },
90
91
  "cSpell.words": [
91
92
  "dtype",
92
93
  "dtypes",
93
94
  "kann",
95
+ "maccs",
96
+ "simd",
94
97
  "simsimd",
98
+ "tanimoto",
95
99
  "unsw"
96
100
  ]
97
101
  }