You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by vi...@apache.org on 2026/02/23 21:19:51 UTC

(lucene) branch branch_10x updated: Prevent zero vectors on cosine similarity fields (#15751)

This is an automated email from the ASF dual-hosted git repository.

vigyasharma pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/branch_10x by this push:
     new 4e00bd9127d Prevent zero vectors on cosine similarity fields  (#15751)
4e00bd9127d is described below

commit 4e00bd9127d4793d95ac6560730e3542d1d32bc2
Author: Vigya Sharma <vi...@gmail.com>
AuthorDate: Mon Feb 23 10:50:14 2026 -0800

    Prevent zero vectors on cosine similarity fields  (#15751)
---
 lucene/CHANGES.txt                                 |  2 ++
 .../apache/lucene/document/KnnByteVectorField.java |  9 +++++++
 .../lucene/document/KnnFloatVectorField.java       |  9 +++++++
 .../java/org/apache/lucene/util/VectorUtil.java    | 20 ++++++++++++++
 .../test/org/apache/lucene/document/TestField.java | 31 ++++++++++++++++++++++
 5 files changed, 71 insertions(+)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 03a4dab9eb9..7a824a940ee 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -15,6 +15,8 @@ API Changes
 
 * GITHUB#15723: Add hook for LRUQueryCache to allow custom population strategies (Nhat Nguyen)
 
+* GITHUB#15751: Prevent zero vectors in knn fields with cosine similarity configured (Vigya Sharma)
+
 New Features
 ---------------------
 
diff --git a/lucene/core/src/java/org/apache/lucene/document/KnnByteVectorField.java b/lucene/core/src/java/org/apache/lucene/document/KnnByteVectorField.java
index ddeef44c72a..570ef43c56f 100644
--- a/lucene/core/src/java/org/apache/lucene/document/KnnByteVectorField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/KnnByteVectorField.java
@@ -23,6 +23,7 @@ import org.apache.lucene.index.VectorEncoding;
 import org.apache.lucene.index.VectorSimilarityFunction;
 import org.apache.lucene.search.KnnByteVectorQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.util.VectorUtil;
 
 /**
  * A field that contains a single byte numeric vector (or none) for each document. Vectors are dense
@@ -49,6 +50,10 @@ public class KnnByteVectorField extends Field {
     if (similarityFunction == null) {
       throw new IllegalArgumentException("similarity function must not be null");
     }
+    if (similarityFunction == VectorSimilarityFunction.COSINE
+        && VectorUtil.isZeroVector(v) == true) {
+      throw new IllegalArgumentException("zero vector not allowed with cosine similarity function");
+    }
     FieldType type = new FieldType();
     type.setVectorAttributes(dimension, VectorEncoding.BYTE, similarityFunction);
     type.freeze();
@@ -138,6 +143,10 @@ public class KnnByteVectorField extends Field {
       throw new IllegalArgumentException(
           "The number of vector dimensions does not match the field type");
     }
+    if (fieldType.vectorSimilarityFunction() == VectorSimilarityFunction.COSINE
+        && VectorUtil.isZeroVector(vector) == true) {
+      throw new IllegalArgumentException("zero vector not allowed with cosine similarity function");
+    }
     fieldsData = vector;
   }
 
diff --git a/lucene/core/src/java/org/apache/lucene/document/KnnFloatVectorField.java b/lucene/core/src/java/org/apache/lucene/document/KnnFloatVectorField.java
index 63a55ddc669..dcdb8a1735d 100644
--- a/lucene/core/src/java/org/apache/lucene/document/KnnFloatVectorField.java
+++ b/lucene/core/src/java/org/apache/lucene/document/KnnFloatVectorField.java
@@ -50,6 +50,10 @@ public class KnnFloatVectorField extends Field {
     if (similarityFunction == null) {
       throw new IllegalArgumentException("similarity function must not be null");
     }
+    if (similarityFunction == VectorSimilarityFunction.COSINE
+        && VectorUtil.isZeroVector(v) == true) {
+      throw new IllegalArgumentException("zero vector not allowed with cosine similarity function");
+    }
     FieldType type = new FieldType();
     type.setVectorAttributes(dimension, VectorEncoding.FLOAT32, similarityFunction);
     type.freeze();
@@ -139,6 +143,11 @@ public class KnnFloatVectorField extends Field {
       throw new IllegalArgumentException(
           "The number of vector dimensions does not match the field type");
     }
+    if (fieldType.vectorSimilarityFunction() == VectorSimilarityFunction.COSINE
+        && VectorUtil.isZeroVector(vector) == true) {
+      throw new IllegalArgumentException(
+          "zero vector not allowed with cosine similarity configured in field type");
+    }
     fieldsData = VectorUtil.checkFinite(vector);
   }
 
diff --git a/lucene/core/src/java/org/apache/lucene/util/VectorUtil.java b/lucene/core/src/java/org/apache/lucene/util/VectorUtil.java
index be606d90582..a526363b679 100644
--- a/lucene/core/src/java/org/apache/lucene/util/VectorUtil.java
+++ b/lucene/core/src/java/org/apache/lucene/util/VectorUtil.java
@@ -453,6 +453,26 @@ public final class VectorUtil {
     return v;
   }
 
+  /** Returns true if all dimensions of provided vector are zero, false otherwise. */
+  public static boolean isZeroVector(float[] v) {
+    for (float value : v) {
+      if (value != 0) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /** Returns true if all dimensions of provided vector are zero, false otherwise. */
+  public static boolean isZeroVector(byte[] v) {
+    for (float value : v) {
+      if (value != 0) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   /**
    * Given an array {@code buffer} that is sorted between indexes {@code 0} inclusive and {@code to}
    * exclusive, find the first array index whose value is greater than or equal to {@code target}.
diff --git a/lucene/core/src/test/org/apache/lucene/document/TestField.java b/lucene/core/src/test/org/apache/lucene/document/TestField.java
index 5c1b8f17294..8eeecaf0fa6 100644
--- a/lucene/core/src/test/org/apache/lucene/document/TestField.java
+++ b/lucene/core/src/test/org/apache/lucene/document/TestField.java
@@ -734,6 +734,37 @@ public class TestField extends LuceneTestCase {
     }
   }
 
+  public void testKnnFieldZeroVectors() throws Exception {
+    float[] v = new float[5];
+    IllegalArgumentException zeroError =
+        expectThrows(
+            IllegalArgumentException.class,
+            () -> new KnnFloatVectorField("knnFloats", v, VectorSimilarityFunction.COSINE));
+    assertTrue(zeroError.getMessage().contains("zero vector not allowed"));
+
+    FieldType fieldType = KnnFloatVectorField.createFieldType(5, VectorSimilarityFunction.COSINE);
+    zeroError =
+        expectThrows(
+            IllegalArgumentException.class,
+            () -> new KnnFloatVectorField("knnFloats", v, fieldType));
+    assertTrue(zeroError.getMessage().contains("zero vector not allowed"));
+
+    byte[] bv = new byte[5];
+    zeroError =
+        expectThrows(
+            IllegalArgumentException.class,
+            () -> new KnnByteVectorField("knnBytes", bv, VectorSimilarityFunction.COSINE));
+    assertTrue(zeroError.getMessage().contains("zero vector not allowed"));
+
+    FieldType byteFieldType =
+        KnnByteVectorField.createFieldType(5, VectorSimilarityFunction.COSINE);
+    zeroError =
+        expectThrows(
+            IllegalArgumentException.class,
+            () -> new KnnByteVectorField("knnBytes", bv, byteFieldType));
+    assertTrue(zeroError.getMessage().contains("zero vector not allowed"));
+  }
+
   private void trySetByteValue(Field f) {
     expectThrows(
         IllegalArgumentException.class,