usearch 2.9.0 → 2.9.2

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 (46) hide show
  1. package/CITATION.cff +1 -1
  2. package/CONTRIBUTING.md +36 -2
  3. package/Cargo.toml +1 -1
  4. package/README.md +12 -15
  5. package/VERSION +1 -1
  6. package/c/lib.cpp +19 -17
  7. package/c/test.c +80 -38
  8. package/c/usearch.h +15 -15
  9. package/conanfile.py +1 -1
  10. package/include/usearch/index.hpp +5 -5
  11. package/include/usearch/index_dense.hpp +8 -4
  12. package/include/usearch/index_plugins.hpp +52 -32
  13. package/javascript/types/usearch.d.ts.map +1 -1
  14. package/javascript/usearch.js +4 -1
  15. package/package.json +3 -2
  16. package/simsimd/CONTRIBUTING.md +88 -0
  17. package/simsimd/Cargo.lock +4 -3
  18. package/simsimd/Cargo.toml +10 -2
  19. package/simsimd/README.md +102 -60
  20. package/simsimd/include/simsimd/binary.h +44 -0
  21. package/simsimd/include/simsimd/complex.h +174 -0
  22. package/simsimd/include/simsimd/simsimd.h +18 -9
  23. package/simsimd/include/simsimd/spatial.h +128 -36
  24. package/simsimd/include/simsimd/types.h +3 -2
  25. package/simsimd/package-lock.json +5 -5
  26. package/simsimd/package.json +1 -1
  27. package/simsimd/rust/benches/native.rs +12 -9
  28. package/simsimd/rust/lib.c +32 -55
  29. package/simsimd/rust/lib.rs +374 -56
  30. package/stringzilla/CONTRIBUTING.md +17 -0
  31. package/stringzilla/Cargo.lock +1 -1
  32. package/stringzilla/Cargo.toml +1 -1
  33. package/stringzilla/README.md +175 -34
  34. package/stringzilla/VERSION +1 -1
  35. package/stringzilla/build.rs +77 -4
  36. package/stringzilla/c/lib.c +33 -0
  37. package/stringzilla/cli/README.md +30 -13
  38. package/stringzilla/cli/__init__.py +0 -0
  39. package/stringzilla/cli/split.py +66 -33
  40. package/stringzilla/cli/wc.py +101 -21
  41. package/stringzilla/include/stringzilla/experimental.h +122 -1
  42. package/stringzilla/include/stringzilla/stringzilla.h +262 -132
  43. package/stringzilla/include/stringzilla/stringzilla.hpp +13 -5
  44. package/stringzilla/package.json +1 -1
  45. package/stringzilla/rust/lib.rs +673 -162
  46. package/wasmer.toml +1 -1
@@ -926,7 +926,7 @@ class i8_converted_t {
926
926
  std::int8_t int8_{};
927
927
 
928
928
  public:
929
- constexpr static float divisor_k = 100.f;
929
+ constexpr static f32_t divisor_k = 100.f;
930
930
  constexpr static std::int8_t min_k = -100;
931
931
  constexpr static std::int8_t max_k = 100;
932
932
 
@@ -938,9 +938,9 @@ class i8_converted_t {
938
938
  inline i8_converted_t(i8_converted_t const&) = default;
939
939
  inline i8_converted_t& operator=(i8_converted_t const&) = default;
940
940
 
941
- inline operator float() const noexcept { return float(int8_) / divisor_k; }
942
- inline operator f16_t() const noexcept { return float(int8_) / divisor_k; }
943
- inline operator double() const noexcept { return double(int8_) / divisor_k; }
941
+ inline operator f16_t() const noexcept { return static_cast<f16_t>(f32_t(int8_) / divisor_k); }
942
+ inline operator f32_t() const noexcept { return f32_t(int8_) / divisor_k; }
943
+ inline operator f64_t() const noexcept { return f64_t(int8_) / divisor_k; }
944
944
  inline explicit operator bool() const noexcept { return int8_ > (max_k / 2); }
945
945
  inline explicit operator std::int8_t() const noexcept { return int8_; }
946
946
  inline explicit operator std::int16_t() const noexcept { return int8_; }
@@ -949,9 +949,9 @@ class i8_converted_t {
949
949
 
950
950
  inline i8_converted_t(f16_t v)
951
951
  : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
952
- inline i8_converted_t(float v)
952
+ inline i8_converted_t(f32_t v)
953
953
  : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
954
- inline i8_converted_t(double v)
954
+ inline i8_converted_t(f64_t v)
955
955
  : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
956
956
  };
957
957
 
@@ -1006,10 +1006,11 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct met
1006
1006
  #elif defined(USEARCH_DEFINED_GCC)
1007
1007
  #pragma GCC ivdep
1008
1008
  #endif
1009
- for (std::size_t i = 0; i != dim; ++i)
1010
- ab += result_t(a[i]) * result_t(b[i]), //
1011
- a2 += square<result_t>(a[i]), //
1012
- b2 += square<result_t>(b[i]);
1009
+ for (std::size_t i = 0; i != dim; ++i) {
1010
+ result_t ai = static_cast<result_t>(a[i]);
1011
+ result_t bi = static_cast<result_t>(b[i]);
1012
+ ab += ai * bi, a2 += square(ai), b2 += square(bi);
1013
+ }
1013
1014
 
1014
1015
  result_t result_if_zero[2][2];
1015
1016
  result_if_zero[0][0] = 1 - ab / (std::sqrt(a2) * std::sqrt(b2));
@@ -1036,8 +1037,11 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct met
1036
1037
  #elif defined(USEARCH_DEFINED_GCC)
1037
1038
  #pragma GCC ivdep
1038
1039
  #endif
1039
- for (std::size_t i = 0; i != dim; ++i)
1040
- ab_deltas_sq += square(result_t(a[i]) - result_t(b[i]));
1040
+ for (std::size_t i = 0; i != dim; ++i) {
1041
+ result_t ai = static_cast<result_t>(a[i]);
1042
+ result_t bi = static_cast<result_t>(b[i]);
1043
+ ab_deltas_sq += square(ai - bi);
1044
+ }
1041
1045
  return ab_deltas_sq;
1042
1046
  }
1043
1047
  };
@@ -1095,9 +1099,10 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
1095
1099
  #elif defined(USEARCH_DEFINED_GCC)
1096
1100
  #pragma GCC ivdep
1097
1101
  #endif
1098
- for (std::size_t i = 0; i != words; ++i)
1099
- and_count += std::bitset<bits_per_word_k>(a[i] & b[i]).count(),
1100
- or_count += std::bitset<bits_per_word_k>(a[i] | b[i]).count();
1102
+ for (std::size_t i = 0; i != words; ++i) {
1103
+ and_count += std::bitset<bits_per_word_k>(a[i] & b[i]).count();
1104
+ or_count += std::bitset<bits_per_word_k>(a[i] | b[i]).count();
1105
+ }
1101
1106
  return 1 - result_t(and_count) / or_count;
1102
1107
  }
1103
1108
  };
@@ -1126,9 +1131,10 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
1126
1131
  #elif defined(USEARCH_DEFINED_GCC)
1127
1132
  #pragma GCC ivdep
1128
1133
  #endif
1129
- for (std::size_t i = 0; i != words; ++i)
1130
- and_count += std::bitset<bits_per_word_k>(a[i] & b[i]).count(),
1131
- any_count += std::bitset<bits_per_word_k>(a[i]).count() + std::bitset<bits_per_word_k>(b[i]).count();
1134
+ for (std::size_t i = 0; i != words; ++i) {
1135
+ and_count += std::bitset<bits_per_word_k>(a[i] & b[i]).count();
1136
+ any_count += std::bitset<bits_per_word_k>(a[i]).count() + std::bitset<bits_per_word_k>(b[i]).count();
1137
+ }
1132
1138
  return 1 - 2 * result_t(and_count) / any_count;
1133
1139
  }
1134
1140
  };
@@ -1159,13 +1165,20 @@ template <typename scalar_at = std::int32_t, typename result_at = float> struct
1159
1165
  };
1160
1166
 
1161
1167
  /**
1162
- * @brief Measures Pearson Correlation between two sequences.
1168
+ * @brief Measures Pearson Correlation between two sequences in a single pass.
1163
1169
  */
1164
1170
  template <typename scalar_at = float, typename result_at = float> struct metric_pearson_gt {
1165
1171
  using scalar_t = scalar_at;
1166
1172
  using result_t = result_at;
1167
1173
 
1168
1174
  inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
1175
+ // The correlation coefficient can't be defined for one or zero-dimensional data.
1176
+ if (dim <= 1)
1177
+ return 0;
1178
+ // Conventional Pearson Correlation Coefficient definiton subtracts the mean value of each
1179
+ // sequence from each element, before dividing them. WikiPedia article suggests a convenient
1180
+ // single-pass algorithm for calculating sample correlations, though depending on the numbers
1181
+ // involved, it can sometimes be numerically unstable.
1169
1182
  result_t a_sum{}, b_sum{}, ab_sum{};
1170
1183
  result_t a_sq_sum{}, b_sq_sum{};
1171
1184
  #if USEARCH_USE_OPENMP
@@ -1176,15 +1189,20 @@ template <typename scalar_at = float, typename result_at = float> struct metric_
1176
1189
  #pragma GCC ivdep
1177
1190
  #endif
1178
1191
  for (std::size_t i = 0; i != dim; ++i) {
1179
- a_sum += result_t(a[i]);
1180
- b_sum += result_t(b[i]);
1181
- ab_sum += result_t(a[i]) * result_t(b[i]);
1182
- a_sq_sum += result_t(a[i]) * result_t(a[i]);
1183
- b_sq_sum += result_t(b[i]) * result_t(b[i]);
1192
+ result_t ai = static_cast<result_t>(a[i]);
1193
+ result_t bi = static_cast<result_t>(b[i]);
1194
+ a_sum += ai;
1195
+ b_sum += bi;
1196
+ ab_sum += ai * bi;
1197
+ a_sq_sum += ai * ai;
1198
+ b_sq_sum += bi * bi;
1184
1199
  }
1185
- result_t denom = std::sqrt((dim * a_sq_sum - a_sum * a_sum) * (dim * b_sq_sum - b_sum * b_sum));
1186
- result_t corr = (dim * ab_sum - a_sum * b_sum) / denom;
1187
- return -corr;
1200
+ result_t denom = (dim * a_sq_sum - a_sum * a_sum) * (dim * b_sq_sum - b_sum * b_sum);
1201
+ if (denom == 0)
1202
+ return 0;
1203
+ result_t corr = dim * ab_sum - a_sum * b_sum;
1204
+ denom = std::sqrt(denom);
1205
+ return -corr / denom;
1188
1206
  }
1189
1207
  };
1190
1208
 
@@ -1197,7 +1215,7 @@ template <typename scalar_at = float, typename result_at = float> struct metric_
1197
1215
 
1198
1216
  inline result_t operator()(scalar_t const* p, scalar_t const* q, std::size_t dim) const noexcept {
1199
1217
  result_t kld_pm{}, kld_qm{};
1200
- scalar_t epsilon = std::numeric_limits<scalar_t>::epsilon();
1218
+ result_t epsilon = std::numeric_limits<result_t>::epsilon();
1201
1219
  #if USEARCH_USE_OPENMP
1202
1220
  #pragma omp simd reduction(+ : kld_pm, kld_qm)
1203
1221
  #elif defined(USEARCH_DEFINED_CLANG)
@@ -1206,9 +1224,11 @@ template <typename scalar_at = float, typename result_at = float> struct metric_
1206
1224
  #pragma GCC ivdep
1207
1225
  #endif
1208
1226
  for (std::size_t i = 0; i != dim; ++i) {
1209
- result_t mi = result_t(p[i] + q[i]) / 2 + epsilon;
1210
- kld_pm += p[i] * std::log((p[i] + epsilon) / mi);
1211
- kld_qm += q[i] * std::log((q[i] + epsilon) / mi);
1227
+ result_t pi = static_cast<result_t>(p[i]);
1228
+ result_t qi = static_cast<result_t>(q[i]);
1229
+ result_t mi = (pi + qi) / 2 + epsilon;
1230
+ kld_pm += pi * std::log((pi + epsilon) / mi);
1231
+ kld_qm += qi * std::log((qi + epsilon) / mi);
1212
1232
  }
1213
1233
  return (kld_pm + kld_qm) / 2;
1214
1234
  }
@@ -1253,7 +1273,7 @@ struct l2sq_i8_t {
1253
1273
  #endif
1254
1274
  for (std::size_t i = 0; i != dim; i++)
1255
1275
  ab_deltas_sq += square(std::int16_t(a[i]) - std::int16_t(b[i]));
1256
- return ab_deltas_sq;
1276
+ return static_cast<result_t>(ab_deltas_sq);
1257
1277
  }
1258
1278
  };
1259
1279
 
@@ -1 +1 @@
1
- {"version":3,"file":"usearch.d.ts","sourceRoot":"","sources":["../usearch.js"],"names":[],"mappings":"AAsIA;IAEI;;;;;;;;;;;OAWG;IACH;oBATkC,MAAM;gBAAU,UAAU;sBAAiC,UAAU;sBAAiC,MAAM;uBAAqB,MAAM;0BAAwB,MAAM;eAAa,OAAO;gBAChN,UAAU,iBACV,UAAU,iBACV,MAAM,kBACN,MAAM,qBACN,MAAM,UACN,OAAO,EAgCjB;IADG,oBAAwI;IAG5I;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,UAPW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,WAE9B,YAAY,GAAC,YAAY,GAAC,SAAS,QAoB7C;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,gBANW,YAAY,GAAC,YAAY,GAAC,SAAS,GAAC,MAAM,MAAM,MAAM,CAAC,CAAC,KACxD,MAAM,GACL,OAAO,GAAC,YAAY,CAqB/B;IAED;;;;;;;;;OASG;IACH,eAJW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,GAC7B,OAAO,GAAC,OAAO,EAAE,CAU5B;IAED;;;;;;OAMG;IACH,YAJW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,GAC7B,MAAM,GAAC,MAAM,EAAE,CAU1B;IAED;;;;;;;;;OASG;IACH,aAJW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,GAC7B,MAAM,GAAC,MAAM,EAAE,CAU1B;IAED;;;OAGG;IACH,cAFY,MAAM,CAEsC;IAExD;;;OAGG;IACH,gBAFY,MAAM,CAE0C;IAE5D;;;OAGG;IACH,QAFY,MAAM,CAE0B;IAE5C;;;OAGG;IACH,YAFY,MAAM,CAEkC;IAEpD;;;;OAIG;IACH,WAHW,MAAM,QAMhB;IAED;;;;OAIG;IACH,WAHW,MAAM,QAMhB;IAED;;;;OAIG;IACH,WAHW,MAAM,QAMhB;CACJ;;;;yBAzWS,MAAM;;;;;;;;;;;;;;;;;yBAmBN,MAAM;;;;;;;;;;AAYhB;;GAEG;AACH;IACI;;;;;OAKG;IACH,kBAHW,cAAc,aACd,YAAY,EAKtB;IAFG,qBAAgB;IAChB,wBAA0B;CAEjC;AAED;;GAEG;AACH;IACI;;;;;;;OAOG;IACH,kBALW,cAAc,aACd,YAAY,UACZ,cAAc,KACd,MAAM,EAOhB;IAJG,qBAAgB;IAChB,wBAA0B;IAC1B,uBAAoB;IACpB,UAAU;IAGd;;;;;OAKG;IACH,OAHW,MAAM,GACJ,OAAO,CAQnB;CACJ;AA4RD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qCA1BW,YAAY,GAAC,YAAY,GAAC,SAAS,GAAC,MAAM,MAAM,MAAM,CAAC,CAAC,WACxD,YAAY,GAAC,YAAY,GAAC,SAAS,GAAC,MAAM,MAAM,MAAM,CAAC,CAAC,cACxD,MAAM,SACN,MAAM,UACN,UAAU,GACT,OAAO,GAAC,YAAY,CA0D/B"}
1
+ {"version":3,"file":"usearch.d.ts","sourceRoot":"","sources":["../usearch.js"],"names":[],"mappings":"AAsIA;IAEI;;;;;;;;;;;OAWG;IACH;oBATkC,MAAM;gBAAU,UAAU;sBAAiC,UAAU;sBAAiC,MAAM;uBAAqB,MAAM;0BAAwB,MAAM;eAAa,OAAO;gBAChN,UAAU,iBACV,UAAU,iBACV,MAAM,kBACN,MAAM,qBACN,MAAM,UACN,OAAO,EAmCjB;IADG,oBAAwI;IAG5I;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,UAPW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,WAE9B,YAAY,GAAC,YAAY,GAAC,SAAS,QAoB7C;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,gBANW,YAAY,GAAC,YAAY,GAAC,SAAS,GAAC,MAAM,MAAM,MAAM,CAAC,CAAC,KACxD,MAAM,GACL,OAAO,GAAC,YAAY,CAqB/B;IAED;;;;;;;;;OASG;IACH,eAJW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,GAC7B,OAAO,GAAC,OAAO,EAAE,CAU5B;IAED;;;;;;OAMG;IACH,YAJW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,GAC7B,MAAM,GAAC,MAAM,EAAE,CAU1B;IAED;;;;;;;;;OASG;IACH,aAJW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,GAC7B,MAAM,GAAC,MAAM,EAAE,CAU1B;IAED;;;OAGG;IACH,cAFY,MAAM,CAEsC;IAExD;;;OAGG;IACH,gBAFY,MAAM,CAE0C;IAE5D;;;OAGG;IACH,QAFY,MAAM,CAE0B;IAE5C;;;OAGG;IACH,YAFY,MAAM,CAEkC;IAEpD;;;;OAIG;IACH,WAHW,MAAM,QAMhB;IAED;;;;OAIG;IACH,WAHW,MAAM,QAMhB;IAED;;;;OAIG;IACH,WAHW,MAAM,QAMhB;CACJ;;;;yBA5WS,MAAM;;;;;;;;;;;;;;;;;yBAmBN,MAAM;;;;;;;;;;AAYhB;;GAEG;AACH;IACI;;;;;OAKG;IACH,kBAHW,cAAc,aACd,YAAY,EAKtB;IAFG,qBAAgB;IAChB,wBAA0B;CAEjC;AAED;;GAEG;AACH;IACI;;;;;;;OAOG;IACH,kBALW,cAAc,aACd,YAAY,UACZ,cAAc,KACd,MAAM,EAOhB;IAJG,qBAAgB;IAChB,wBAA0B;IAC1B,uBAAoB;IACpB,UAAU;IAGd;;;;;OAKG;IACH,OAHW,MAAM,GACJ,OAAO,CAQnB;CACJ;AA+RD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qCA1BW,YAAY,GAAC,YAAY,GAAC,SAAS,GAAC,MAAM,MAAM,MAAM,CAAC,CAAC,WACxD,YAAY,GAAC,YAAY,GAAC,SAAS,GAAC,MAAM,MAAM,MAAM,CAAC,CAAC,cACxD,MAAM,SACN,MAAM,UACN,UAAU,GACT,OAAO,GAAC,YAAY,CA0D/B"}
@@ -158,7 +158,10 @@ class Index {
158
158
  throw new Error("Invalid arguments. Expected either individual arguments or a single object argument.");
159
159
  }
160
160
 
161
- if (!Number.isInteger(dimensions) || !Number.isInteger(connectivity) || !Number.isInteger(expansion_add) || !Number.isInteger(expansion_search) || dimensions <= 0 || connectivity < 0 || expansion_add < 0 || expansion_search < 0) {
161
+ if ((typeof dimensions !== 'bigint' && (!Number.isInteger(dimensions) || dimensions <= 0)) ||
162
+ (typeof connectivity !== 'bigint' && (!Number.isInteger(connectivity) || connectivity < 0)) ||
163
+ (typeof expansion_add !== 'bigint' && (!Number.isInteger(expansion_add) || expansion_add < 0)) ||
164
+ (typeof expansion_search !== 'bigint' && (!Number.isInteger(expansion_search) || expansion_search < 0))) {
162
165
  throw new Error("`dimensions`, `connectivity`, `expansion_add`, and `expansion_search` must be non-negative integers, with `dimensions` being positive.");
163
166
  }
164
167
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.9.0",
3
+ "version": "2.9.2",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian (https://ashvardanian.com/)",
6
6
  "license": "Apache 2.0",
@@ -34,7 +34,8 @@
34
34
  "exports": {
35
35
  ".": {
36
36
  "types": "./javascript/types/usearch.d.ts",
37
- "require": "./javascript/usearch.js"
37
+ "require": "./javascript/usearch.js",
38
+ "import": "./javascript/usearch.js"
38
39
  },
39
40
  "./package.json": "./package.json"
40
41
  },
@@ -0,0 +1,88 @@
1
+ # Contributing
2
+
3
+ ## C and C++
4
+
5
+ To rerun experiments utilize the following command:
6
+
7
+ ```sh
8
+ cmake -DCMAKE_BUILD_TYPE=Release -DSIMSIMD_BUILD_BENCHMARKS=1 -B ./build_release
9
+ cmake --build build_release --config Release
10
+ ./build_release/simsimd_bench
11
+ ./build_release/simsimd_bench --benchmark_filter=js
12
+ ```
13
+
14
+ ## Python
15
+
16
+ Testing:
17
+
18
+ ```sh
19
+ pip install -e . # to install the package in editable mode
20
+ pytest python/test.py -s -x -Wd # to run tests
21
+ ```
22
+
23
+ Here, `-s` will output the logs.
24
+ The `-x` will stop on the first failure.
25
+ The `-Wd` will silence overflows and runtime warnings.
26
+
27
+ Benchmarking:
28
+
29
+ ```sh
30
+ pip install numpy scipy scikit-learn # for comparison baselines
31
+ python python/bench.py # to run default benchmarks
32
+ python python/bench.py --n 1000 --ndim 1000000 # batch size and dimensions
33
+ ```
34
+
35
+ Before merging your changes you may want to test your changes against the entire matrix of Python versions USearch supports.
36
+ For that you need the `cibuildwheel`, which is tricky to use on MacOS and Windows, as it would target just the local environment.
37
+ Still, if you have Docker running on any desktop OS, you can use it to build and test the Python bindings for all Python versions for Linux:
38
+
39
+ ```sh
40
+ pip install cibuildwheel
41
+ cibuildwheel
42
+ cibuildwheel --platform linux # works on any OS and builds all Linux backends
43
+ cibuildwheel --platform linux --archs x86_64 # 64-bit x86, the most common on desktop and servers
44
+ cibuildwheel --platform linux --archs aarch64 # 64-bit Arm for mobile devices, Apple M-series, and AWS Graviton
45
+ cibuildwheel --platform linux --archs i686 # 32-bit Linux
46
+ cibuildwheel --platform linux --archs s390x # emulating big-endian IBM Z
47
+ cibuildwheel --platform macos # works only on MacOS
48
+ cibuildwheel --platform windows # works only on Windows
49
+ ```
50
+
51
+ You may need root previligies for multi-architecture builds:
52
+
53
+ ```sh
54
+ sudo $(which cibuildwheel) --platform linux
55
+ ```
56
+
57
+ On Windows and MacOS, to avoid frequent path resolution issues, you may want to use:
58
+
59
+ ```sh
60
+ python -m cibuildwheel --platform windows
61
+ ```
62
+
63
+ ## Rust
64
+
65
+ ```sh
66
+ cargo test
67
+ cargo bench
68
+ open ./target/criterion/report/index.html
69
+ ```
70
+
71
+ ## JavaScript
72
+
73
+ Testing and benchmarking:
74
+
75
+ ```sh
76
+ npm install --dev
77
+ npm test
78
+ npm run bench
79
+ ```
80
+
81
+ ## GoLang
82
+
83
+ ```sh
84
+ cd golang
85
+ go test # To test
86
+ go test -run=^$ -bench=. -benchmem # To benchmark
87
+ ```
88
+
@@ -210,9 +210,9 @@ dependencies = [
210
210
 
211
211
  [[package]]
212
212
  name = "half"
213
- version = "2.3.1"
213
+ version = "2.4.0"
214
214
  source = "registry+https://github.com/rust-lang/crates.io-index"
215
- checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872"
215
+ checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e"
216
216
  dependencies = [
217
217
  "cfg-if",
218
218
  "crunchy",
@@ -496,10 +496,11 @@ dependencies = [
496
496
 
497
497
  [[package]]
498
498
  name = "simsimd"
499
- version = "3.7.7"
499
+ version = "3.8.1"
500
500
  dependencies = [
501
501
  "cc",
502
502
  "criterion",
503
+ "half",
503
504
  "rand",
504
505
  ]
505
506
 
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "simsimd"
3
3
  description = "Fastest SIMD-Accelerated Vector Similarity Functions for x86 and Arm"
4
- version = "3.8.0"
4
+ version = "3.9.0"
5
5
  edition = "2021"
6
6
  license = "Apache-2.0"
7
7
  authors = [
@@ -12,7 +12,13 @@ repository = "https://github.com/ashvardanian/SimSIMD"
12
12
  documentation = "https://docs.rs/simsimd"
13
13
  homepage = "https://ashvardanian.com/posts/simsimd-faster-scipy"
14
14
  keywords = ["simd", "search", "linear-algebra", "vector"]
15
- categories = ["mathematics", "hardware-support", "no-std", "wasm", "external-ffi-bindings"]
15
+ categories = [
16
+ "mathematics",
17
+ "hardware-support",
18
+ "no-std",
19
+ "wasm",
20
+ "external-ffi-bindings",
21
+ ]
16
22
 
17
23
  [lib]
18
24
  name = "simsimd"
@@ -21,6 +27,7 @@ path = "rust/lib.rs"
21
27
  [build-dependencies]
22
28
  cc = "1.0.83"
23
29
 
30
+
24
31
  [[bench]]
25
32
  name = "cosine"
26
33
  harness = false
@@ -34,3 +41,4 @@ path = "rust/benches/sqeuclidean.rs"
34
41
  [dev-dependencies]
35
42
  criterion = { version = "0.5.1" }
36
43
  rand = { version = "0.8.5" }
44
+ half = { version = "2.4.0" }
package/simsimd/README.md CHANGED
@@ -1,20 +1,41 @@
1
1
  # SimSIMD 📏
2
2
 
3
3
  <div>
4
- <a href="https://pepy.tech/project/simsimd"> <img alt="PyPI" src="https://static.pepy.tech/personalized-badge/simsimd?period=total&units=abbreviation&left_color=black&right_color=blue&left_text=SimSIMD%20Python%20installs"> </a>
5
- <a href="https://www.npmjs.com/package/simsimd"> <img alt="npm" src="https://img.shields.io/npm/dy/simsimd?label=npm%20dowloads"> </a>
6
- <a href="https://crates.io/crates/simsimd"> <img alt="rust" src="https://img.shields.io/crates/d/simsimd?logo=rust" /> </a>
4
+ <a href="https://pepy.tech/project/simsimd">
5
+ <img alt="PyPI" src="https://static.pepy.tech/personalized-badge/simsimd?period=total&units=abbreviation&left_color=black&right_color=blue&left_text=SimSIMD%20Python%20installs">
6
+ </a>
7
+ <a href="https://www.npmjs.com/package/simsimd">
8
+ <img alt="npm" src="https://img.shields.io/npm/dy/simsimd?label=npm%20dowloads">
9
+ </a>
10
+ <a href="https://crates.io/crates/simsimd">
11
+ <img alt="rust" src="https://img.shields.io/crates/d/simsimd?logo=rust" />
12
+ </a>
7
13
  <img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/ashvardanian/simsimd">
14
+ <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
15
+ <img alt="GitHub Actions Ubuntu" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=Ubuntu&logo=github&color=blue">
16
+ </a>
17
+ <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
18
+ <img alt="GitHub Actions Windows" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=Windows&logo=windows&color=blue">
19
+ </a>
20
+ <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
21
+ <img alt="GitHub Actions MacOS" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=MacOS&logo=apple&color=blue">
22
+ </a>
23
+ <a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
24
+ <img alt="GitHub Actions CentOS Linux" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=CentOS&logo=centos&color=blue">
25
+ </a>
26
+
8
27
  </div>
9
28
 
29
+
10
30
  ## Hardware-Accelerated Similarity Metrics and Distance Functions
11
31
 
12
32
  - Zero-dependency [header-only C 99](#using-simsimd-in-c) library.
13
- - Bindings for [Python](#using-simsimd-in-python), [Rust](#using-simsimd-in-rust) and [JavaScript](#using-simsimd-in-javascript).
14
- - Targets ARM NEON, SVE, x86 AVX2, AVX-512 (VNNI, FP16) hardware backends.
15
- - Zero-copy compatible with NumPy, PyTorch, TensorFlow, and other tensors.
16
33
  - Handles `f64` double-, `f32` single-, and `f16` half-precision, `i8` integral, and binary vectors.
34
+ - Targets ARM NEON, SVE, x86 AVX2, AVX-512 (VNNI, FP16) hardware backends.
35
+ - Bindings for [Python](#using-simsimd-in-python), [Rust](#using-simsimd-in-rust) and [JavaScript](#using-simsimd-in-javascript).
17
36
  - __Up to 200x faster__ than [`scipy.spatial.distance`][scipy] and [`numpy.inner`][numpy].
37
+ - [Supports more Python versions][compatibility] than SciPy and NumPy.
38
+ - Zero-copy compatible with NumPy, PyTorch, TensorFlow, and other tensors.
18
39
  - Used in [USearch](https://github.com/unum-cloud/usearch) and several DBMS products.
19
40
 
20
41
  __Implemented distance functions__ include:
@@ -25,6 +46,7 @@ __Implemented distance functions__ include:
25
46
 
26
47
  [scipy]: https://docs.scipy.org/doc/scipy/reference/spatial.distance.html#module-scipy.spatial.distance
27
48
  [numpy]: https://numpy.org/doc/stable/reference/generated/numpy.inner.html
49
+ [compatibility]: https://pypi.org/project/simsimd/#files
28
50
 
29
51
  __Technical Insights__ and related articles:
30
52
 
@@ -67,6 +89,11 @@ __Broader Benchmarking Results__:
67
89
 
68
90
  ## Using SimSIMD in Python
69
91
 
92
+ The package is intended to replace the usage of `numpy.inner`, `numpy.dot`, and `scipy.spatial.distance`.
93
+ Aside from drastic performance improvements, SimSIMD significantly improves accuracy in mixed precision setups.
94
+ NumPy and SciPy, processing `i8` or `f16` vectors, will use the same types for accumulators, while SimSIMD can combine `i8` enumeration, `i16` multiplication, and `i32` accumulation to entirely avoid overflows.
95
+ The same applies to processing `f16` values with `f32` precision.
96
+
70
97
  ### Installation
71
98
 
72
99
  ```sh
@@ -85,6 +112,14 @@ dist = simsimd.cosine(vec1, vec2)
85
112
  ```
86
113
 
87
114
  Supported functions include `cosine`, `inner`, `sqeuclidean`, `hamming`, and `jaccard`.
115
+ Unlike SciPy, SimSIMD allows explicitly stating the precision of the input vectors, which is especially useful for mixed-precision setups.
116
+
117
+ ```py
118
+ dist = simsimd.cosine(vec1, vec2, "i8")
119
+ dist = simsimd.cosine(vec1, vec2, "f16")
120
+ dist = simsimd.cosine(vec1, vec2, "f32")
121
+ dist = simsimd.cosine(vec1, vec2, "f64")
122
+ ```
88
123
 
89
124
  ### Distance Between 2 Batches
90
125
 
@@ -150,20 +185,73 @@ To install, add the following to your `Cargo.toml`:
150
185
  simsimd = "..."
151
186
  ```
152
187
 
153
- To use it:
188
+ Before using the SimSIMD library, ensure you have imported the necessary traits and types into your Rust source file.
189
+ The library provides several traits for different distance/similarity kinds - `SpatialSimilarity`, `BinarySimilarity`, and `ProbabilitySimilarity`.
154
190
 
155
191
  ```rust
156
- use simsimd::{cosine, sqeuclidean};
192
+ use simsimd::SpatialSimilarity;
157
193
 
158
194
  fn main() {
159
- let vector_a = vec![1.0, 2.0, 3.0];
160
- let vector_b = vec![4.0, 5.0, 6.0];
195
+ let vector_a: Vec<f32> = vec![1.0, 2.0, 3.0];
196
+ let vector_b: Vec<f32> = vec![4.0, 5.0, 6.0];
161
197
 
162
- let distance = cosine(&vector_a, &vector_b);
163
- println!("Cosine Distance: {}", distance);
198
+ // Compute the cosine similarity between vector_a and vector_b
199
+ let cosine_similarity = f32::cosine(&vector_a, &vector_b)
200
+ .expect("Vectors must be of the same length");
164
201
 
165
- let distance = sqeuclidean(&vector_a, &vector_b);
166
- println!("Squared Euclidean Distance: {}", distance);
202
+ println!("Cosine Similarity: {}", cosine_similarity);
203
+
204
+ // Compute the squared Euclidean distance between vector_a and vector_b
205
+ let sq_euclidean_distance = f32::sqeuclidean(&vector_a, &vector_b)
206
+ .expect("Vectors must be of the same length");
207
+
208
+ println!("Squared Euclidean Distance: {}", sq_euclidean_distance);
209
+ }
210
+ ```
211
+
212
+ Similarly, one can compute bit-level distance functions between slices of unsigned integers:
213
+
214
+ ```rust
215
+ use simsimd::BinarySimilarity;
216
+
217
+ fn main() {
218
+ let vector_a = &[0b11110000, 0b00001111, 0b10101010];
219
+ let vector_b = &[0b11110000, 0b00001111, 0b01010101];
220
+
221
+ // Compute the Hamming distance between vector_a and vector_b
222
+ let hamming_distance = u8::hamming(&vector_a, &vector_b)
223
+ .expect("Vectors must be of the same length");
224
+
225
+ println!("Hamming Distance: {}", hamming_distance);
226
+
227
+ // Compute the Jaccard distance between vector_a and vector_b
228
+ let jaccard_distance = u8::jaccard(&vector_a, &vector_b)
229
+ .expect("Vectors must be of the same length");
230
+
231
+ println!("Jaccard Distance: {}", jaccard_distance);
232
+ }
233
+ ```
234
+
235
+ Rust has no native support for half-precision floating-point numbers, but SimSIMD provides a `f16` type for this purpose.
236
+ It doesn't have any functionality and is a `transparent` wrapper around `u16`, so it can be used with `half`, or any other half-precision library.
237
+
238
+ ```rust
239
+ use simsimd::SpatialSimilarity;
240
+ use simsimd::f16 as SimF16;
241
+ use half::f16 as HalfF16;
242
+
243
+ fn main() {
244
+ let vector_a: Vec<HalfF16> = ...
245
+ let vector_b: Vec<HalfF16> = ...
246
+
247
+ let buffer_a: &[SimF16] = unsafe { std::slice::from_raw_parts(a_half.as_ptr() as *const SimF16, a_half.len()) };
248
+ let buffer_b: &[SimF16] = unsafe { std::slice::from_raw_parts(b_half.as_ptr() as *const SimF16, b_half.len()) };
249
+
250
+ // Compute the cosine similarity between vector_a and vector_b
251
+ let cosine_similarity = SimF16::cosine(&vector_a, &vector_b)
252
+ .expect("Vectors must be of the same length");
253
+
254
+ println!("Cosine Similarity: {}", cosine_similarity);
167
255
  }
168
256
  ```
169
257
 
@@ -227,49 +315,3 @@ All of the functions names follow the same pattern: `simsimd_{backend}_{type}_{m
227
315
  - The metric can be `cos`, `ip`, `l2sq`, `hamming`, `jaccard`, `kl`, or `js`.
228
316
 
229
317
  In case you want to avoid hard-coding the backend, you can use the `simsimd_metric_punned_t` to pun the function pointer, and `simsimd_capabilities` function to get the available backends at runtime.
230
-
231
- ## Benchmarking and Contributing
232
-
233
- __To rerun experiments__ utilize the following command:
234
-
235
- ```sh
236
- cmake -DCMAKE_BUILD_TYPE=Release -DSIMSIMD_BUILD_BENCHMARKS=1 -B ./build_release
237
- cmake --build build_release --config Release
238
- ./build_release/simsimd_bench
239
- ./build_release/simsimd_bench --benchmark_filter=js
240
- ```
241
-
242
- __To test and benchmark with Python bindings__:
243
-
244
- ```sh
245
- pip install -e .
246
- pytest python/test.py -s -x
247
-
248
- pip install numpy scipy scikit-learn # for comparison baselines
249
- python python/bench.py # to run default benchmarks
250
- python python/bench.py --n 1000 --ndim 1000000 # batch size and dimensions
251
- ```
252
-
253
- __To test and benchmark JavaScript bindings__:
254
-
255
- ```sh
256
- npm install --dev
257
- npm test
258
- npm run bench
259
- ```
260
-
261
- __To test and benchmark GoLang bindings__:
262
-
263
- ```sh
264
- cd golang
265
- go test # To test
266
- go test -run=^$ -bench=. -benchmem # To benchmark
267
- ```
268
-
269
- __To test and benchmark Rust bindings__:
270
-
271
- ```sh
272
- cargo test
273
- cargo bench
274
- open ./target/criterion/report/index.html
275
- ```
@@ -190,6 +190,50 @@ simsimd_avx512_b8_jaccard_cycle:
190
190
  }
191
191
 
192
192
  #endif // SIMSIMD_TARGET_X86_AVX512
193
+
194
+ /*
195
+ * @file x86_avx2_b8.h
196
+ * @brief x86 AVX2 implementation of the most common similarity metrics for bitsets.
197
+ * @author Ash Vardanian
198
+ *
199
+ * - Implements: Hamming and Jaccard similarity.
200
+ * - Requires compiler capabilities: avx2.
201
+ *
202
+ * AVX2 provides no built-in functionality for popcount, so we use the BMI2 instruction set.
203
+ * Wojciech Mu la, Nathan Kurz and Daniel Lemire propose an alternative solution based on Harley-Seal
204
+ * transforms and lookup tables. Those are very fast on large vectors, but on short vectors they lose
205
+ * to built-in scalar population counts. https://arxiv.org/pdf/1611.07612.pdf
206
+ */
207
+
208
+ #if SIMSIMD_TARGET_X86_AVX2
209
+
210
+ __attribute__((target("popcnt"))) //
211
+ inline static simsimd_f32_t
212
+ simsimd_avx2_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
213
+ // x86 supports unaligned loads and works just fine with the scalar version for small vectors.
214
+ simsimd_size_t differences = 0;
215
+ for (; n_words >= 8; n_words -= 8, a += 8, b += 8)
216
+ differences += _mm_popcnt_u64(*(simsimd_u64_t const*)a ^ *(simsimd_u64_t const*)b);
217
+ for (; n_words; --n_words, ++a, ++b)
218
+ differences += _mm_popcnt_u32(*a ^ *b);
219
+ return (simsimd_f32_t)differences;
220
+ }
221
+
222
+ __attribute__((target("popcnt"))) //
223
+ inline static simsimd_f32_t
224
+ simsimd_avx2_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
225
+ // x86 supports unaligned loads and works just fine with the scalar version for small vectors.
226
+ simsimd_size_t intersection = 0, union_ = 0;
227
+ for (; n_words >= 8; n_words -= 8, a += 8, b += 8)
228
+ intersection += _mm_popcnt_u64(*(simsimd_u64_t const*)a & *(simsimd_u64_t const*)b),
229
+ union_ += _mm_popcnt_u64(*(simsimd_u64_t const*)a | *(simsimd_u64_t const*)b);
230
+ for (; n_words; --n_words, ++a, ++b)
231
+ intersection += _mm_popcnt_u32(*a & *b), union_ += _mm_popcnt_u32(*a | *b);
232
+ return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
233
+ }
234
+
235
+ #endif // SIMSIMD_TARGET_X86_AVX2
236
+
193
237
  #endif // SIMSIMD_TARGET_X86
194
238
 
195
239
  #undef popcount64