usearch 2.15.2 → 2.16.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.
- package/README.md +1 -1
- package/binding.gyp +1 -1
- package/c/CMakeLists.txt +10 -3
- package/cpp/README.md +1 -1
- package/cpp/bench.cpp +2 -2
- package/cpp/test.cpp +16 -0
- package/include/usearch/index.hpp +44 -17
- package/include/usearch/index_dense.hpp +51 -89
- package/include/usearch/index_plugins.hpp +468 -43
- package/javascript/lib.cpp +17 -5
- package/javascript/usearch.test.js +205 -34
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/.git-blame-ignore-revs +2 -0
- package/simsimd/CODE_OF_CONDUCT.md +128 -0
- package/simsimd/CONTRIBUTING.md +84 -26
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +4 -4
- package/simsimd/MANIFEST.in +3 -0
- package/simsimd/Package.swift +15 -3
- package/simsimd/README.md +534 -177
- package/simsimd/binding.gyp +6 -2
- package/simsimd/build.rs +13 -1
- package/simsimd/c/lib.c +266 -53
- package/simsimd/include/simsimd/binary.h +321 -112
- package/simsimd/include/simsimd/curved.h +803 -0
- package/simsimd/include/simsimd/dot.h +694 -521
- package/simsimd/include/simsimd/elementwise.h +1437 -0
- package/simsimd/include/simsimd/geospatial.h +9 -3
- package/simsimd/include/simsimd/mesh.h +69 -0
- package/simsimd/include/simsimd/probability.h +234 -206
- package/simsimd/include/simsimd/simsimd.h +1476 -617
- package/simsimd/include/simsimd/sparse.h +1428 -0
- package/simsimd/include/simsimd/spatial.h +1556 -742
- package/simsimd/include/simsimd/types.h +276 -132
- package/simsimd/javascript/lib.c +1 -2
- package/simsimd/javascript/simsimd.ts +4 -4
- package/simsimd/package.json +4 -4
- package/simsimd/rust/lib.rs +179 -1
- package/simsimd/scripts/bench.cxx +1070 -0
- package/simsimd/scripts/bench.ipynb +187 -0
- package/simsimd/{javascript → scripts}/bench.js +1 -1
- package/simsimd/{rust/benches/sqeuclidean.rs → scripts/bench_sqeuclidean.rs} +6 -6
- package/simsimd/scripts/bench_vectors.py +700 -0
- package/simsimd/scripts/bench_vectors_live.py +213 -0
- package/simsimd/scripts/test.c +155 -0
- package/simsimd/{javascript → scripts}/test.mjs +2 -2
- package/simsimd/scripts/test.py +1410 -0
- package/simsimd/swift/{source/SimSIMD.swift → SimSIMD.swift} +6 -2
- package/simsimd/swift/{tests/SimSIMDTests.swift → Test.swift} +1 -1
- /package/simsimd/{rust/benches/cosine.rs → scripts/bench_cosine.rs} +0 -0
- /package/simsimd/{rust/benches/native.rs → scripts/bench_native.rs} +0 -0
package/javascript/lib.cpp
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
using namespace unum::usearch;
|
|
21
21
|
using namespace unum;
|
|
22
22
|
|
|
23
|
+
using add_result_t = typename index_dense_t::add_result_t;
|
|
24
|
+
|
|
23
25
|
class CompiledIndex : public Napi::ObjectWrap<CompiledIndex> {
|
|
24
26
|
public:
|
|
25
27
|
static Napi::Object Init(Napi::Env env, Napi::Object exports);
|
|
@@ -129,6 +131,8 @@ void CompiledIndex::Load(Napi::CallbackInfo const& ctx) {
|
|
|
129
131
|
auto result = native_->load(path.c_str());
|
|
130
132
|
if (!result)
|
|
131
133
|
Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
|
|
134
|
+
if (!native_->try_reserve(ceil2(native_->size())))
|
|
135
|
+
Napi::Error::New(ctx.Env(), "Failed to reserve memory").ThrowAsJavaScriptException();
|
|
132
136
|
|
|
133
137
|
} catch (...) {
|
|
134
138
|
Napi::TypeError::New(ctx.Env(), "Loading failed").ThrowAsJavaScriptException();
|
|
@@ -142,6 +146,8 @@ void CompiledIndex::View(Napi::CallbackInfo const& ctx) {
|
|
|
142
146
|
auto result = native_->view(path.c_str());
|
|
143
147
|
if (!result)
|
|
144
148
|
Napi::TypeError::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
|
|
149
|
+
if (!native_->try_reserve(ceil2(native_->size())))
|
|
150
|
+
Napi::Error::New(ctx.Env(), "Failed to reserve memory").ThrowAsJavaScriptException();
|
|
145
151
|
|
|
146
152
|
} catch (...) {
|
|
147
153
|
Napi::TypeError::New(ctx.Env(), "Memory-mapping failed").ThrowAsJavaScriptException();
|
|
@@ -161,7 +167,10 @@ void CompiledIndex::Add(Napi::CallbackInfo const& ctx) {
|
|
|
161
167
|
auto run_parallel = [&](auto vectors) {
|
|
162
168
|
executor_stl_t executor;
|
|
163
169
|
executor.fixed(tasks, [&](std::size_t /*thread_idx*/, std::size_t task_idx) {
|
|
164
|
-
native_->add(static_cast<default_key_t>(keys[task_idx]),
|
|
170
|
+
add_result_t result = native_->add(static_cast<default_key_t>(keys[task_idx]),
|
|
171
|
+
vectors + task_idx * native_->dimensions());
|
|
172
|
+
if (!result)
|
|
173
|
+
Napi::Error::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
|
|
165
174
|
});
|
|
166
175
|
};
|
|
167
176
|
|
|
@@ -245,11 +254,14 @@ Napi::Value CompiledIndex::Remove(Napi::CallbackInfo const& ctx) {
|
|
|
245
254
|
Napi::Env env = ctx.Env();
|
|
246
255
|
Napi::BigUint64Array keys = ctx[0].As<Napi::BigUint64Array>();
|
|
247
256
|
std::size_t length = keys.ElementLength();
|
|
248
|
-
Napi::Array
|
|
257
|
+
Napi::Array results = Napi::Array::New(env, length);
|
|
249
258
|
for (std::size_t i = 0; i < length; ++i) {
|
|
250
|
-
result
|
|
259
|
+
auto result = native_->remove(static_cast<default_key_t>(keys[i]));
|
|
260
|
+
if (!result)
|
|
261
|
+
Napi::Error::New(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
|
|
262
|
+
results[i] = Napi::Number::New(env, result.completed);
|
|
251
263
|
}
|
|
252
|
-
return
|
|
264
|
+
return results;
|
|
253
265
|
}
|
|
254
266
|
|
|
255
267
|
Napi::Value CompiledIndex::Contains(Napi::CallbackInfo const& ctx) {
|
|
@@ -268,7 +280,7 @@ Napi::Value CompiledIndex::Count(Napi::CallbackInfo const& ctx) {
|
|
|
268
280
|
std::size_t length = keys.ElementLength();
|
|
269
281
|
Napi::Array result = Napi::Array::New(env, length);
|
|
270
282
|
for (std::size_t i = 0; i < length; ++i)
|
|
271
|
-
result[i] = Napi::
|
|
283
|
+
result[i] = Napi::Number::New(env, native_->count(static_cast<default_key_t>(keys[i])));
|
|
272
284
|
return result;
|
|
273
285
|
}
|
|
274
286
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const test = require('node:test');
|
|
2
2
|
const assert = require('node:assert');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
3
6
|
const usearch = require('./dist/cjs/usearch.js');
|
|
4
7
|
|
|
5
8
|
function assertAlmostEqual(actual, expected, tolerance = 1e-6) {
|
|
@@ -12,38 +15,84 @@ function assertAlmostEqual(actual, expected, tolerance = 1e-6) {
|
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
test('Single-entry operations', () => {
|
|
16
|
-
|
|
18
|
+
test('Single-entry operations', async (t) => {
|
|
19
|
+
await t.test('index info', () => {
|
|
20
|
+
const index = new usearch.Index(2, 'l2sq');
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
assert.equal(index.connectivity(), 16, 'connectivity should be 16');
|
|
23
|
+
assert.equal(index.dimensions(), 2, 'dimensions should be 2');
|
|
24
|
+
assert.equal(index.size(), 0, 'initial size should be 0');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await t.test('add', () => {
|
|
28
|
+
const index = new usearch.Index(2, 'l2sq');
|
|
29
|
+
|
|
30
|
+
index.add(15n, new Float32Array([10, 20]));
|
|
31
|
+
index.add(16n, new Float32Array([10, 25]));
|
|
32
|
+
|
|
33
|
+
assert.equal(index.size(), 2, 'size after adding elements should be 2');
|
|
34
|
+
assert.equal(index.contains(15), true, 'entry must be present after insertion');
|
|
35
|
+
|
|
36
|
+
const results = index.search(new Float32Array([13, 14]), 2);
|
|
37
|
+
|
|
38
|
+
assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
|
|
39
|
+
assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
|
|
40
|
+
});
|
|
21
41
|
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
await t.test('remove', () => {
|
|
43
|
+
const index = new usearch.Index(2, 'l2sq');
|
|
24
44
|
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
index.add(15n, new Float32Array([10, 20]));
|
|
46
|
+
index.add(16n, new Float32Array([10, 25]));
|
|
47
|
+
index.add(25n, new Float32Array([20, 40]));
|
|
48
|
+
index.add(26n, new Float32Array([20, 45]));
|
|
27
49
|
|
|
28
|
-
|
|
50
|
+
assert.equal(index.remove(15n), 1);
|
|
29
51
|
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
assert.equal(index.size(), 3, 'size after remoing elements should be 3');
|
|
53
|
+
assert.equal(index.contains(15), false, 'entry must be absent after insertion');
|
|
54
|
+
|
|
55
|
+
const results = index.search(new Float32Array([13, 14]), 2);
|
|
56
|
+
|
|
57
|
+
assert.deepEqual(results.keys, new BigUint64Array([16n, 25n]), 'keys should not include 15');
|
|
58
|
+
});
|
|
32
59
|
});
|
|
33
60
|
|
|
34
|
-
test('Batch operations', () => {
|
|
35
|
-
|
|
61
|
+
test('Batch operations', async (t) => {
|
|
62
|
+
await t.test('add', () => {
|
|
63
|
+
const indexBatch = new usearch.Index(2, 'l2sq');
|
|
64
|
+
|
|
65
|
+
const keys = [15n, 16n];
|
|
66
|
+
const vectors = [new Float32Array([10, 20]), new Float32Array([10, 25])];
|
|
67
|
+
|
|
68
|
+
indexBatch.add(keys, vectors);
|
|
69
|
+
assert.equal(indexBatch.size(), 2, 'size after adding batch should be 2');
|
|
70
|
+
|
|
71
|
+
const results = indexBatch.search(new Float32Array([13, 14]), 2);
|
|
36
72
|
|
|
37
|
-
|
|
38
|
-
|
|
73
|
+
assert.deepEqual(results.keys, new BigUint64Array([15n, 16n]), 'keys should be 15 and 16');
|
|
74
|
+
assert.deepEqual(results.distances, new Float32Array([45, 130]), 'distances should be 45 and 130');
|
|
75
|
+
});
|
|
39
76
|
|
|
40
|
-
|
|
41
|
-
|
|
77
|
+
await t.test('remove', () => {
|
|
78
|
+
const indexBatch = new usearch.Index(2, 'l2sq');
|
|
42
79
|
|
|
43
|
-
|
|
80
|
+
const keys = [15n, 16n, 25n, 26n];
|
|
81
|
+
const vectors = [
|
|
82
|
+
new Float32Array([10, 20]),
|
|
83
|
+
new Float32Array([10, 25]),
|
|
84
|
+
new Float32Array([20, 40]),
|
|
85
|
+
new Float32Array([20, 45])
|
|
86
|
+
];
|
|
87
|
+
indexBatch.add(keys, vectors);
|
|
44
88
|
|
|
45
|
-
|
|
46
|
-
|
|
89
|
+
assert.deepEqual(indexBatch.remove([15n, 25n]), [1, 1])
|
|
90
|
+
assert.equal(indexBatch.size(), 2, 'size after removing batch should be 2');
|
|
91
|
+
|
|
92
|
+
const results = indexBatch.search(new Float32Array([13, 14]), 2);
|
|
93
|
+
|
|
94
|
+
assert.deepEqual(results.keys, new BigUint64Array([16n, 26n]), 'keys should not include 15 and 25');
|
|
95
|
+
});
|
|
47
96
|
});
|
|
48
97
|
|
|
49
98
|
test("Expected results", () => {
|
|
@@ -60,7 +109,27 @@ test("Expected results", () => {
|
|
|
60
109
|
assertAlmostEqual(results.distances[0], new Float32Array([0]));
|
|
61
110
|
});
|
|
62
111
|
|
|
112
|
+
test('Expected count()', async (t) => {
|
|
113
|
+
const index = new usearch.Index({
|
|
114
|
+
metric: 'l2sq',
|
|
115
|
+
connectivity: 16,
|
|
116
|
+
dimensions: 3,
|
|
117
|
+
});
|
|
118
|
+
index.add(
|
|
119
|
+
[42n, 43n],
|
|
120
|
+
[new Float32Array([0.2, 0.6, 0.4]), new Float32Array([0.2, 0.6, 0.4])]
|
|
121
|
+
);
|
|
63
122
|
|
|
123
|
+
await t.test('Argument is a number', () => {
|
|
124
|
+
assert.equal(1, index.count(43n));
|
|
125
|
+
});
|
|
126
|
+
await t.test('Argument is a number (does not exist)', () => {
|
|
127
|
+
assert.equal(0, index.count(44n));
|
|
128
|
+
});
|
|
129
|
+
await t.test('Argument is an array', () => {
|
|
130
|
+
assert.deepEqual([1, 1, 0], index.count([42n, 43n, 44n]));
|
|
131
|
+
});
|
|
132
|
+
});
|
|
64
133
|
|
|
65
134
|
test('Operations with invalid values', () => {
|
|
66
135
|
const indexBatch = new usearch.Index(2, 'l2sq');
|
|
@@ -68,17 +137,119 @@ test('Operations with invalid values', () => {
|
|
|
68
137
|
const keys = [NaN, 16n];
|
|
69
138
|
const vectors = [new Float32Array([10, 30]), new Float32Array([1, 5])];
|
|
70
139
|
|
|
71
|
-
|
|
72
|
-
indexBatch.add(keys, vectors)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
140
|
+
assert.throws(
|
|
141
|
+
() => indexBatch.add(keys, vectors),
|
|
142
|
+
{
|
|
143
|
+
name: 'Error',
|
|
144
|
+
message: 'All keys must be positive integers or bigints.'
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
assert.throws(
|
|
149
|
+
() => indexBatch.search(NaN, 2),
|
|
150
|
+
{
|
|
151
|
+
name: 'Error',
|
|
152
|
+
message: 'Vectors must be a TypedArray or an array of arrays.'
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('Invalid operations', async (t) => {
|
|
158
|
+
await t.test('Add the same keys', () => {
|
|
159
|
+
const index = new usearch.Index({
|
|
160
|
+
metric: "l2sq",
|
|
161
|
+
connectivity: 16,
|
|
162
|
+
dimensions: 3,
|
|
163
|
+
});
|
|
164
|
+
index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
|
|
165
|
+
assert.throws(
|
|
166
|
+
() => index.add(42n, new Float32Array([0.2, 0.6, 0.4])),
|
|
167
|
+
{
|
|
168
|
+
name: 'Error',
|
|
169
|
+
message: 'Duplicate keys not allowed in high-level wrappers'
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
test('Serialization', async (t) => {
|
|
177
|
+
const indexPath = path.join(os.tmpdir(), 'usearch.test.index')
|
|
178
|
+
|
|
179
|
+
t.beforeEach(() => {
|
|
180
|
+
const index = new usearch.Index({
|
|
181
|
+
metric: "l2sq",
|
|
182
|
+
connectivity: 16,
|
|
183
|
+
dimensions: 3,
|
|
184
|
+
});
|
|
185
|
+
index.add(42n, new Float32Array([0.2, 0.6, 0.4]));
|
|
186
|
+
index.save(indexPath);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
t.afterEach(() => {
|
|
190
|
+
fs.unlinkSync(indexPath);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await t.test('load', () => {
|
|
194
|
+
const index = new usearch.Index({
|
|
195
|
+
metric: "l2sq",
|
|
196
|
+
connectivity: 16,
|
|
197
|
+
dimensions: 3,
|
|
198
|
+
});
|
|
199
|
+
index.load(indexPath);
|
|
200
|
+
const results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10);
|
|
201
|
+
|
|
202
|
+
assert.equal(index.size(), 1);
|
|
203
|
+
assert.deepEqual(results.keys, new BigUint64Array([42n]));
|
|
204
|
+
assertAlmostEqual(results.distances[0], new Float32Array([0]));
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// todo: Skip as the test fails only on windows.
|
|
208
|
+
// The following error in afterEach().
|
|
209
|
+
// `error: "EBUSY: resource busy or locked, unlink`
|
|
210
|
+
await t.test('view: Read data', {skip: process.platform === 'win32'}, () => {
|
|
211
|
+
const index = new usearch.Index({
|
|
212
|
+
metric: "l2sq",
|
|
213
|
+
connectivity: 16,
|
|
214
|
+
dimensions: 3,
|
|
215
|
+
});
|
|
216
|
+
index.view(indexPath);
|
|
217
|
+
const results = index.search(new Float32Array([0.2, 0.6, 0.4]), 10);
|
|
218
|
+
|
|
219
|
+
assert.equal(index.size(), 1);
|
|
220
|
+
assert.deepEqual(results.keys, new BigUint64Array([42n]));
|
|
221
|
+
assertAlmostEqual(results.distances[0], new Float32Array([0]));
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await t.test('view: Invalid operations: add', {skip: process.platform === 'win32'}, () => {
|
|
225
|
+
const index = new usearch.Index({
|
|
226
|
+
metric: "l2sq",
|
|
227
|
+
connectivity: 16,
|
|
228
|
+
dimensions: 3,
|
|
229
|
+
});
|
|
230
|
+
index.view(indexPath);
|
|
231
|
+
assert.throws(
|
|
232
|
+
() => index.add(43n, new Float32Array([0.2, 0.6, 0.4])),
|
|
233
|
+
{
|
|
234
|
+
name: 'Error',
|
|
235
|
+
message: "Can't add to an immutable index"
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await t.test('view: Invalid operations: remove', {skip: process.platform === 'win32'}, () => {
|
|
241
|
+
const index = new usearch.Index({
|
|
242
|
+
metric: "l2sq",
|
|
243
|
+
connectivity: 16,
|
|
244
|
+
dimensions: 3,
|
|
245
|
+
});
|
|
246
|
+
index.view(indexPath);
|
|
247
|
+
assert.throws(
|
|
248
|
+
() => index.remove(42n),
|
|
249
|
+
{
|
|
250
|
+
name: 'Error',
|
|
251
|
+
message: "Can't remove from an immutable index"
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
});
|
|
84
255
|
});
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity
|
|
10
|
+
and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the
|
|
26
|
+
overall community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or
|
|
31
|
+
advances of any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
* Public or private harassment
|
|
34
|
+
* Publishing others' private information, such as a physical or email
|
|
35
|
+
address, without their explicit permission
|
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official e-mail address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported to the community leaders responsible for enforcement at
|
|
63
|
+
info@unum.cloud.
|
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
+
|
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
+
reporter of any incident.
|
|
68
|
+
|
|
69
|
+
## Enforcement Guidelines
|
|
70
|
+
|
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
+
|
|
74
|
+
### 1. Correction
|
|
75
|
+
|
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
+
unprofessional or unwelcome in the community.
|
|
78
|
+
|
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
|
82
|
+
|
|
83
|
+
### 2. Warning
|
|
84
|
+
|
|
85
|
+
**Community Impact**: A violation through a single incident or series
|
|
86
|
+
of actions.
|
|
87
|
+
|
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
+
like social media. Violating these terms may lead to a temporary or
|
|
93
|
+
permanent ban.
|
|
94
|
+
|
|
95
|
+
### 3. Temporary Ban
|
|
96
|
+
|
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
|
98
|
+
sustained inappropriate behavior.
|
|
99
|
+
|
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
+
communication with the community for a specified period of time. No public or
|
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
+
Violating these terms may lead to a permanent ban.
|
|
105
|
+
|
|
106
|
+
### 4. Permanent Ban
|
|
107
|
+
|
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
+
|
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within
|
|
113
|
+
the community.
|
|
114
|
+
|
|
115
|
+
## Attribution
|
|
116
|
+
|
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
+
version 2.0, available at
|
|
119
|
+
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
120
|
+
|
|
121
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
122
|
+
enforcement ladder](https://github.com/mozilla/diversity).
|
|
123
|
+
|
|
124
|
+
[homepage]: https://www.contributor-covenant.org
|
|
125
|
+
|
|
126
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
127
|
+
https://www.contributor-covenant.org/faq. Translations are available at
|
|
128
|
+
https://www.contributor-covenant.org/translations.
|
package/simsimd/CONTRIBUTING.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# Contributing
|
|
2
2
|
|
|
3
|
+
To keep the quality of the code high, we have a set of [guidelines](https://github.com/unum-cloud).
|
|
4
|
+
|
|
5
|
+
- [What's the procedure?](https://github.com/unum-cloud/awesome/blob/main/Workflow.md#organizing-software-development)
|
|
6
|
+
- [How to organize branches?](https://github.com/unum-cloud/awesome/blob/main/Workflow.md#branches)
|
|
7
|
+
- [How to style commits?](https://github.com/unum-cloud/awesome/blob/main/Workflow.md#commits)
|
|
8
|
+
|
|
3
9
|
## C and C++
|
|
4
10
|
|
|
5
11
|
To rerun experiments utilize the following command:
|
|
6
12
|
|
|
7
13
|
```sh
|
|
8
14
|
sudo apt install libopenblas-dev # BLAS installation is optional, but recommended for benchmarks
|
|
9
|
-
cmake -
|
|
15
|
+
cmake -D CMAKE_BUILD_TYPE=Release -D SIMSIMD_BUILD_TESTS=1 -D SIMSIMD_BUILD_BENCHMARKS=1 -D SIMSIMD_BUILD_BENCHMARKS_WITH_CBLAS=1 -B build_release
|
|
10
16
|
cmake --build build_release --config Release
|
|
11
17
|
build_release/simsimd_bench
|
|
12
18
|
build_release/simsimd_bench --benchmark_filter=js
|
|
@@ -14,7 +20,7 @@ build_release/simsimd_test_run_time
|
|
|
14
20
|
build_release/simsimd_test_compile_time # no need to run this one, it's just a compile-time test
|
|
15
21
|
```
|
|
16
22
|
|
|
17
|
-
To utilize
|
|
23
|
+
To utilize `f16` instructions, use GCC 12 or newer, or Clang 16 or newer.
|
|
18
24
|
To install them on Ubuntu 22.04, use:
|
|
19
25
|
|
|
20
26
|
```sh
|
|
@@ -23,15 +29,37 @@ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100
|
|
|
23
29
|
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100
|
|
24
30
|
```
|
|
25
31
|
|
|
32
|
+
To compile with the default Apple Clang on MacOS, use:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
brew install openblas
|
|
36
|
+
cmake -D CMAKE_BUILD_TYPE=Release \
|
|
37
|
+
-D SIMSIMD_BUILD_TESTS=1 \
|
|
38
|
+
-D SIMSIMD_BUILD_BENCHMARKS=1 \
|
|
39
|
+
-D SIMSIMD_BUILD_BENCHMARKS_WITH_CBLAS=1 \
|
|
40
|
+
-D CMAKE_PREFIX_PATH="$(brew --prefix openblas)" \
|
|
41
|
+
-D CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES="$(brew --prefix openblas)/include" \
|
|
42
|
+
-B build_release
|
|
43
|
+
cmake --build build_release --config Release
|
|
44
|
+
```
|
|
45
|
+
|
|
26
46
|
On MacOS it's recommended to use Homebrew and install Clang, as opposed to "Apple Clang".
|
|
27
|
-
Replacing the default compiler is not recommended, as it may break the system, but you can pass it as an environment variable:
|
|
47
|
+
Replacing the default compiler across the entire system is not recommended on MacOS, as it may break the system, but you can pass it as an environment variable:
|
|
28
48
|
|
|
29
49
|
```sh
|
|
30
|
-
brew install llvm
|
|
31
|
-
cmake -
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
brew install llvm openblas
|
|
51
|
+
cmake -D CMAKE_BUILD_TYPE=Release \
|
|
52
|
+
-D SIMSIMD_BUILD_TESTS=1 \
|
|
53
|
+
-D SIMSIMD_BUILD_BENCHMARKS=1 \
|
|
54
|
+
-D SIMSIMD_BUILD_BENCHMARKS_WITH_CBLAS=1 \
|
|
55
|
+
-D CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES="$(brew --prefix openblas)/include" \
|
|
56
|
+
-D CMAKE_C_LINK_FLAGS="-L$(xcrun --sdk macosx --show-sdk-path)/usr/lib" \
|
|
57
|
+
-D CMAKE_EXE_LINKER_FLAGS="-L$(xcrun --sdk macosx --show-sdk-path)/usr/lib" \
|
|
58
|
+
-D CMAKE_C_COMPILER="$(brew --prefix llvm)/bin/clang" \
|
|
59
|
+
-D CMAKE_CXX_COMPILER="$(brew --prefix llvm)/bin/clang++" \
|
|
60
|
+
-D CMAKE_OSX_SYSROOT="$(xcrun --sdk macosx --show-sdk-path)" \
|
|
61
|
+
-D CMAKE_OSX_DEPLOYMENT_TARGET=$(sw_vers -productVersion) \
|
|
62
|
+
-B build_release
|
|
35
63
|
cmake --build build_release --config Release
|
|
36
64
|
```
|
|
37
65
|
|
|
@@ -40,40 +68,64 @@ cmake --build build_release --config Release
|
|
|
40
68
|
Testing:
|
|
41
69
|
|
|
42
70
|
```sh
|
|
43
|
-
pip install -e .
|
|
44
|
-
pip install pytest pytest-repeat # testing dependencies
|
|
45
|
-
pytest
|
|
71
|
+
pip install -e . # to install the package in editable mode
|
|
72
|
+
pip install pytest pytest-repeat tabulate # testing dependencies
|
|
73
|
+
pytest scripts/test.py -s -x -Wd # to run tests
|
|
74
|
+
|
|
75
|
+
# to check supported SIMD instructions:
|
|
76
|
+
python -c "import simsimd; print(simsimd.get_capabilities())"
|
|
46
77
|
```
|
|
47
78
|
|
|
48
79
|
Here, `-s` will output the logs.
|
|
49
80
|
The `-x` will stop on the first failure.
|
|
50
81
|
The `-Wd` will silence overflows and runtime warnings.
|
|
51
82
|
|
|
83
|
+
When building on MacOS, same as with C/C++, use non-Apple Clang version:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
brew install llvm
|
|
87
|
+
CC=$(brew --prefix llvm)/bin/clang CXX=$(brew --prefix llvm)/bin/clang++ pip install -e .
|
|
88
|
+
```
|
|
89
|
+
|
|
52
90
|
Benchmarking:
|
|
53
91
|
|
|
54
92
|
```sh
|
|
55
|
-
pip install numpy scipy scikit-learn
|
|
56
|
-
python
|
|
57
|
-
python
|
|
93
|
+
pip install numpy scipy scikit-learn # for comparison baselines
|
|
94
|
+
python scripts/bench_vectors.py # to run default benchmarks
|
|
95
|
+
python scripts/bench_vectors.py --n 1000 --ndim 1536 # batch size and dimensions
|
|
58
96
|
```
|
|
59
97
|
|
|
60
|
-
You can also benchmark against other libraries:
|
|
98
|
+
You can also benchmark against other libraries, filter the numeric types, and distance metrics:
|
|
61
99
|
|
|
62
100
|
```sh
|
|
63
|
-
$ python
|
|
64
|
-
> usage: bench.py [-h] [--
|
|
101
|
+
$ python scripts/bench_vectors.py --help
|
|
102
|
+
> usage: bench.py [-h] [--ndim NDIM] [-n COUNT]
|
|
103
|
+
> [--metric {all,dot,spatial,binary,probability,sparse}]
|
|
104
|
+
> [--dtype {all,bits,int8,uint16,uint32,float16,float32,float64,bfloat16,complex32,complex64,complex128}]
|
|
105
|
+
> [--scipy] [--scikit] [--torch] [--tf] [--jax]
|
|
65
106
|
>
|
|
66
107
|
> Benchmark SimSIMD vs. other libraries
|
|
67
108
|
>
|
|
68
|
-
>
|
|
69
|
-
> -h, --help
|
|
70
|
-
> --
|
|
71
|
-
>
|
|
72
|
-
>
|
|
73
|
-
> --
|
|
74
|
-
>
|
|
75
|
-
>
|
|
76
|
-
>
|
|
109
|
+
> optional arguments:
|
|
110
|
+
> -h, --help show this help message and exit
|
|
111
|
+
> --ndim NDIM Number of dimensions in vectors (default: 1536) For binary vectors (e.g., Hamming, Jaccard), this is the number of bits. In
|
|
112
|
+
> case of SimSIMD, the inputs will be treated at the bit-level. Other packages will be matching/comparing 8-bit integers. The
|
|
113
|
+
> volume of exchanged data will be identical, but the results will differ.
|
|
114
|
+
> -n COUNT, --count COUNT
|
|
115
|
+
> Number of vectors per batch (default: 1) By default, when set to 1 the benchmark will generate many vectors of size (ndim, )
|
|
116
|
+
> and call the functions on pairs of single vectors: both directly, and through `cdist`. Alternatively, for larger batch sizes
|
|
117
|
+
> the benchmark will generate two matrices of size (n, ndim) and compute: - batch mode: (n) distances between vectors in
|
|
118
|
+
> identical rows of the two matrices, - all-pairs mode: (n^2) distances between all pairs of vectors in the two matrices via
|
|
119
|
+
> `cdist`.
|
|
120
|
+
> --metric {all,dot,spatial,binary,probability,sparse}
|
|
121
|
+
> Distance metric to use, profiles everything by default
|
|
122
|
+
> --dtype {all,bits,int8,uint16,uint32,float16,float32,float64,bfloat16,complex32,complex64,complex128}
|
|
123
|
+
> Defines numeric types to benchmark, profiles everything by default
|
|
124
|
+
> --scipy Profile SciPy, must be installed
|
|
125
|
+
> --scikit Profile scikit-learn, must be installed
|
|
126
|
+
> --torch Profile PyTorch, must be installed
|
|
127
|
+
> --tf Profile TensorFlow, must be installed
|
|
128
|
+
> --jax Profile JAX, must be installed
|
|
77
129
|
```
|
|
78
130
|
|
|
79
131
|
|
|
@@ -145,6 +197,12 @@ npm install -g bun
|
|
|
145
197
|
bun test
|
|
146
198
|
```
|
|
147
199
|
|
|
200
|
+
## Swift
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
swift build && swift test -v
|
|
204
|
+
```
|
|
205
|
+
|
|
148
206
|
## GoLang
|
|
149
207
|
|
|
150
208
|
```sh
|