Search and filtering¶
Every search computes a metric for every selected row. Results are exact over
the stored float32 representation, subject to floating-point arithmetic.
There is no approximate nearest-neighbor index.
Choose a metric¶
| Method | Ranking | Inclusive threshold | Typical use |
|---|---|---|---|
cosine_search |
Larger similarity first | min_value |
Direction-based embedding similarity |
dot_search |
Larger dot product first | min_value |
Magnitude-sensitive scoring with raw vectors |
euclidean_search |
Smaller distance first | max_value |
Nearest coordinates or features with raw vectors |
All methods accept query, keyword-only top_k=10, and within_rows=None.
A threshold removes rows whose computed values do not qualify, before choosing
the top results. It does not avoid computing the metric for selected rows.
Normalization changes the meaning¶
normalize=True is the default. Insertion scales each vector to unit length;
[3.0, 4.0] becomes approximately [0.6, 0.8]. The magnitude is discarded.
Queries are normalized too. Dot product then has effectively the same meaning
as cosine similarity, and Euclidean distance measures distance between directions.
With normalize=False, stored vectors retain their converted float32 values.
Dot product and Euclidean distance use their original magnitudes. Cosine still
compares directions, computing row norms during the search.
from numpy_vector_store import VectorStore
store = VectorStore[str](dimensions=2, normalize=False)
store.add([[1.0, 0.0], [5.0, 0.0], [0.0, 1.0]], ["near", "strong", "other"])
query = [1.0, 0.0]
print("cosine:", [hit.metadata for hit in store.cosine_search(query, top_k=2)])
print("dot:", [hit.metadata for hit in store.dot_search(query, top_k=2)])
print("distance:", [hit.metadata for hit in store.euclidean_search(query, top_k=2)])
cosine: ['near', 'strong']
dot: ['strong', 'near']
distance: ['near', 'other']
Zero vectors and numerical inputs¶
Normalized stores reject zero vectors. Raw stores allow them for dot product
and Euclidean distance. Raw cosine search raises ValueError if a selected
stored row is zero; exclude those rows with within_rows when needed.
Cosine always requires a nonzero query. Dot product and Euclidean distance
require a nonzero query only in normalized stores. These query rules also apply
to empty stores and empty filters. Inputs and thresholds must be finite.
Norms and raw metric intermediates use float64 where needed for numerical range.
Deterministic ranking and ties¶
Exactly equal computed metric values are ordered by ascending original store
row index. This also chooses which tied rows cross the top_k boundary.
Close but unequal values keep their metric order.
The cosine example above gives the two collinear rows equal values. Row 0
(near) precedes row 1 (strong). Reversing those rows in within_rows would
not change the tie break. With the default normalization, you should not depend
on approximate unit-length arithmetic producing bit-for-bit equal values for
every mathematically equivalent expression.
Filter candidates before searching¶
The store has no metadata query language. Evaluate your application's predicate first and supply the resulting row indexes:
from numpy_vector_store import VectorStore
store = VectorStore[dict[str, str]](dimensions=2)
store.add(
[[1.0, 0.0], [0.8, 0.2], [0.0, 1.0]],
[
{"title": "Install", "product": "A"},
{"title": "Configure", "product": "B"},
{"title": "Deploy", "product": "A"},
],
)
rows = [i for i, payload in enumerate(store.metadata) if payload["product"] == "A"]
hits = store.cosine_search([1.0, 0.0], top_k=2, within_rows=rows)
print([(hit.index, hit.metadata["title"]) for hit in hits])
[(0, 'Install'), (2, 'Deploy')]
within_rows must be a one-dimensional sequence or NumPy array of unique
integer row indexes. A Boolean mask is not a row selector; convert one with
np.flatnonzero(mask). An empty sequence returns no hits after validation.
Returned hit.index values always refer to the original store.
Unfiltered search uses the stored vector matrix directly. Filtering gathers a
temporary matrix proportional to the selected rows and dimensions. Omit
within_rows when you mean all rows; explicitly passing every row adds a full
matrix copy. Keep row indexes current after clear() or reload().
NumPy predicates and an external metadata table¶
Keep structured records outside the store when NumPy predicates suit your application. Store the record indexes as opaque payloads, then turn a Boolean mask into candidate row indexes. This example keeps record order aligned with store insertion order:
import numpy as np
from numpy_vector_store import VectorStore
records = np.array(
[("intro", "A", 2024), ("setup", "A", 2023), ("guide", "B", 2024)],
dtype=[("title", "U20"), ("product", "U10"), ("year", "i4")],
)
store = VectorStore[int](dimensions=2)
store.add([[1.0, 0.0], [0.8, 0.2], [0.0, 1.0]], list(range(len(records))))
mask = (records["product"] == "A") & (records["year"] >= 2024)
rows = np.flatnonzero(mask)
for hit in store.cosine_search([1.0, 0.0], within_rows=rows):
print(hit.index, records[hit.metadata]["title"])
0 intro
Input errors¶
| Input | Accepted | Invalid input |
|---|---|---|
dimensions, top_k, get(index) |
Python and NumPy integers, or integer-index objects | Booleans and non-integers raise TypeError; nonpositive dimensions and top-k raise ValueError |
normalize |
Python or NumPy Boolean | Other types raise TypeError |
| Threshold | Finite Python or NumPy real scalar | Booleans, strings, complex values, or arrays raise TypeError; non-finite values raise ValueError |
within_rows |
Unique integer indexes in range | Wrong shape or duplicates raise ValueError; non-integers or booleans raise TypeError; out-of-range indexes raise IndexError |
A Python selector such as [0, True] is rejected before NumPy can convert the
Boolean into an integer. Exact exception messages are not stable identifiers.
See the API reference for signatures.