vicinity 0.3.3__tar.gz → 0.4.0__tar.gz
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.
- {vicinity-0.3.3 → vicinity-0.4.0}/PKG-INFO +1 -1
- {vicinity-0.3.3 → vicinity-0.4.0}/tests/test_vicinity.py +8 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/uv.lock +1 -1
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/backends/annoy.py +5 -4
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/backends/base.py +1 -1
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/backends/basic.py +9 -6
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/backends/faiss.py +8 -5
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/backends/hnsw.py +6 -4
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/backends/pynndescent.py +6 -6
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/backends/usearch.py +12 -8
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/backends/voyager.py +6 -4
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/version.py +1 -1
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/vicinity.py +12 -8
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity.egg-info/PKG-INFO +1 -1
- {vicinity-0.3.3 → vicinity-0.4.0}/.github/workflows/ci.yaml +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/.gitignore +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/.pre-commit-config.yaml +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/LICENSE +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/Makefile +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/README.md +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/assets/images/vicinity_logo.png +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/pyproject.toml +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/setup.cfg +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/tests/conftest.py +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/tests/test_utils.py +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/__init__.py +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/backends/__init__.py +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/datatypes.py +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/py.typed +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity/utils.py +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity.egg-info/SOURCES.txt +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity.egg-info/dependency_links.txt +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity.egg-info/requires.txt +0 -0
- {vicinity-0.3.3 → vicinity-0.4.0}/vicinity.egg-info/top_level.txt +0 -0
|
@@ -58,6 +58,10 @@ def test_vicinity_query(vicinity_instance: Vicinity, query_vector: np.ndarray) -
|
|
|
58
58
|
|
|
59
59
|
assert len(results) == 1
|
|
60
60
|
|
|
61
|
+
results = vicinity_instance.query(np.stack([query_vector, query_vector]), k=2)
|
|
62
|
+
|
|
63
|
+
assert results[0] == results[1]
|
|
64
|
+
|
|
61
65
|
|
|
62
66
|
def test_vicinity_query_threshold(vicinity_instance: Vicinity, query_vector: np.ndarray) -> None:
|
|
63
67
|
"""
|
|
@@ -70,6 +74,10 @@ def test_vicinity_query_threshold(vicinity_instance: Vicinity, query_vector: np.
|
|
|
70
74
|
|
|
71
75
|
assert len(results) >= 1
|
|
72
76
|
|
|
77
|
+
results = vicinity_instance.query_threshold(np.stack([query_vector, query_vector]), threshold=0.7)
|
|
78
|
+
|
|
79
|
+
assert results[0] == results[1]
|
|
80
|
+
|
|
73
81
|
|
|
74
82
|
def test_vicinity_insert(vicinity_instance: Vicinity, query_vector: np.ndarray) -> None:
|
|
75
83
|
"""
|
|
@@ -126,9 +126,10 @@ class AnnoyBackend(AbstractBackend[AnnoyArgs]):
|
|
|
126
126
|
"""Delete vectors from the backend."""
|
|
127
127
|
raise NotImplementedError("Deletion is not supported in Annoy backend.")
|
|
128
128
|
|
|
129
|
-
def threshold(self, vectors: npt.NDArray, threshold: float) ->
|
|
129
|
+
def threshold(self, vectors: npt.NDArray, threshold: float, max_k: int) -> QueryResult:
|
|
130
130
|
"""Threshold the backend."""
|
|
131
|
-
out:
|
|
132
|
-
for x, y in self.query(vectors,
|
|
133
|
-
|
|
131
|
+
out: QueryResult = []
|
|
132
|
+
for x, y in self.query(vectors, max_k):
|
|
133
|
+
mask = y < threshold
|
|
134
|
+
out.append((x[mask], y[mask]))
|
|
134
135
|
return out
|
|
@@ -92,7 +92,7 @@ class AbstractBackend(ABC, Generic[ArgType]):
|
|
|
92
92
|
raise NotImplementedError()
|
|
93
93
|
|
|
94
94
|
@abstractmethod
|
|
95
|
-
def threshold(self, vectors: npt.NDArray, threshold: float) ->
|
|
95
|
+
def threshold(self, vectors: npt.NDArray, threshold: float, max_k: int) -> QueryResult:
|
|
96
96
|
"""Threshold the backend."""
|
|
97
97
|
raise NotImplementedError()
|
|
98
98
|
|
|
@@ -150,22 +150,25 @@ class BasicBackend(BasicVectorStore, AbstractBackend[BasicArgs], ABC):
|
|
|
150
150
|
self,
|
|
151
151
|
vectors: npt.NDArray,
|
|
152
152
|
threshold: float,
|
|
153
|
-
|
|
153
|
+
max_k: int,
|
|
154
|
+
) -> QueryResult:
|
|
154
155
|
"""
|
|
155
156
|
Batched distance thresholding.
|
|
156
157
|
|
|
157
158
|
:param vectors: The vectors to threshold.
|
|
158
159
|
:param threshold: The threshold to use.
|
|
159
|
-
:
|
|
160
|
+
:param max_k: The maximum number of neighbors to consider.
|
|
161
|
+
:return: A list of tuples with the indices and distances.
|
|
160
162
|
"""
|
|
161
|
-
out:
|
|
163
|
+
out: QueryResult = []
|
|
162
164
|
for i in range(0, len(vectors), 1024):
|
|
163
165
|
batch = vectors[i : i + 1024]
|
|
164
166
|
distances = self._dist(batch)
|
|
165
167
|
for dists in distances:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
mask = dists <= threshold
|
|
169
|
+
indices = np.flatnonzero(mask)
|
|
170
|
+
filtered_distances = dists[mask]
|
|
171
|
+
out.append((indices, filtered_distances))
|
|
169
172
|
return out
|
|
170
173
|
|
|
171
174
|
def query(
|
|
@@ -146,6 +146,7 @@ class FaissBackend(AbstractBackend[FaissArgs]):
|
|
|
146
146
|
|
|
147
147
|
def query(self, vectors: npt.NDArray, k: int) -> QueryResult:
|
|
148
148
|
"""Perform a k-NN search in the FAISS index."""
|
|
149
|
+
k = min(len(self), k)
|
|
149
150
|
if self.arguments.metric == "cosine":
|
|
150
151
|
vectors = normalize(vectors)
|
|
151
152
|
distances, indices = self.index.search(vectors, k)
|
|
@@ -163,9 +164,9 @@ class FaissBackend(AbstractBackend[FaissArgs]):
|
|
|
163
164
|
"""Delete vectors from the backend."""
|
|
164
165
|
raise NotImplementedError("Deletion is not supported in FAISS backends.")
|
|
165
166
|
|
|
166
|
-
def threshold(self, vectors: npt.NDArray, threshold: float) ->
|
|
167
|
+
def threshold(self, vectors: npt.NDArray, threshold: float, max_k: int) -> QueryResult:
|
|
167
168
|
"""Query vectors within a distance threshold, using range_search if supported."""
|
|
168
|
-
out:
|
|
169
|
+
out: QueryResult = []
|
|
169
170
|
if self.arguments.metric == "cosine":
|
|
170
171
|
vectors = normalize(vectors)
|
|
171
172
|
|
|
@@ -178,13 +179,15 @@ class FaissBackend(AbstractBackend[FaissArgs]):
|
|
|
178
179
|
dist = D[start:end]
|
|
179
180
|
if self.arguments.metric == "cosine":
|
|
180
181
|
dist = 1 - dist
|
|
181
|
-
|
|
182
|
+
mask = dist < threshold
|
|
183
|
+
out.append((idx[mask], dist[mask]))
|
|
182
184
|
else:
|
|
183
|
-
distances, indices = self.index.search(vectors,
|
|
185
|
+
distances, indices = self.index.search(vectors, max_k)
|
|
184
186
|
for dist, idx in zip(distances, indices):
|
|
185
187
|
if self.arguments.metric == "cosine":
|
|
186
188
|
dist = 1 - dist
|
|
187
|
-
|
|
189
|
+
mask = dist < threshold
|
|
190
|
+
out.append((idx[mask], dist[mask]))
|
|
188
191
|
|
|
189
192
|
return out
|
|
190
193
|
|
|
@@ -93,6 +93,7 @@ class HNSWBackend(AbstractBackend[HNSWArgs]):
|
|
|
93
93
|
|
|
94
94
|
def query(self, vectors: npt.NDArray, k: int) -> QueryResult:
|
|
95
95
|
"""Query the backend."""
|
|
96
|
+
k = min(k, len(self))
|
|
96
97
|
return list(zip(*self.index.knn_query(vectors, k)))
|
|
97
98
|
|
|
98
99
|
def insert(self, vectors: npt.NDArray) -> None:
|
|
@@ -103,10 +104,11 @@ class HNSWBackend(AbstractBackend[HNSWArgs]):
|
|
|
103
104
|
"""Delete vectors from the backend."""
|
|
104
105
|
raise NotImplementedError("Deletion is not supported in HNSW backend.")
|
|
105
106
|
|
|
106
|
-
def threshold(self, vectors: npt.NDArray, threshold: float) ->
|
|
107
|
+
def threshold(self, vectors: npt.NDArray, threshold: float, max_k: int) -> QueryResult:
|
|
107
108
|
"""Threshold the backend."""
|
|
108
|
-
out:
|
|
109
|
-
for x, y in self.query(vectors,
|
|
110
|
-
|
|
109
|
+
out: QueryResult = []
|
|
110
|
+
for x, y in self.query(vectors, max_k):
|
|
111
|
+
mask = y < threshold
|
|
112
|
+
out.append((x[mask], y[mask]))
|
|
111
113
|
|
|
112
114
|
return out
|
|
@@ -80,15 +80,15 @@ class PyNNDescentBackend(AbstractBackend[PyNNDescentArgs]):
|
|
|
80
80
|
"""Delete vectors from the backend."""
|
|
81
81
|
raise NotImplementedError("Deletion is not supported in PyNNDescent backend.")
|
|
82
82
|
|
|
83
|
-
def threshold(self, vectors: npt.NDArray, threshold: float) ->
|
|
83
|
+
def threshold(self, vectors: npt.NDArray, threshold: float, max_k: int) -> QueryResult:
|
|
84
84
|
"""Find neighbors within a distance threshold."""
|
|
85
85
|
normalized_vectors = normalize_or_copy(vectors)
|
|
86
|
-
indices, distances = self.index.query(normalized_vectors, k=
|
|
87
|
-
|
|
86
|
+
indices, distances = self.index.query(normalized_vectors, k=max_k)
|
|
87
|
+
out: QueryResult = []
|
|
88
88
|
for idx, dist in zip(indices, distances):
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return
|
|
89
|
+
mask = dist < threshold
|
|
90
|
+
out.append((idx[mask], dist[mask]))
|
|
91
|
+
return out
|
|
92
92
|
|
|
93
93
|
def save(self, base_path: Path) -> None:
|
|
94
94
|
"""Save the vectors and configuration to a specified path."""
|
|
@@ -115,9 +115,10 @@ class UsearchBackend(AbstractBackend[UsearchArgs]):
|
|
|
115
115
|
|
|
116
116
|
def query(self, vectors: npt.NDArray, k: int) -> QueryResult:
|
|
117
117
|
"""Query the backend and return results as tuples of keys and distances."""
|
|
118
|
+
k = min(k, len(self))
|
|
118
119
|
results = self.index.search(vectors, k)
|
|
119
|
-
keys = np.
|
|
120
|
-
distances = np.
|
|
120
|
+
keys = np.atleast_2d(results.keys)
|
|
121
|
+
distances = np.atleast_2d(results.distances)
|
|
121
122
|
return list(zip(keys, distances))
|
|
122
123
|
|
|
123
124
|
def insert(self, vectors: npt.NDArray) -> None:
|
|
@@ -128,9 +129,12 @@ class UsearchBackend(AbstractBackend[UsearchArgs]):
|
|
|
128
129
|
"""Delete vectors from the index (not supported by Usearch)."""
|
|
129
130
|
raise NotImplementedError("Dynamic deletion is not supported in Usearch.")
|
|
130
131
|
|
|
131
|
-
def threshold(self, vectors: npt.NDArray, threshold: float) ->
|
|
132
|
-
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
def threshold(self, vectors: npt.NDArray, threshold: float, max_k: int) -> QueryResult:
|
|
133
|
+
"""Query vectors within a distance threshold and return keys and distances."""
|
|
134
|
+
out: QueryResult = []
|
|
135
|
+
for keys_row, distances_row in self.query(vectors, max_k):
|
|
136
|
+
keys_row = np.array(keys_row)
|
|
137
|
+
distances_row = np.array(distances_row, dtype=np.float32)
|
|
138
|
+
mask = distances_row < threshold
|
|
139
|
+
out.append((keys_row[mask], distances_row[mask]))
|
|
140
|
+
return out
|
|
@@ -68,6 +68,7 @@ class VoyagerBackend(AbstractBackend[VoyagerArgs]):
|
|
|
68
68
|
|
|
69
69
|
def query(self, query: npt.NDArray, k: int) -> QueryResult:
|
|
70
70
|
"""Query the backend for the nearest neighbors."""
|
|
71
|
+
k = min(k, len(self))
|
|
71
72
|
indices, distances = self.index.query(query, k)
|
|
72
73
|
return list(zip(indices, distances))
|
|
73
74
|
|
|
@@ -93,11 +94,12 @@ class VoyagerBackend(AbstractBackend[VoyagerArgs]):
|
|
|
93
94
|
"""Delete vectors from the backend."""
|
|
94
95
|
raise NotImplementedError("Deletion is not supported in Voyager backend.")
|
|
95
96
|
|
|
96
|
-
def threshold(self, vectors: npt.NDArray, threshold: float) ->
|
|
97
|
+
def threshold(self, vectors: npt.NDArray, threshold: float, max_k: int) -> QueryResult:
|
|
97
98
|
"""Threshold the backend."""
|
|
98
|
-
out: list[npt.NDArray] = []
|
|
99
|
-
for x, y in self.query(vectors,
|
|
100
|
-
|
|
99
|
+
out: list[tuple[npt.NDArray, npt.NDArray]] = []
|
|
100
|
+
for x, y in self.query(vectors, max_k):
|
|
101
|
+
mask = y < threshold
|
|
102
|
+
out.append((x[mask], y[mask]))
|
|
101
103
|
|
|
102
104
|
return out
|
|
103
105
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version_triple__ = (0,
|
|
1
|
+
__version_triple__ = (0, 4, 0)
|
|
2
2
|
__version__ = ".".join(map(str, __version_triple__))
|
|
@@ -14,7 +14,7 @@ from numpy import typing as npt
|
|
|
14
14
|
|
|
15
15
|
from vicinity import Metric
|
|
16
16
|
from vicinity.backends import AbstractBackend, BasicBackend, BasicVectorStore, get_backend_class
|
|
17
|
-
from vicinity.datatypes import Backend, PathLike
|
|
17
|
+
from vicinity.datatypes import Backend, PathLike, QueryResult
|
|
18
18
|
|
|
19
19
|
logger = logging.getLogger(__name__)
|
|
20
20
|
|
|
@@ -114,7 +114,7 @@ class Vicinity:
|
|
|
114
114
|
self,
|
|
115
115
|
vectors: npt.NDArray,
|
|
116
116
|
k: int = 10,
|
|
117
|
-
) -> list[
|
|
117
|
+
) -> list[QueryResult]:
|
|
118
118
|
"""
|
|
119
119
|
Find the nearest neighbors to some arbitrary vector.
|
|
120
120
|
|
|
@@ -140,22 +140,26 @@ class Vicinity:
|
|
|
140
140
|
self,
|
|
141
141
|
vectors: npt.NDArray,
|
|
142
142
|
threshold: float = 0.5,
|
|
143
|
-
|
|
143
|
+
max_k: int = 100,
|
|
144
|
+
) -> list[QueryResult]:
|
|
144
145
|
"""
|
|
145
|
-
Find the nearest neighbors to some arbitrary vector with some threshold.
|
|
146
|
+
Find the nearest neighbors to some arbitrary vector with some threshold. Note: the output is not sorted.
|
|
146
147
|
|
|
147
148
|
:param vectors: The vectors to find the most similar vectors to.
|
|
148
149
|
:param threshold: The threshold to use.
|
|
150
|
+
:param max_k: The maximum number of neighbors to consider for the threshold query.
|
|
149
151
|
|
|
150
|
-
:return: For each item in the input,
|
|
152
|
+
:return: For each item in the input, the items above the threshold are returned in the form of
|
|
153
|
+
(NAME, SIMILARITY) tuples.
|
|
151
154
|
"""
|
|
152
|
-
vectors = np.
|
|
155
|
+
vectors = np.asarray(vectors)
|
|
153
156
|
if np.ndim(vectors) == 1:
|
|
154
157
|
vectors = vectors[None, :]
|
|
155
158
|
|
|
156
159
|
out = []
|
|
157
|
-
for
|
|
158
|
-
|
|
160
|
+
for indices, distances in self.backend.threshold(vectors, threshold, max_k=max_k):
|
|
161
|
+
distances.clip(min=0, out=distances)
|
|
162
|
+
out.append([(self.items[idx], dist) for idx, dist in zip(indices, distances)])
|
|
159
163
|
|
|
160
164
|
return out
|
|
161
165
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|