usearch 2.4.0 → 2.4.1

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/docs/format.md CHANGED
@@ -1,48 +1,71 @@
1
- # USearch Format Specification
1
+ # USearch File Format Specification
2
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.
3
+ > ⚠️ __Important Note__: The file format is slated for updates in the upcoming 3.0 release. These changes aim to support sparse indexes and enhance startup performance when loading large indexes from disk using the `view` command.
4
4
 
5
- ## Table of Contents
5
+ ## Upcoming Version: v3
6
6
 
7
- - [Dense Indexes](#dense-indexes)
8
- - [Core](#core)
9
- - [Fragmented Storage](#fragmented-storage)
7
+ The v3 file format is designed for compatibility with Apache Arrow arrays and consists of an array of variable-length binary strings. The structure for a file with `N` entries is as follows:
10
8
 
11
- ## Dense Indexes
9
+ ### File Structure
10
+
11
+ 1. __File Header__: Metadata and other essential information.
12
+ 2. __Offset Array__: An array of `N+1` eight-byte unsigned integers (`uint64_t`).
13
+ - The entry at index `0 <= i < N` indicates the byte-level offset of the `i-th` vector in the file.
14
+ - The entry at index `N` specifies the total length of the binary file.
15
+ 3. __Data Chunks__: `N` binary data chunks, each starting with the vector itself, followed by index system data.
16
+
17
+ ### Data Retrieval
18
+
19
+ To retrieve the Binary Large Object (BLOB) corresponding to a specific vector, subtract the consecutive offsets indicated in the Offset Array.
20
+
21
+ ### Advantages of the v3 Format
22
+
23
+ The v3 format offers several benefits, making it more future-proof:
24
+
25
+ - __Flexibility__: Easily accommodates variable-length strings.
26
+ - __Performance__: Co-locates vectors with proximity graph entries for optimized data access.
27
+ - __Compatibility__: Simplifies memory-mapping and casting to an Apache Arrow array of vectors or strings.
28
+ - __Extensibility__: Enables external storage systems to reuse the underlying `index_gt` more extensively, particularly in database systems.
29
+
30
+ ---
31
+
32
+ ## Current: v2
33
+
34
+ ### Dense Indexes
12
35
 
13
36
  Dense index files consist of two main parts: [a binary matrix of all vectors](#matrix-blob) and the [index](#index-blob) itself.
14
37
 
15
- ### Matrix BLOB
38
+ #### Matrix BLOB
16
39
 
17
40
  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
41
 
19
42
  - The number of `rows` corresponds to the number of vectors in the index.
20
43
  - The number of `columns` corresponds to the number of bytes in a single vector.
21
44
 
22
- ### Index BLOB
45
+ #### Index BLOB
23
46
 
24
47
  The index BLOB is composed of three main sections:
25
48
 
26
- 1. **Metadata**: Occupies 64 bytes and contains the following fields:
49
+ 1. __Metadata__: Occupies 64 bytes and contains the following fields:
27
50
  1. 7-byte magic string `usearch` to infer the MIME type of the file.
28
51
  2. 3-byte version number, containing the `major`, `minor`, and `patch` release numbers.
29
52
  3. 1-byte enums for `metric`, `scalar`, `key`, and `compressed_slot` types.
30
53
  4. 8-byte integers for the number of present vectors, number of deleted vectors, and number of dimensions.
31
54
  5. 1-byte flags for multi-vector support.
32
55
 
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.
56
+ 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
57
 
35
- 3. **Core**: Detailed in the [Core section](#core). This format is consistent across all types of indexes.
58
+ 3. __Core__: Detailed in the [Core section](#core). This format is consistent across all types of indexes.
36
59
 
37
- ## Fragmented Storage
60
+ ### Fragmented Storage
38
61
 
39
62
  The matrix BLOB can be stored separately and is not required to successfully open the file.
40
63
 
41
- ## Core
64
+ ### Core
42
65
 
43
66
  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
67
 
45
- ### Header
68
+ #### Header
46
69
 
47
70
  The core section begins with a header containing the following fields:
48
71
 
@@ -54,10 +77,10 @@ The core section begins with a header containing the following fields:
54
77
 
55
78
  These fields are serialized in a contiguous block of bytes, in the order listed.
56
79
 
57
- ### Levels
80
+ #### Levels
58
81
 
59
82
  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
83
 
61
- ### Nodes
84
+ #### Nodes
62
85
 
63
86
  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.
@@ -11,7 +11,7 @@
11
11
 
12
12
  #define USEARCH_VERSION_MAJOR 2
13
13
  #define USEARCH_VERSION_MINOR 4
14
- #define USEARCH_VERSION_PATCH 0
14
+ #define USEARCH_VERSION_PATCH 1
15
15
 
16
16
  // Inferring C++ version
17
17
  // https://stackoverflow.com/a/61552074
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian",
6
6
  "license": "Apache 2.0",
@@ -25,7 +25,7 @@
25
25
  "@semantic-release/exec": "^6.0.3",
26
26
  "@semantic-release/git": "^10.0.1",
27
27
  "conventional-changelog-eslint": "^3.0.9",
28
- "semantic-release": "^21.1.1",
28
+ "semantic-release": "^21.1.2",
29
29
  "typescript": "^5.1.6"
30
30
  }
31
31
  }
package/python/lib.cpp CHANGED
@@ -608,9 +608,9 @@ static py::tuple search_many_brute_force( //
608
608
  if (dataset_kind != queries_kind)
609
609
  throw std::invalid_argument("The types of vectors don't match!");
610
610
 
611
- py::array_t<dense_key_t> keys_py({dataset_count, static_cast<Py_ssize_t>(wanted)});
612
- py::array_t<distance_t> distances_py({dataset_count, static_cast<Py_ssize_t>(wanted)});
613
- py::array_t<Py_ssize_t> counts_py(dataset_count);
611
+ py::array_t<dense_key_t> keys_py({queries_count, static_cast<Py_ssize_t>(wanted)});
612
+ py::array_t<distance_t> distances_py({queries_count, static_cast<Py_ssize_t>(wanted)});
613
+ py::array_t<Py_ssize_t> counts_py(queries_count);
614
614
 
615
615
  std::size_t dimensions = static_cast<std::size_t>(queries_dimensions);
616
616
  metric_t metric = //
@@ -1200,7 +1200,7 @@ class Index:
1200
1200
  def __repr__(self) -> str:
1201
1201
  if not hasattr(self, "_compiled"):
1202
1202
  return "usearch.Index(failed)"
1203
- f = "usearch.Index({} x {}, {}, connectivity: {}, expansion: {} & {}, {:,} vectors in {} levels, {} hardware accerlation)"
1203
+ f = "usearch.Index({} x {}, {}, connectivity: {}, expansion: {} & {}, {:,} vectors in {} levels, {} hardware acceleration)"
1204
1204
  return f.format(
1205
1205
  self.dtype,
1206
1206
  self.ndim,
package/wasmer.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name="unum/usearch"
3
- version="2.4.0"
3
+ version="2.4.1"
4
4
  description="Smaller & Faster Single-File Vector Search Engine from Unum"
5
5
  license="Apache-2.0"
6
6
  readme="README.md"
@@ -1,306 +0,0 @@
1
- using Cloud.Unum.USearch;
2
-
3
- namespace Cloud.Unum.USearch.Tests;
4
-
5
- public class InitTests
6
- {
7
- // Epsilon for real values comparisons
8
- private const float Epsilon = 0.000001f;
9
-
10
- [Fact]
11
- public void InitializedAndDisposedSucceeds()
12
- {
13
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
14
- {
15
- Assert.NotNull(index);
16
- Assert.Equal((uint)3, index.Dimensions());
17
- }
18
- }
19
-
20
- [Fact]
21
- public void AddDoubleVectorSucceeds()
22
- {
23
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float64, 3))
24
- {
25
- index.Add(1, new double[] { 1.0, 2.0, 3.0 });
26
- Assert.True(index.Contains(1));
27
- Assert.False(index.Contains(2));
28
- Assert.Equal((uint)1, index.Size());
29
- }
30
- }
31
-
32
- [Fact]
33
- public void AddDoubleVectorsUnderSameKeySucceeds()
34
- {
35
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float64, 3, multi: true))
36
- {
37
- index.Add(1, new double[] { 1.0, 2.0, 3.0 });
38
- index.Add(2, new double[] { 1.0, 2.0, 3.0 });
39
- index.Add(3, new double[] { 1.0, 2.0, 3.0 });
40
- index.Add(1, new double[] { 1.0, 2.0, 3.0 });
41
- index.Add(2, new double[] { 1.0, 2.0, 3.0 });
42
- Assert.True(index.Contains(1));
43
- Assert.True(index.Contains(2));
44
- Assert.True(index.Contains(3));
45
- Assert.Equal((uint)5, index.Size());
46
- }
47
- }
48
-
49
- [Fact]
50
- public void ItCanGetMultipleFloatVectorsForSingleKey()
51
- {
52
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3, multi: true))
53
- {
54
- var inputVectors = new float[][] {
55
- new float[] { 1.0f, 6.0f, 11.0f },
56
- new float[] { 2.0f, 7.0f, 12.0f },
57
- new float[] { 3.0f, 8.0f, 13.0f },
58
- new float[] { 5.0f, 10.0f, 15.0f }
59
- };
60
- index.Add(
61
- new ulong[] { 2, 2, 2, 2 },
62
- inputVectors
63
- );
64
- Assert.True(index.Contains(2));
65
- Assert.Equal((uint)4, index.Size());
66
- Assert.Equal(4, index.Count(2));
67
-
68
- int foundVectorsCount = index.Get(2, 3, out float[][] retrievedVectors);
69
- Assert.Equal(3, foundVectorsCount);
70
- }
71
- }
72
-
73
- [Fact]
74
- public void ItCanGetMultipleDoubleVectorsForSingleKey()
75
- {
76
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float64, 3, multi: true))
77
- {
78
- var inputVectors = new double[][] {
79
- new double[] { 1.0, 6.0, 11.0 },
80
- new double[] { 2.0, 7.0, 12.0 },
81
- new double[] { 3.0, 8.0, 13.0 },
82
- new double[] { 5.0, 10.0, 15.0 }
83
- };
84
- index.Add(
85
- new ulong[] { 2, 2, 2, 2 },
86
- inputVectors
87
- );
88
- Assert.True(index.Contains(2));
89
- Assert.Equal((uint)4, index.Size());
90
- Assert.Equal(4, index.Count(2));
91
-
92
- int foundVectorsCount = index.Get(2, 3, out double[][] retrievedVectors);
93
- Assert.Equal(3, foundVectorsCount);
94
- }
95
- }
96
-
97
- [Fact]
98
- public void AddFloatVectorSucceeds()
99
- {
100
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
101
- {
102
- index.Add(1, new float[] { 1.0f, 2.0f, 3.0f });
103
- Assert.Equal((uint)1, index.Size());
104
- Assert.True(index.Contains(1));
105
- Assert.False(index.Contains(2));
106
- }
107
- }
108
-
109
- [Fact]
110
- public void AddFloatVectorsUnderSameKeySucceeds()
111
- {
112
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3, multi: true))
113
- {
114
- index.Add(1, new float[] { 1.0f, 2.0f, 3.0f });
115
- index.Add(2, new float[] { 1.0f, 2.0f, 3.0f });
116
- index.Add(3, new float[] { 1.0f, 2.0f, 3.0f });
117
- index.Add(1, new float[] { 1.0f, 2.0f, 3.0f });
118
- index.Add(2, new float[] { 1.0f, 2.0f, 3.0f });
119
- Assert.True(index.Contains(1));
120
- Assert.True(index.Contains(2));
121
- Assert.True(index.Contains(3));
122
- Assert.Equal((uint)5, index.Size());
123
- }
124
- }
125
-
126
- [Fact]
127
- public void AddGetFloatVectorSucceeds()
128
- {
129
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
130
- {
131
- var inputVector = new float[] { 1.0f, 2.0f, 3.0f };
132
- index.Add(1, inputVector);
133
- Assert.Equal((uint)1, index.Size());
134
- Assert.True(index.Contains(1));
135
- Assert.False(index.Contains(2));
136
- Assert.True(index.Get(1, out float[] retrievedVector) == 1);
137
- Assert.NotNull(retrievedVector);
138
- bool areEqual = inputVector
139
- .Zip(retrievedVector, (a, b) => (a, b))
140
- .All(pair => Math.Abs(pair.a - pair.b) <= Epsilon);
141
-
142
- Assert.True(areEqual);
143
- }
144
- }
145
-
146
- [Fact]
147
- public void AddGetDoubleVectorSucceeds()
148
- {
149
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
150
- {
151
- var inputVector = new double[] { 1.0, 2.0, 3.33 };
152
- index.Add(1, inputVector);
153
- Assert.Equal((uint)1, index.Size());
154
- Assert.True(index.Contains(1));
155
- Assert.False(index.Contains(2));
156
- Assert.True(index.Get(1, out double[] retrievedVector) == 1);
157
- Assert.NotNull(retrievedVector);
158
- bool areEqual = inputVector
159
- .Zip(retrievedVector, (a, b) => (a, b))
160
- .All(pair => Math.Abs(pair.a - pair.b) <= Epsilon);
161
-
162
- Assert.True(areEqual);
163
- }
164
- }
165
-
166
- [Fact]
167
- public void ItThrowsException_AddAddWithSameKeyAndGetVector()
168
- {
169
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 3))
170
- {
171
- ulong key = 1;
172
- float[] vector1 = { 1.0f, 2.0f, 33.3f };
173
- index.Add(key, vector1);
174
- float[] vector2 = { 3.0f, 4.0f, 34.2f };
175
- Assert.Throws<USearchException>(() => index.Add(key, vector2));
176
- }
177
- }
178
-
179
- [Fact]
180
- public void TestAddRemoveAddAndGet()
181
- {
182
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 2))
183
- {
184
- ulong key = 1;
185
- float[] vector = { 1.0f, 2.0f };
186
-
187
- index.Add(key, vector);
188
- Assert.True(index.Contains(key));
189
- index.Remove(key);
190
- Assert.False(index.Contains(key));
191
- index.Add(key, vector);
192
- Assert.True(index.Contains(key));
193
- Assert.True(index.Get(key, out float[] retrievedVector) == 1);
194
- Assert.Equal(vector, retrievedVector);
195
- }
196
- }
197
-
198
- [Fact]
199
- public void TestAddRemoveAddWithSameKeyVectorAndGet()
200
- {
201
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 2))
202
- {
203
- ulong key = 1;
204
- float[] vector = { 1.0f, 2.0f };
205
-
206
- index.Add(key, vector);
207
- Assert.True(index.Contains(key));
208
- index.Remove(key);
209
- Assert.False(index.Contains(key));
210
- index.Add(key, vector);
211
- Assert.True(index.Contains(key));
212
- Assert.True(index.Get(key, out float[] retrievedVector) == 1);
213
- Assert.Equal(vector, retrievedVector);
214
- }
215
- }
216
-
217
- [Fact]
218
- public void TestGetKeyThatNotExist()
219
- {
220
- using (var index = new USearchIndex(MetricKind.Cos, ScalarKind.Float32, 2))
221
- {
222
- Assert.True(index.Get(1, out float[] retrievedVector) == 0);
223
- Assert.False(index.Contains(1));
224
- }
225
- }
226
-
227
- [Fact]
228
- public void Search_FindsExpectedResults()
229
- {
230
- using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
231
- {
232
- index.Add(
233
- Enumerable.Range(1, 5).Select(i => (ulong)i).ToArray(),
234
- new float[][]
235
- {
236
- new float[] { -0.5f, -0.6f },
237
- new float[] { -0.1f, -0.1f },
238
- new float[] { 0.2f, 0.2f },
239
- new float[] { 0.7f, 0.8f },
240
- new float[] { 0.9f, 1.0f }
241
- }
242
- );
243
- var queryVector = new float[] { 0.0f, 0.0f };
244
-
245
- var matches = index.Search(queryVector, 2, out ulong[] keys, out float[] distances);
246
-
247
- Assert.Equal(2, matches);
248
- Assert.Contains(2UL, keys);
249
- Assert.Contains(3UL, keys);
250
- Assert.Equal(0.02, distances[0], Epsilon);
251
- Assert.Equal(0.08, distances[1], Epsilon);
252
- }
253
- }
254
-
255
- [Fact]
256
- public void Search_AfterRemovingKey_FindsExpectedResults()
257
- {
258
- using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
259
- {
260
- index.Add(1, new float[] { 0.1f, 0.2f });
261
- index.Add(2, new float[] { 0.2f, 0.3f });
262
- index.Add(3, new float[] { 0.3f, 0.4f });
263
- index.Remove(2);
264
- var queryVector = new float[] { 0.2f, 0.3f };
265
-
266
- var matches = index.Search(queryVector, 2, out ulong[] keys, out float[] distances);
267
-
268
- Assert.Equal(2, matches);
269
- Assert.Contains(1UL, keys);
270
- Assert.Contains(3UL, keys);
271
- Assert.DoesNotContain(2UL, keys);
272
- }
273
- }
274
-
275
- [Fact]
276
- public void Search_WithVectorsLessThanResultsLimit()
277
- {
278
- using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
279
- {
280
- index.Add(1, new float[] { 0.9f, 0.9f });
281
- index.Add(2, new float[] { 0.8f, 0.8f });
282
- var queryVector = new float[] { 0.1f, 0.2f };
283
-
284
- var matches = index.Search(queryVector, 10, out ulong[] keys, out float[] distances);
285
-
286
- Assert.Equal(2, matches);
287
- Assert.Contains(1UL, keys);
288
- Assert.Contains(2UL, keys);
289
- }
290
- }
291
-
292
- [Fact]
293
- public void ItCanRenameKey()
294
- {
295
- using (var index = new USearchIndex(MetricKind.L2sq, ScalarKind.Float32, 2))
296
- {
297
- index.Add(1, new float[] { 0.9f, 0.9f });
298
- index.Add(2, new float[] { 0.8f, 0.8f });
299
- index.Rename(1, 3);
300
- Assert.False(index.Contains(1));
301
- Assert.True(index.Contains(2));
302
- Assert.True(index.Contains(3));
303
- }
304
- }
305
-
306
- }