Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions qdrant_client/local/distances.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,16 +127,19 @@ def cosine_similarity(query: types.NumpyArray, vectors: types.NumpyArray) -> typ
Returns:
distances
"""
vectors = np.array(vectors, copy=True)
query = np.array(query, copy=True)

vectors_norm = np.linalg.norm(vectors, axis=-1)[:, np.newaxis]
vectors /= np.where(vectors_norm != 0.0, vectors_norm, EPSILON)
vectors = vectors / np.where(vectors_norm > EPSILON, vectors_norm, 1.0)

if len(query.shape) == 1:
query_norm = np.linalg.norm(query)
query /= np.where(query_norm != 0.0, query_norm, EPSILON)
query = query / np.where(query_norm > EPSILON, query_norm, 1.0)
return np.dot(vectors, query)

query_norm = np.linalg.norm(query, axis=-1)[:, np.newaxis]
query /= np.where(query_norm != 0.0, query_norm, EPSILON)
query = query / np.where(query_norm > EPSILON, query_norm, 1.0)
return np.dot(query, vectors.T)


Expand Down
6 changes: 3 additions & 3 deletions qdrant_client/local/local_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2442,7 +2442,7 @@ def _update_point(self, point: models.PointStruct) -> None:

if params.distance == models.Distance.COSINE:
vector_norm = np.linalg.norm(vector, axis=-1)[:, np.newaxis]
vector /= np.where(vector_norm != 0.0, vector_norm, EPSILON)
vector /= np.where(vector_norm > EPSILON, vector_norm, 1.0)
self.multivectors[vector_name][idx] = np.array(vector)
self.deleted_per_vector[vector_name][idx] = 0
else:
Expand Down Expand Up @@ -2541,7 +2541,7 @@ def _add_point(self, point: models.PointStruct) -> None:
params = self.get_vector_params(vector_name)
if params.distance == models.Distance.COSINE:
vector_norm = np.linalg.norm(vector_np, axis=-1)[:, np.newaxis]
vector_np /= np.where(vector_norm != 0.0, vector_norm, EPSILON)
vector_np /= np.where(vector_norm > EPSILON, vector_norm, 1.0)
named_vectors[idx] = vector_np
self.deleted_per_vector[vector_name] = np.append(
self.deleted_per_vector[vector_name], 0
Expand Down Expand Up @@ -2686,7 +2686,7 @@ def _update_named_vectors(
else:
if params.distance == models.Distance.COSINE:
vector_norm = np.linalg.norm(vector_np, axis=-1)[:, np.newaxis]
vector_np /= np.where(vector_norm != 0.0, vector_norm, EPSILON)
vector_np /= np.where(vector_norm > EPSILON, vector_norm, 1.0)
self.multivectors[vector_name][idx] = vector_np

def update_vectors(
Expand Down
24 changes: 24 additions & 0 deletions qdrant_client/local/tests/test_distances.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,27 @@ def test_distances() -> None:
multivector_query = np.array([[1, 2, 3], [3, 4, 5]])
docs = [np.array([[1, 2, 3], [0, 1, 2]])]
assert calculate_multi_distance(multivector_query, docs, models.Distance.DOT)[0] == 40.0


def test_cosine_similarity_keeps_near_zero_vectors_unchanged() -> None:
tiny = np.array([5e-11] * 4, dtype=np.float32)
vectors = tiny.copy()
query = np.array([1.0] * 4, dtype=np.float32)

result = calculate_distance(query, vectors[None, :], models.Distance.COSINE)

assert np.allclose(vectors, tiny)
assert np.allclose(result, [1e-10], atol=1e-12)


def test_cosine_similarity_does_not_mutate_inputs() -> None:
query = np.array([1.0, 1.0], dtype=np.float32)
vectors = np.array([[1.0, 1.0], [0.0, 0.0]], dtype=np.float32)
query_before = query.copy()
vectors_before = vectors.copy()

result = calculate_distance(query, vectors, models.Distance.COSINE)

assert np.allclose(query, query_before)
assert np.allclose(vectors, vectors_before)
assert np.allclose(result, [1.0, 0.0])