usearch 2.8.15 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +1 -1
  2. package/.github/workflows/prerelease.yml +37 -11
  3. package/.github/workflows/release.yml +30 -28
  4. package/.gitmodules +4 -1
  5. package/CITATION.cff +1 -1
  6. package/CMakeLists.txt +6 -8
  7. package/CONTRIBUTING.md +20 -1
  8. package/Cargo.toml +1 -1
  9. package/README.md +8 -7
  10. package/VERSION +1 -1
  11. package/conanfile.py +1 -1
  12. package/cpp/test.cpp +9 -2
  13. package/include/usearch/index.hpp +11 -11
  14. package/include/usearch/index_plugins.hpp +4 -2
  15. package/javascript/usearch.js +1 -1
  16. package/package.json +1 -1
  17. package/pyproject.toml +2 -2
  18. package/setup.py +10 -4
  19. package/simsimd/.releaserc +3 -2
  20. package/simsimd/Cargo.lock +708 -0
  21. package/simsimd/Cargo.toml +36 -0
  22. package/simsimd/README.md +91 -25
  23. package/simsimd/binding.gyp +18 -0
  24. package/simsimd/build.rs +20 -0
  25. package/simsimd/include/simsimd/probability.h +5 -5
  26. package/simsimd/include/simsimd/simsimd.h +9 -5
  27. package/simsimd/include/simsimd/spatial.h +20 -21
  28. package/simsimd/include/simsimd/types.h +5 -1
  29. package/simsimd/javascript/bench.js +20 -10
  30. package/simsimd/javascript/dist-package-cjs.json +3 -0
  31. package/simsimd/javascript/dist-package-esm.json +3 -0
  32. package/simsimd/javascript/fallback.ts +193 -0
  33. package/simsimd/javascript/node-gyp-build.d.ts +1 -0
  34. package/simsimd/javascript/simsimd.ts +124 -0
  35. package/simsimd/javascript/test.mjs +153 -0
  36. package/simsimd/javascript/tsconfig-base.json +25 -0
  37. package/simsimd/javascript/tsconfig-cjs.json +8 -0
  38. package/simsimd/javascript/tsconfig-esm.json +8 -0
  39. package/simsimd/package-lock.json +1688 -0
  40. package/simsimd/package.json +26 -11
  41. package/simsimd/rust/benches/cosine.rs +27 -0
  42. package/simsimd/rust/benches/native.rs +27 -0
  43. package/simsimd/rust/benches/sqeuclidean.rs +27 -0
  44. package/simsimd/rust/lib.c +68 -0
  45. package/simsimd/rust/lib.rs +232 -0
  46. package/sqlite/README.md +178 -0
  47. package/sqlite/lib.cpp +325 -0
  48. package/stringzilla/CONTRIBUTING.md +429 -0
  49. package/stringzilla/Cargo.lock +25 -0
  50. package/stringzilla/Cargo.toml +26 -0
  51. package/stringzilla/LICENSE +201 -0
  52. package/stringzilla/README.md +1281 -0
  53. package/stringzilla/VERSION +1 -0
  54. package/stringzilla/assets/cover-strinzilla.jpeg +0 -0
  55. package/stringzilla/assets/meme-stringzilla-v2.jpeg +0 -0
  56. package/stringzilla/assets/meme-stringzilla-v3.jpeg +0 -0
  57. package/stringzilla/binding.gyp +16 -0
  58. package/stringzilla/build.rs +14 -0
  59. package/stringzilla/c/lib.c +304 -0
  60. package/stringzilla/cli/README.md +48 -0
  61. package/stringzilla/cli/split.py +72 -0
  62. package/stringzilla/cli/wc.py +37 -0
  63. package/stringzilla/include/stringzilla/experimental.h +724 -0
  64. package/stringzilla/include/stringzilla/module.modulemap +9 -0
  65. package/stringzilla/include/stringzilla/stringzilla.h +5240 -0
  66. package/stringzilla/include/stringzilla/stringzilla.hpp +3830 -0
  67. package/stringzilla/javascript/lib.c +125 -0
  68. package/stringzilla/javascript/stringzilla.js +22 -0
  69. package/stringzilla/package-ci.json +10 -0
  70. package/stringzilla/package.json +32 -0
  71. package/stringzilla/rust/lib.rs +362 -0
  72. package/stringzilla/swift/StringProtocol+StringZilla.swift +280 -0
  73. package/stringzilla/swift/Test.swift +60 -0
  74. package/wasmer.toml +1 -1
  75. package/simsimd/javascript/simsimd.js +0 -45
  76. package/simsimd/javascript/test/simsimd.test.js +0 -47
package/sqlite/lib.cpp ADDED
@@ -0,0 +1,325 @@
1
+ /**
2
+ * @brief SQLite3 bindings for USearch.
3
+ * @file lib.cpp
4
+ * @author Ash Vardanian
5
+ * @date November 28, 2023
6
+ * @copyright Copyright (c) 2023
7
+ */
8
+
9
+ #include <charconv> // `std::from_chars`
10
+ #include <cstdlib> // `std::strtod`
11
+
12
+ #include <sqlite3ext.h>
13
+ SQLITE_EXTENSION_INIT1
14
+
15
+ #define SZ_DYNAMIC_DISPATCH 1
16
+ #include <stringzilla/stringzilla.hpp>
17
+
18
+ #include <usearch/index_dense.hpp>
19
+ #include <usearch/index_plugins.hpp>
20
+
21
+ using namespace unum::usearch;
22
+ using namespace unum;
23
+
24
+ namespace sz = ashvardanian::stringzilla;
25
+
26
+ using metric_t = metric_punned_t;
27
+ using distance_t = distance_punned_t;
28
+
29
+ template <scalar_kind_t scalar_kind_ak> struct parsed_scalar_kind_gt {
30
+ using type = f32_t;
31
+ static constexpr scalar_kind_t kind = scalar_kind_t::f32_k;
32
+ };
33
+
34
+ template <> struct parsed_scalar_kind_gt<scalar_kind_t::f64_k> {
35
+ using type = f64_t;
36
+ static constexpr scalar_kind_t kind = scalar_kind_t::f64_k;
37
+ };
38
+
39
+ template <scalar_kind_t scalar_kind_ak, metric_kind_t metric_kind_ak>
40
+ static void sqlite_dense(sqlite3_context* context, int argc, sqlite3_value** argv) {
41
+
42
+ if (argc < 2) {
43
+ sqlite3_result_error(context, "Distance function expects at least two arguments", -1);
44
+ return;
45
+ }
46
+
47
+ int type1 = sqlite3_value_type(argv[0]);
48
+ int type2 = sqlite3_value_type(argv[1]);
49
+
50
+ // Our primary case is having two BLOBs containing dense vector representations.
51
+ if (argc == 2 && type1 == SQLITE_BLOB && type2 == SQLITE_BLOB) {
52
+ // The pointer obtained from `sqlite3_value_text` or `sqlite3_value_blob` can
53
+ // be invalidated by the next call to `sqlite3_value_bytes` according to docs, so read it first.
54
+ int bytes1 = sqlite3_value_bytes(argv[0]);
55
+ int bytes2 = sqlite3_value_bytes(argv[1]);
56
+ if (bytes1 != bytes2) {
57
+ sqlite3_result_error(context, "Vectors have different number of dimensions", -1);
58
+ return;
59
+ }
60
+ void const* vec1 = sqlite3_value_blob(argv[0]);
61
+ void const* vec2 = sqlite3_value_blob(argv[1]);
62
+
63
+ std::size_t dimensions = (size_t)(bytes1)*CHAR_BIT / bits_per_scalar(scalar_kind_ak);
64
+ metric_t metric = metric_t(dimensions, metric_kind_ak, scalar_kind_ak);
65
+ distance_punned_t distance =
66
+ metric(reinterpret_cast<byte_t const*>(vec1), reinterpret_cast<byte_t const*>(vec2));
67
+ sqlite3_result_double(context, distance);
68
+ }
69
+
70
+ // Worst case is to have JSON arrays or comma-separated values
71
+ else if (argc == 2 && type1 == SQLITE_TEXT && type2 == SQLITE_TEXT) {
72
+ // The pointer obtained from `sqlite3_value_text` or `sqlite3_value_blob` can
73
+ // be invalidated by the next call to `sqlite3_value_bytes` according to docs, so read it first.
74
+ size_t bytes1 = (size_t)sqlite3_value_bytes(argv[0]);
75
+ size_t bytes2 = (size_t)sqlite3_value_bytes(argv[1]);
76
+ char* vec1 = (char*)sqlite3_value_text(argv[0]);
77
+ char* vec2 = (char*)sqlite3_value_text(argv[1]);
78
+ size_t commas1 = sz::string_view(vec1, bytes1).find_all(",").size();
79
+ size_t commas2 = sz::string_view(vec2, bytes2).find_all(",").size();
80
+ if (commas1 != commas2) {
81
+ sqlite3_result_error(context, "Vectors have different number of dimensions", -1);
82
+ return;
83
+ }
84
+
85
+ // Valid JSON array of numbers would be packed into [] square brackets
86
+ if (bytes1 && vec1[0] == '[')
87
+ ++vec1, --bytes1;
88
+ if (bytes2 && vec2[0] == '[')
89
+ ++vec2, --bytes2;
90
+ // We don't have to trim the end
91
+ // if (bytes1 && vec1[bytes1 - 1] == ']')
92
+ // --bytes1;
93
+ // if (bytes2 && vec2[bytes2 - 1] == ']')
94
+ // --bytes2;
95
+
96
+ // Allocate vectors on stack and parse strings into them
97
+ using scalar_t = typename parsed_scalar_kind_gt<scalar_kind_ak>::type;
98
+ size_t dimensions = commas1 + 1;
99
+ scalar_t parsed1[dimensions], parsed2[dimensions];
100
+ for (size_t i = 0; i != dimensions; ++i) {
101
+ // Skip whitespace
102
+ while (bytes1 && vec1[0] == ' ')
103
+ ++vec1, --bytes1;
104
+ while (bytes2 && vec2[0] == ' ')
105
+ ++vec2, --bytes2;
106
+
107
+ // Parse the floating-point numbers
108
+ // Sadly, most modern compilers don't support the `std::from_chars` yet
109
+ #if __cpp_lib_to_chars
110
+ std::from_chars_result result1 = std::from_chars(vec1, vec1 + bytes1, parsed1[i]);
111
+ std::from_chars_result result2 = std::from_chars(vec2, vec2 + bytes2, parsed2[i]);
112
+ if (result1.ec != std::errc() || result2.ec != std::errc()) {
113
+ sqlite3_result_error(context, "Number can't be parsed", -1);
114
+ return;
115
+ }
116
+ bytes1 -= result1.ptr - vec1;
117
+ bytes2 -= result2.ptr - vec2;
118
+ vec1 = (char*)result1.ptr;
119
+ vec2 = (char*)result2.ptr;
120
+ #else
121
+ char* parsed1_end = vec1 + bytes1;
122
+ parsed1[i] = std::strtod(vec1, &parsed1_end);
123
+ char* parsed2_end = vec2 + bytes2;
124
+ parsed2[i] = std::strtod(vec2, &parsed2_end);
125
+ if (vec1 == parsed1_end || vec2 == parsed2_end) {
126
+ sqlite3_result_error(context, "Number can't be parsed", -1);
127
+ return;
128
+ }
129
+ bytes1 -= parsed1_end - vec1;
130
+ bytes2 -= parsed2_end - vec2;
131
+ vec1 = parsed1_end;
132
+ vec2 = parsed2_end;
133
+ #endif
134
+
135
+ // Skip the whitespaces and commas
136
+ while (bytes1 && (vec1[0] == ' ' || vec1[0] == ','))
137
+ ++vec1, --bytes1;
138
+ while (bytes2 && (vec2[0] == ' ' || vec2[0] == ','))
139
+ ++vec2, --bytes2;
140
+ }
141
+
142
+ // Compute the distance itself
143
+ metric_t metric = metric_t(dimensions, metric_kind_ak, parsed_scalar_kind_gt<scalar_kind_ak>::kind);
144
+ distance_punned_t distance =
145
+ metric(reinterpret_cast<byte_t const*>(parsed1), reinterpret_cast<byte_t const*>(parsed2));
146
+ sqlite3_result_double(context, distance);
147
+ }
148
+
149
+ // Less efficient, yet still common case is to have many scalar columns
150
+ else if (argc % 2 == 0) {
151
+
152
+ // Allocate vectors on stack and parse floating-point values into them
153
+ using scalar_t = typename parsed_scalar_kind_gt<scalar_kind_ak>::type;
154
+ size_t dimensions = argc / 2;
155
+ scalar_t parsed1[dimensions], parsed2[dimensions];
156
+ for (size_t i = 0; i != dimensions; ++i) {
157
+ switch (sqlite3_value_type(argv[i])) {
158
+ case SQLITE_FLOAT: parsed1[i] = sqlite3_value_double(argv[i]); break;
159
+ case SQLITE_INTEGER: parsed1[i] = sqlite3_value_int(argv[i]); break;
160
+ case SQLITE_NULL: parsed1[i] = 0; break;
161
+ default:
162
+ sqlite3_result_error(context, "Scalar columns may only contain 32-bit integers, floats, or NULLs.", -1);
163
+ return;
164
+ }
165
+ switch (sqlite3_value_type(argv[dimensions + i])) {
166
+ case SQLITE_FLOAT: parsed2[i] = sqlite3_value_double(argv[dimensions + i]); break;
167
+ case SQLITE_INTEGER: parsed2[i] = sqlite3_value_int(argv[dimensions + i]); break;
168
+ case SQLITE_NULL: parsed2[i] = 0; break;
169
+ default:
170
+ sqlite3_result_error(context, "Scalar columns may only contain 32-bit integers, floats, or NULLs.", -1);
171
+ return;
172
+ }
173
+ }
174
+
175
+ // Compute the distance itself
176
+ metric_t metric = metric_t(dimensions, metric_kind_ak, parsed_scalar_kind_gt<scalar_kind_ak>::kind);
177
+ distance_punned_t distance =
178
+ metric(reinterpret_cast<byte_t const*>(parsed1), reinterpret_cast<byte_t const*>(parsed2));
179
+ sqlite3_result_double(context, distance);
180
+ }
181
+ // Unsupported arguments combination
182
+ else {
183
+ sqlite3_result_error(context, "Number of columns in two vectors must be divisible by two", -1);
184
+ }
185
+ }
186
+
187
+ enum class string_metric_kind_t : std::uint8_t {
188
+ levenshtein_bytes_k,
189
+ levenshtein_unicode_k,
190
+ hamming_bytes_k,
191
+ hamming_unicode_k,
192
+ };
193
+
194
+ template <string_metric_kind_t string_metric_kind_ak>
195
+ static void sqlite_strings(sqlite3_context* context, int argc, sqlite3_value** argv) {
196
+
197
+ if (argc != 2 && argc != 3) {
198
+ sqlite3_result_error(context, "Distance function expects 2 or 3 arguments", -1);
199
+ return;
200
+ }
201
+
202
+ int type1 = sqlite3_value_type(argv[0]);
203
+ int type2 = sqlite3_value_type(argv[1]);
204
+ auto is_text_or_blob = [](int type) { return type == SQLITE_TEXT || type == SQLITE_BLOB; };
205
+ if (!is_text_or_blob(type1) || !is_text_or_blob(type2)) {
206
+ sqlite3_result_error(context, "Distance function expects text or blob arguments", -1);
207
+ return;
208
+ }
209
+
210
+ sz_size_t bound = 0;
211
+ if (argc == 3) {
212
+ int type3 = sqlite3_value_type(argv[2]);
213
+ if (type3 != SQLITE_INTEGER) {
214
+ sqlite3_result_error(context, "Distance function expects integer as the third argument", -1);
215
+ return;
216
+ }
217
+ std::int64_t signed_bound = sqlite3_value_int64(argv[2]);
218
+ if (signed_bound < 0) {
219
+ sqlite3_result_error(context, "Distance function expects non-negative integer as the third argument", -1);
220
+ return;
221
+ }
222
+ bound = (sz_size_t)signed_bound;
223
+ }
224
+
225
+ // The pointer obtained from `sqlite3_value_text` or `sqlite3_value_blob` can
226
+ // be invalidated by the next call to `sqlite3_value_bytes` according to docs, so read it first.
227
+ sz_size_t bytes1 = (sz_size_t)sqlite3_value_bytes(argv[0]);
228
+ sz_size_t bytes2 = (sz_size_t)sqlite3_value_bytes(argv[1]);
229
+ auto extract_data_pointer = [](int type, sqlite3_value* value) -> sz_cptr_t {
230
+ return type == SQLITE_BLOB ? (sz_cptr_t)sqlite3_value_blob(value) : (sz_cptr_t)sqlite3_value_text(value);
231
+ };
232
+ sz_cptr_t vec1 = extract_data_pointer(type1, argv[0]);
233
+ sz_cptr_t vec2 = extract_data_pointer(type2, argv[1]);
234
+ sz::string_view str1(vec1, bytes1);
235
+ sz::string_view str2(vec2, bytes2);
236
+
237
+ // Dispatch the right backend
238
+ sz_size_t result = SZ_SIZE_MAX;
239
+ switch (string_metric_kind_ak) {
240
+ case string_metric_kind_t::levenshtein_bytes_k: result = sz::edit_distance(str1, str2, bound); break;
241
+ case string_metric_kind_t::levenshtein_unicode_k: result = sz::edit_distance_utf8(str1, str2, bound); break;
242
+ case string_metric_kind_t::hamming_bytes_k: result = sz::hamming_distance(str1, str2, bound); break;
243
+ case string_metric_kind_t::hamming_unicode_k: result = sz::hamming_distance_utf8(str1, str2, bound); break;
244
+ }
245
+
246
+ // Check for errors
247
+ if (result == SZ_SIZE_MAX) {
248
+ sqlite3_result_error(context, "Distance function failed to compute the result", -1);
249
+ return;
250
+ }
251
+
252
+ sqlite3_result_int64(context, (std::int64_t)result);
253
+ }
254
+
255
+ extern "C" SZ_DYNAMIC int sqlite3_compiled_init( //
256
+ sqlite3* db, //
257
+ char** error_message, //
258
+ sqlite3_api_routines const* api) {
259
+ SQLITE_EXTENSION_INIT2(api)
260
+
261
+ int flags = SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS;
262
+ int num_params = -1; // Any number will be accepted
263
+
264
+ // String similarity metrics
265
+ sqlite3_create_function(db, "distance_levenshtein_bytes", num_params, flags, NULL,
266
+ sqlite_strings<string_metric_kind_t::levenshtein_bytes_k>, NULL, NULL);
267
+ sqlite3_create_function(db, "distance_levenshtein_unicode", num_params, flags, NULL,
268
+ sqlite_strings<string_metric_kind_t::levenshtein_unicode_k>, NULL, NULL);
269
+ sqlite3_create_function(db, "distance_hamming_bytes", num_params, flags, NULL,
270
+ sqlite_strings<string_metric_kind_t::hamming_bytes_k>, NULL, NULL);
271
+ sqlite3_create_function(db, "distance_hamming_unicode", num_params, flags, NULL,
272
+ sqlite_strings<string_metric_kind_t::hamming_unicode_k>, NULL, NULL);
273
+
274
+ // Bit-wise metrics
275
+ sqlite3_create_function(db, "distance_hamming_binary", num_params, flags, NULL,
276
+ sqlite_dense<scalar_kind_t::b1x8_k, metric_kind_t::hamming_k>, NULL, NULL);
277
+ sqlite3_create_function(db, "distance_jaccard_binary", num_params, flags, NULL,
278
+ sqlite_dense<scalar_kind_t::b1x8_k, metric_kind_t::jaccard_k>, NULL, NULL);
279
+ sqlite3_create_function(db, "distance_haversine_meters", num_params, flags, NULL,
280
+ sqlite_dense<scalar_kind_t::f64_k, metric_kind_t::haversine_k>, NULL, NULL);
281
+
282
+ // Double-precision metrics
283
+ sqlite3_create_function(db, "distance_sqeuclidean_f64", num_params, flags, NULL,
284
+ sqlite_dense<scalar_kind_t::f64_k, metric_kind_t::l2sq_k>, NULL, NULL);
285
+ sqlite3_create_function(db, "distance_cosine_f64", num_params, flags, NULL,
286
+ sqlite_dense<scalar_kind_t::f64_k, metric_kind_t::cos_k>, NULL, NULL);
287
+ sqlite3_create_function(db, "distance_inner_f64", num_params, flags, NULL,
288
+ sqlite_dense<scalar_kind_t::f64_k, metric_kind_t::ip_k>, NULL, NULL);
289
+ sqlite3_create_function(db, "distance_divergence_f64", num_params, flags, NULL,
290
+ sqlite_dense<scalar_kind_t::f64_k, metric_kind_t::divergence_k>, NULL, NULL);
291
+
292
+ // Single-precision metrics
293
+ sqlite3_create_function(db, "distance_sqeuclidean_f32", num_params, flags, NULL,
294
+ sqlite_dense<scalar_kind_t::f32_k, metric_kind_t::l2sq_k>, NULL, NULL);
295
+ sqlite3_create_function(db, "distance_cosine_f32", num_params, flags, NULL,
296
+ sqlite_dense<scalar_kind_t::f32_k, metric_kind_t::cos_k>, NULL, NULL);
297
+ sqlite3_create_function(db, "distance_inner_f32", num_params, flags, NULL,
298
+ sqlite_dense<scalar_kind_t::f32_k, metric_kind_t::ip_k>, NULL, NULL);
299
+ sqlite3_create_function(db, "distance_divergence_f32", num_params, flags, NULL,
300
+ sqlite_dense<scalar_kind_t::f32_k, metric_kind_t::divergence_k>, NULL, NULL);
301
+
302
+ // Half-precision metrics
303
+ sqlite3_create_function(db, "distance_sqeuclidean_f16", num_params, flags, NULL,
304
+ sqlite_dense<scalar_kind_t::f16_k, metric_kind_t::l2sq_k>, NULL, NULL);
305
+ sqlite3_create_function(db, "distance_cosine_f16", num_params, flags, NULL,
306
+ sqlite_dense<scalar_kind_t::f16_k, metric_kind_t::cos_k>, NULL, NULL);
307
+ sqlite3_create_function(db, "distance_inner_f16", num_params, flags, NULL,
308
+ sqlite_dense<scalar_kind_t::f16_k, metric_kind_t::ip_k>, NULL, NULL);
309
+ sqlite3_create_function(db, "distance_divergence_f16", num_params, flags, NULL,
310
+ sqlite_dense<scalar_kind_t::f16_k, metric_kind_t::divergence_k>, NULL, NULL);
311
+
312
+ // Integer metrics
313
+ sqlite3_create_function(db, "distance_sqeuclidean_i8", num_params, flags, NULL,
314
+ sqlite_dense<scalar_kind_t::i8_k, metric_kind_t::l2sq_k>, NULL, NULL);
315
+ sqlite3_create_function(db, "distance_cosine_i8", num_params, flags, NULL,
316
+ sqlite_dense<scalar_kind_t::i8_k, metric_kind_t::cos_k>, NULL, NULL);
317
+ sqlite3_create_function(db, "distance_inner_i8", num_params, flags, NULL,
318
+ sqlite_dense<scalar_kind_t::i8_k, metric_kind_t::ip_k>, NULL, NULL);
319
+ sqlite3_create_function(db, "distance_divergence_i8", num_params, flags, NULL,
320
+ sqlite_dense<scalar_kind_t::i8_k, metric_kind_t::divergence_k>, NULL, NULL);
321
+
322
+ return SQLITE_OK;
323
+ }
324
+
325
+ #include "../stringzilla/c/lib.c"