usearch 0.17.0 → 0.18.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/.github/workflows/prerelease.yml +4 -4
- package/.github/workflows/release.yml +2 -0
- package/Cargo.toml +1 -1
- package/conanfile.py +1 -1
- package/package.json +1 -1
- package/python/scripts/test.py +3 -1
- package/python/usearch/eval.py +20 -23
|
@@ -42,13 +42,13 @@ jobs:
|
|
|
42
42
|
run: pytest python/scripts/test.py
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
name: Test Python 3.
|
|
45
|
+
test_python_37:
|
|
46
|
+
name: Test Python 3.7
|
|
47
47
|
runs-on: ${{ matrix.os }}
|
|
48
48
|
strategy:
|
|
49
49
|
matrix:
|
|
50
50
|
os: [ubuntu-20.04]
|
|
51
|
-
python-version: ["3.
|
|
51
|
+
python-version: ["3.7"]
|
|
52
52
|
|
|
53
53
|
steps:
|
|
54
54
|
- uses: actions/checkout@v3
|
|
@@ -62,7 +62,7 @@ jobs:
|
|
|
62
62
|
- name: Install dependencies
|
|
63
63
|
run: |
|
|
64
64
|
python -m pip install --upgrade pip
|
|
65
|
-
pip install pytest numpy
|
|
65
|
+
pip install pytest numpy
|
|
66
66
|
|
|
67
67
|
- name: Build locally
|
|
68
68
|
run: python -m pip install .
|
package/Cargo.toml
CHANGED
package/conanfile.py
CHANGED
|
@@ -4,7 +4,7 @@ from conans import ConanFile
|
|
|
4
4
|
class USearchConan(ConanFile):
|
|
5
5
|
|
|
6
6
|
name = 'USearch'
|
|
7
|
-
version = '0.
|
|
7
|
+
version = '0.18.0'
|
|
8
8
|
license = 'Apache 2.0'
|
|
9
9
|
url = 'https://github.com/unum-cloud/usearch'
|
|
10
10
|
description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
|
package/package.json
CHANGED
package/python/scripts/test.py
CHANGED
|
@@ -6,6 +6,7 @@ from usearch.io import load_matrix, save_matrix
|
|
|
6
6
|
from usearch.eval import recall_members, random_vectors
|
|
7
7
|
|
|
8
8
|
from usearch.index import Index, SparseIndex, MetricKind, ScalarKind, Matches
|
|
9
|
+
from usearch.index import _normalize_dtype
|
|
9
10
|
from usearch.index import (
|
|
10
11
|
DEFAULT_CONNECTIVITY,
|
|
11
12
|
DEFAULT_EXPANSION_ADD,
|
|
@@ -131,7 +132,8 @@ def test_index_batch(
|
|
|
131
132
|
assert matches.counts.shape[0] == batch_size
|
|
132
133
|
assert np.all(np.sort(index.labels) == np.sort(labels))
|
|
133
134
|
|
|
134
|
-
|
|
135
|
+
if _normalize_dtype(numpy_type) == _normalize_dtype(index_type):
|
|
136
|
+
assert recall_members(index, exact=True) == 1
|
|
135
137
|
|
|
136
138
|
|
|
137
139
|
@pytest.mark.parametrize('ndim', dimensions)
|
package/python/usearch/eval.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from time import time_ns
|
|
3
|
+
from typing import Tuple, Any, Callable, Union, Optional, List
|
|
4
|
+
from dataclasses import dataclass, asdict
|
|
2
5
|
from collections import defaultdict
|
|
3
6
|
|
|
4
7
|
import numpy as np
|
|
@@ -7,17 +10,6 @@ from usearch.io import load_matrix
|
|
|
7
10
|
from usearch.index import Index, Matches, MetricKind, MetricKindBitwise
|
|
8
11
|
|
|
9
12
|
|
|
10
|
-
# For compatibility with Python 3.6
|
|
11
|
-
try:
|
|
12
|
-
from time import time_ns
|
|
13
|
-
except ImportError:
|
|
14
|
-
from datetime import datetime
|
|
15
|
-
|
|
16
|
-
def time_ns():
|
|
17
|
-
now = datetime.now()
|
|
18
|
-
return int(now.timestamp() * 1e9)
|
|
19
|
-
|
|
20
|
-
|
|
21
13
|
def random_vectors(
|
|
22
14
|
count: int,
|
|
23
15
|
metric: MetricKind = MetricKind.IP,
|
|
@@ -89,7 +81,8 @@ def measure_seconds(f: Callable) -> Tuple[float, Any]:
|
|
|
89
81
|
return secs, result
|
|
90
82
|
|
|
91
83
|
|
|
92
|
-
|
|
84
|
+
@dataclass
|
|
85
|
+
class Dataset:
|
|
93
86
|
|
|
94
87
|
labels: np.ndarray
|
|
95
88
|
vectors: np.ndarray
|
|
@@ -163,7 +156,8 @@ class Dataset(NamedTuple):
|
|
|
163
156
|
return d
|
|
164
157
|
|
|
165
158
|
|
|
166
|
-
|
|
159
|
+
@dataclass
|
|
160
|
+
class TaskResult:
|
|
167
161
|
|
|
168
162
|
add_operations: Optional[int] = None
|
|
169
163
|
add_per_second: Optional[float] = None
|
|
@@ -190,7 +184,7 @@ class TaskResult(NamedTuple):
|
|
|
190
184
|
def search_seconds(self) -> float:
|
|
191
185
|
return self.search_operations / self.search_per_second
|
|
192
186
|
|
|
193
|
-
def __add__(self, other):
|
|
187
|
+
def __add__(self, other: TaskResult):
|
|
194
188
|
result = TaskResult()
|
|
195
189
|
if self.add_operations and other.add_operations:
|
|
196
190
|
result.add_operations = self.add_operations + other.add_operations
|
|
@@ -217,7 +211,8 @@ class TaskResult(NamedTuple):
|
|
|
217
211
|
return result
|
|
218
212
|
|
|
219
213
|
|
|
220
|
-
|
|
214
|
+
@dataclass
|
|
215
|
+
class AddTask:
|
|
221
216
|
|
|
222
217
|
labels: np.ndarray
|
|
223
218
|
vectors: np.ndarray
|
|
@@ -251,7 +246,7 @@ class AddTask(NamedTuple):
|
|
|
251
246
|
self.labels = self.labels[new_order]
|
|
252
247
|
self.vectors = self.vectors[new_order, :]
|
|
253
248
|
|
|
254
|
-
def slices(self, batch_size: int) ->
|
|
249
|
+
def slices(self, batch_size: int) -> List[AddTask]:
|
|
255
250
|
"""Splits this dataset into smaller chunks."""
|
|
256
251
|
|
|
257
252
|
return [
|
|
@@ -260,7 +255,7 @@ class AddTask(NamedTuple):
|
|
|
260
255
|
vectors=self.vectors[start_row:start_row+batch_size, :],
|
|
261
256
|
) for start_row in range(0, self.count, batch_size)]
|
|
262
257
|
|
|
263
|
-
def clusters(self, number_of_clusters: int) ->
|
|
258
|
+
def clusters(self, number_of_clusters: int) -> List[AddTask]:
|
|
264
259
|
"""Splits this dataset into smaller chunks."""
|
|
265
260
|
|
|
266
261
|
from sklearn.cluster import KMeans
|
|
@@ -280,7 +275,8 @@ class AddTask(NamedTuple):
|
|
|
280
275
|
) for rows in partitioning.values()]
|
|
281
276
|
|
|
282
277
|
|
|
283
|
-
|
|
278
|
+
@dataclass
|
|
279
|
+
class SearchTask:
|
|
284
280
|
|
|
285
281
|
queries: np.ndarray
|
|
286
282
|
neighbors: np.ndarray
|
|
@@ -295,7 +291,7 @@ class SearchTask(NamedTuple):
|
|
|
295
291
|
recall_at_one=results.recall_first(self.neighbors[:, 0].flatten()),
|
|
296
292
|
)
|
|
297
293
|
|
|
298
|
-
def slices(self, batch_size: int) ->
|
|
294
|
+
def slices(self, batch_size: int) -> List[SearchTask]:
|
|
299
295
|
"""Splits this dataset into smaller chunks."""
|
|
300
296
|
|
|
301
297
|
return [
|
|
@@ -305,7 +301,8 @@ class SearchTask(NamedTuple):
|
|
|
305
301
|
) for start_row in range(0, self.queries.shape[0], batch_size)]
|
|
306
302
|
|
|
307
303
|
|
|
308
|
-
|
|
304
|
+
@dataclass
|
|
305
|
+
class Evaluation:
|
|
309
306
|
|
|
310
307
|
tasks: List[Union[AddTask, SearchTask]]
|
|
311
308
|
count: int
|
|
@@ -315,7 +312,7 @@ class Evaluation(NamedTuple):
|
|
|
315
312
|
def for_dataset(
|
|
316
313
|
dataset: Dataset,
|
|
317
314
|
batch_size: int = 0,
|
|
318
|
-
clusters: int = 1):
|
|
315
|
+
clusters: int = 1) -> Evaluation:
|
|
319
316
|
|
|
320
317
|
tasks = []
|
|
321
318
|
add = AddTask(
|
|
@@ -356,5 +353,5 @@ class Evaluation(NamedTuple):
|
|
|
356
353
|
index.clear()
|
|
357
354
|
return {
|
|
358
355
|
**index.specs,
|
|
359
|
-
**task_result
|
|
356
|
+
**asdict(task_result),
|
|
360
357
|
}
|