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

asf-gitbox-commits pushed a commit to branch PHOENIX-7998
in repository https://gitbox.apache.org/repos/asf/phoenix.git

commit a6b14b32547d51918ca8971236ce918178257395
Author: Andrew Purtell <[email protected]>
AuthorDate: Tue Oct 6 00:53:26 2026 -0700

    PHOENIX-7998 Phase 15: Little-endian vector encoding and direct SIMD loads
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../function/BsonVectorValueFunction.java          | 19 +++-----
 .../expression/function/ScalarDistanceKernel.java  |  4 +-
 .../apache/phoenix/schema/types/PVectorDouble.java | 16 ++++---
 .../apache/phoenix/schema/types/PVectorFloat.java  | 17 +++++---
 .../expression/function/DistanceKernel.java        | 49 ++++++---------------
 .../expression/function/PanamaDistanceKernel.java  | 40 +++++++++++------
 .../function/BsonVectorValueFunctionTest.java      | 17 +++++++-
 .../phoenix/schema/types/VectorDataTypeTest.java   |  8 +++-
 .../function/PanamaDistanceKernelParityTest.java   | 51 ++++++++++++++++++----
 9 files changed, 135 insertions(+), 86 deletions(-)

diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/BsonVectorValueFunction.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/BsonVectorValueFunction.java
index 196031b8e1..cc3803d83a 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/BsonVectorValueFunction.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/BsonVectorValueFunction.java
@@ -57,9 +57,10 @@ import 
org.apache.phoenix.thirdparty.com.google.common.base.Preconditions;
  * <li>Vector dimension literal</li>
  * </ol>
  * </p>
- * Transcodes BSON Binary subtype 9 (VECTOR) FLOAT32 payloads from little 
endian format into the big
- * endian packed representation of {@link PVectorFloat}. Missing paths or 
documents evaluate to SQL
- * NULL. Structural or dimension mismatches raise {@link IllegalDataException}.
+ * Extracts BSON Binary subtype 9 (VECTOR) FLOAT32 payloads, whose little 
endian float32 layout is
+ * the packed representation of {@link PVectorFloat}, without copying or 
transcoding. Missing paths
+ * or documents evaluate to SQL NULL. Structural or dimension mismatches raise
+ * {@link IllegalDataException}.
  */
 @FunctionParseNode.BuiltInFunction(name = BsonVectorValueFunction.NAME,
     nodeClass = BsonVectorValueParseNode.class,
@@ -202,16 +203,8 @@ public class BsonVectorValueFunction extends 
ScalarFunction {
             + ", found " + payloadLength / (double) Bytes.SIZEOF_FLOAT)
           .build().buildException());
     }
-    // Convert little endian BSON float32 payload to big endian PVectorFloat 
encoding
-    byte[] out = new byte[payloadLength];
-    for (int i = 0; i < payloadLength; i += Bytes.SIZEOF_FLOAT) {
-      int src = VECTOR_HEADER_SIZE + i;
-      out[i] = data[src + 3];
-      out[i + 1] = data[src + 2];
-      out[i + 2] = data[src + 1];
-      out[i + 3] = data[src];
-    }
-    ptr.set(out);
+    // The little endian BSON float32 payload is already the PVectorFloat 
encoding
+    ptr.set(data, VECTOR_HEADER_SIZE, payloadLength);
     return true;
   }
 
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/ScalarDistanceKernel.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/ScalarDistanceKernel.java
index 7d014c1a28..32a4ae8d49 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/ScalarDistanceKernel.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/ScalarDistanceKernel.java
@@ -17,7 +17,7 @@
  */
 package org.apache.phoenix.expression.function;
 
-import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.schema.types.PVectorFloat;
 
 /**
  * Reference scalar distance kernels over packed float byte buffers and {@code 
float[]} arrays.
@@ -30,7 +30,7 @@ final class ScalarDistanceKernel {
   }
 
   private static float read(byte[] buf, int off, int i) {
-    return Bytes.toFloat(buf, off + i * Bytes.SIZEOF_FLOAT);
+    return PVectorFloat.readElement(buf, off, i);
   }
 
   /**
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDouble.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDouble.java
index b9c59ddea7..e3e99360bb 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDouble.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorDouble.java
@@ -23,7 +23,8 @@ import org.apache.phoenix.schema.SortOrder;
 
 /**
  * Data type representing fixed-dimension double-precision floating point 
vectors,
- * {@code VECTOR(DOUBLE, N)}. Stored as packed big-endian IEEE 754 values.
+ * {@code VECTOR(DOUBLE, N)}. Stored as packed little-endian IEEE 754 values, 
matching
+ * {@link PVectorFloat}.
  */
 public class PVectorDouble extends PVectorDataType<double[]> {
 
@@ -35,18 +36,22 @@ public class PVectorDouble extends 
PVectorDataType<double[]> {
 
   /** Reads one element by index from a packed ascending-order vector buffer. 
*/
   public static double readElement(byte[] buf, int offset, int index) {
-    return Bytes.toDouble(buf, offset + index * Bytes.SIZEOF_DOUBLE);
+    return Double.longBitsToDouble(readBits(buf, offset, index));
   }
 
   /** Reads one element by index from a packed vector buffer with the given 
sort order. */
   public static double readElement(byte[] buf, int offset, int index, 
SortOrder sortOrder) {
-    long b = Bytes.toLong(buf, offset + index * Bytes.SIZEOF_DOUBLE);
+    long b = readBits(buf, offset, index);
     if (sortOrder == SortOrder.DESC) {
       b ^= 0xFFFFFFFFFFFFFFFFL;
     }
     return Double.longBitsToDouble(b);
   }
 
+  private static long readBits(byte[] buf, int offset, int index) {
+    return Long.reverseBytes(Bytes.toLong(buf, offset + index * 
Bytes.SIZEOF_DOUBLE));
+  }
+
   /** Reads one element by index from a packed ascending-order vector pointer. 
*/
   public static double readElement(ImmutableBytesWritable ptr, int index) {
     return readElement(ptr.get(), ptr.getOffset(), index);
@@ -67,7 +72,7 @@ public class PVectorDouble extends PVectorDataType<double[]> {
   }
 
   /**
-   * Writes a vector into a byte buffer as packed contiguous big-endian IEEE 
754 values.
+   * Writes a vector into a byte buffer as packed contiguous little-endian 
IEEE 754 values.
    * @throws IllegalArgumentException if the buffer is too small to contain 
the vector
    */
   public static void writeElements(double[] vector, byte[] buf, int offset) {
@@ -76,7 +81,8 @@ public class PVectorDouble extends PVectorDataType<double[]> {
         + (offset + vector.length * Bytes.SIZEOF_DOUBLE) + " bytes, have " + 
buf.length);
     }
     for (int i = 0; i < vector.length; i++) {
-      Bytes.putDouble(buf, offset + i * Bytes.SIZEOF_DOUBLE, vector[i]);
+      Bytes.putLong(buf, offset + i * Bytes.SIZEOF_DOUBLE,
+        Long.reverseBytes(Double.doubleToRawLongBits(vector[i])));
     }
   }
 
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorFloat.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorFloat.java
index 619a719ae2..232189d87c 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorFloat.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVectorFloat.java
@@ -23,7 +23,9 @@ import org.apache.phoenix.schema.SortOrder;
 
 /**
  * Data type representing fixed-dimension single-precision floating point 
vectors,
- * {@code VECTOR(FLOAT, N)}. Stored as packed big-endian IEEE 754 values.
+ * {@code VECTOR(FLOAT, N)}. Stored as packed little-endian IEEE 754 values, 
the byte order of
+ * x86_64 and arm64, so SIMD kernels load elements directly from cell bytes 
and BSON FLOAT32 vectors
+ * copy in unchanged. The encoding is not order preserving; vectors are not 
comparable by bytes.
  */
 public class PVectorFloat extends PVectorDataType<float[]> {
 
@@ -35,18 +37,22 @@ public class PVectorFloat extends PVectorDataType<float[]> {
 
   /** Reads one element by index from a packed ascending-order vector buffer. 
*/
   public static float readElement(byte[] buf, int offset, int index) {
-    return Bytes.toFloat(buf, offset + index * Bytes.SIZEOF_FLOAT);
+    return Float.intBitsToFloat(readBits(buf, offset, index));
   }
 
   /** Reads one element by index from a packed vector buffer with the given 
sort order. */
   public static float readElement(byte[] buf, int offset, int index, SortOrder 
sortOrder) {
-    int b = Bytes.toInt(buf, offset + index * Bytes.SIZEOF_FLOAT);
+    int b = readBits(buf, offset, index);
     if (sortOrder == SortOrder.DESC) {
       b ^= 0xFFFFFFFF;
     }
     return Float.intBitsToFloat(b);
   }
 
+  private static int readBits(byte[] buf, int offset, int index) {
+    return Integer.reverseBytes(Bytes.toInt(buf, offset + index * 
Bytes.SIZEOF_FLOAT));
+  }
+
   /** Reads one element by index from a packed ascending-order vector pointer. 
*/
   public static float readElement(ImmutableBytesWritable ptr, int index) {
     return readElement(ptr.get(), ptr.getOffset(), index);
@@ -67,7 +73,7 @@ public class PVectorFloat extends PVectorDataType<float[]> {
   }
 
   /**
-   * Writes a vector into a byte buffer as packed contiguous big-endian IEEE 
754 values.
+   * Writes a vector into a byte buffer as packed contiguous little-endian 
IEEE 754 values.
    * @throws IllegalArgumentException if the buffer is too small to contain 
the vector
    */
   public static void writeElements(float[] vector, byte[] buf, int offset) {
@@ -76,7 +82,8 @@ public class PVectorFloat extends PVectorDataType<float[]> {
         + (offset + vector.length * Bytes.SIZEOF_FLOAT) + " bytes, have " + 
buf.length);
     }
     for (int i = 0; i < vector.length; i++) {
-      Bytes.putFloat(buf, offset + i * Bytes.SIZEOF_FLOAT, vector[i]);
+      Bytes.putInt(buf, offset + i * Bytes.SIZEOF_FLOAT,
+        Integer.reverseBytes(Float.floatToRawIntBits(vector[i])));
     }
   }
 
diff --git 
a/phoenix-core-client/src/main/java21/org/apache/phoenix/expression/function/DistanceKernel.java
 
b/phoenix-core-client/src/main/java21/org/apache/phoenix/expression/function/DistanceKernel.java
index 6fff9f6cb8..3b53b51ffa 100644
--- 
a/phoenix-core-client/src/main/java21/org/apache/phoenix/expression/function/DistanceKernel.java
+++ 
b/phoenix-core-client/src/main/java21/org/apache/phoenix/expression/function/DistanceKernel.java
@@ -17,23 +17,17 @@
  */
 package org.apache.phoenix.expression.function;
 
-import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.schema.types.PVectorFloat;
 
 /**
  * Java 21 multi-release implementation of {@code DistanceKernel}. Dispatches 
to
  * {@link PanamaDistanceKernel} when {@code jdk.incubator.vector} is 
available, falling back to
- * {@link ScalarDistanceKernel}.
- * <p>
- * Decodes packed big-endian float vectors into thread local float buffers 
prior to SIMD execution
- * to satisfy Vector API buffer alignment and endianness requirements.
+ * {@link ScalarDistanceKernel}. Both kernels operate directly on packed 
vector bytes.
  */
 final class DistanceKernel {
 
   private static final boolean SIMD = isPanamaAvailable();
 
-  private static final ThreadLocal<float[][]> SCRATCH =
-    ThreadLocal.withInitial(() -> new float[][] { new float[0], new float[0] 
});
-
   private DistanceKernel() {
   }
 
@@ -44,45 +38,28 @@ final class DistanceKernel {
   private static boolean isPanamaAvailable() {
     try {
       Class.forName("jdk.incubator.vector.FloatVector");
-      return PanamaDistanceKernel.dotProduct(new float[] { 1f }, new float[] { 
1f }, 1) == 1.0;
+      byte[] one = PVectorFloat.INSTANCE.toBytes(new float[] { 1f });
+      return PanamaDistanceKernel.dotProduct(one, 0, one, 0, 1) == 1.0;
     } catch (Throwable t) {
       return false;
     }
   }
 
-  private static float[] decode(int slot, byte[] buf, int off, int dim) {
-    float[][] scratch = SCRATCH.get();
-    float[] out = scratch[slot];
-    if (out.length < dim) {
-      out = new float[dim];
-      scratch[slot] = out;
-    }
-    for (int i = 0; i < dim; i++) {
-      out[i] = Bytes.toFloat(buf, off + i * Bytes.SIZEOF_FLOAT);
-    }
-    return out;
-  }
-
   static double l2DistanceSquared(byte[] a, int aOff, byte[] b, int bOff, int 
dim, double bound) {
-    if (!SIMD) {
-      return ScalarDistanceKernel.l2DistanceSquared(a, aOff, b, bOff, dim, 
bound);
-    }
-    return PanamaDistanceKernel.l2DistanceSquared(decode(0, a, aOff, dim), 
decode(1, b, bOff, dim),
-      dim, bound);
+    return SIMD
+      ? PanamaDistanceKernel.l2DistanceSquared(a, aOff, b, bOff, dim, bound)
+      : ScalarDistanceKernel.l2DistanceSquared(a, aOff, b, bOff, dim, bound);
   }
 
   static double dotProduct(byte[] a, int aOff, byte[] b, int bOff, int dim) {
-    if (!SIMD) {
-      return ScalarDistanceKernel.dotProduct(a, aOff, b, bOff, dim);
-    }
-    return PanamaDistanceKernel.dotProduct(decode(0, a, aOff, dim), decode(1, 
b, bOff, dim), dim);
+    return SIMD
+      ? PanamaDistanceKernel.dotProduct(a, aOff, b, bOff, dim)
+      : ScalarDistanceKernel.dotProduct(a, aOff, b, bOff, dim);
   }
 
   static double cosineDistance(byte[] a, int aOff, byte[] b, int bOff, int 
dim) {
-    if (!SIMD) {
-      return ScalarDistanceKernel.cosineDistance(a, aOff, b, bOff, dim);
-    }
-    return PanamaDistanceKernel.cosineDistance(decode(0, a, aOff, dim), 
decode(1, b, bOff, dim),
-      dim);
+    return SIMD
+      ? PanamaDistanceKernel.cosineDistance(a, aOff, b, bOff, dim)
+      : ScalarDistanceKernel.cosineDistance(a, aOff, b, bOff, dim);
   }
 }
diff --git 
a/phoenix-core-client/src/main/java21/org/apache/phoenix/expression/function/PanamaDistanceKernel.java
 
b/phoenix-core-client/src/main/java21/org/apache/phoenix/expression/function/PanamaDistanceKernel.java
index 386f1a4f86..d168677412 100644
--- 
a/phoenix-core-client/src/main/java21/org/apache/phoenix/expression/function/PanamaDistanceKernel.java
+++ 
b/phoenix-core-client/src/main/java21/org/apache/phoenix/expression/function/PanamaDistanceKernel.java
@@ -17,12 +17,20 @@
  */
 package org.apache.phoenix.expression.function;
 
+import jdk.incubator.vector.ByteVector;
 import jdk.incubator.vector.FloatVector;
 import jdk.incubator.vector.VectorOperators;
 import jdk.incubator.vector.VectorSpecies;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.schema.types.PVectorFloat;
 
 /**
- * SIMD distance kernels leveraging the Panama Vector API over {@code float[]} 
operands.
+ * SIMD distance kernels leveraging the Panama Vector API over packed 
little-endian float vectors.
+ * <p>
+ * Operands are loaded directly from their {@code byte[]} encodings, at any 
offset, as byte vectors
+ * reinterpreted to float lanes. The Vector API defines that reinterpretation 
as little-endian on
+ * every platform, which matches the {@link PVectorFloat} encoding, so no 
decoding pass or copy
+ * precedes the arithmetic.
  * <p>
  * Accumulates intermediate lane sums in single precision across fixed size 
blocks of
  * {@link #BLOCK_STRIDES} vector strides before reducing to double precision. 
Block level reduction
@@ -32,12 +40,19 @@ import jdk.incubator.vector.VectorSpecies;
 final class PanamaDistanceKernel {
 
   private static final VectorSpecies<Float> SPECIES = 
FloatVector.SPECIES_PREFERRED;
+  private static final VectorSpecies<Byte> BYTE_SPECIES =
+    VectorSpecies.of(byte.class, SPECIES.vectorShape());
   static final int BLOCK_STRIDES = 8;
 
   private PanamaDistanceKernel() {
   }
 
-  static double l2DistanceSquared(float[] a, float[] b, int dim, double bound) 
{
+  private static FloatVector load(byte[] buf, int off, int i) {
+    return ByteVector.fromArray(BYTE_SPECIES, buf, off + i * 
Bytes.SIZEOF_FLOAT)
+      .reinterpretAsFloats();
+  }
+
+  static double l2DistanceSquared(byte[] a, int aOff, byte[] b, int bOff, int 
dim, double bound) {
     int step = SPECIES.length();
     int upper = SPECIES.loopBound(dim);
     int blockLen = step * BLOCK_STRIDES;
@@ -47,8 +62,7 @@ final class PanamaDistanceKernel {
       int blockEnd = Math.min(upper, i + blockLen);
       FloatVector acc = FloatVector.zero(SPECIES);
       for (; i < blockEnd; i += step) {
-        FloatVector diff =
-          FloatVector.fromArray(SPECIES, a, 
i).sub(FloatVector.fromArray(SPECIES, b, i));
+        FloatVector diff = load(a, aOff, i).sub(load(b, bOff, i));
         acc = diff.fma(diff, acc);
       }
       total += acc.reduceLanes(VectorOperators.ADD);
@@ -57,7 +71,7 @@ final class PanamaDistanceKernel {
       }
     }
     for (; i < dim; i++) {
-      double diff = a[i] - b[i];
+      double diff = PVectorFloat.readElement(a, aOff, i) - 
PVectorFloat.readElement(b, bOff, i);
       total += diff * diff;
       if (total > bound) {
         return Double.MAX_VALUE;
@@ -66,7 +80,7 @@ final class PanamaDistanceKernel {
     return total;
   }
 
-  static double dotProduct(float[] a, float[] b, int dim) {
+  static double dotProduct(byte[] a, int aOff, byte[] b, int bOff, int dim) {
     int step = SPECIES.length();
     int upper = SPECIES.loopBound(dim);
     int blockLen = step * BLOCK_STRIDES;
@@ -76,17 +90,17 @@ final class PanamaDistanceKernel {
       int blockEnd = Math.min(upper, i + blockLen);
       FloatVector acc = FloatVector.zero(SPECIES);
       for (; i < blockEnd; i += step) {
-        acc = FloatVector.fromArray(SPECIES, a, 
i).fma(FloatVector.fromArray(SPECIES, b, i), acc);
+        acc = load(a, aOff, i).fma(load(b, bOff, i), acc);
       }
       total += acc.reduceLanes(VectorOperators.ADD);
     }
     for (; i < dim; i++) {
-      total += (double) a[i] * b[i];
+      total += (double) PVectorFloat.readElement(a, aOff, i) * 
PVectorFloat.readElement(b, bOff, i);
     }
     return total;
   }
 
-  static double cosineDistance(float[] a, float[] b, int dim) {
+  static double cosineDistance(byte[] a, int aOff, byte[] b, int bOff, int 
dim) {
     int step = SPECIES.length();
     int upper = SPECIES.loopBound(dim);
     int blockLen = step * BLOCK_STRIDES;
@@ -100,8 +114,8 @@ final class PanamaDistanceKernel {
       FloatVector vNormA = FloatVector.zero(SPECIES);
       FloatVector vNormB = FloatVector.zero(SPECIES);
       for (; i < blockEnd; i += step) {
-        FloatVector va = FloatVector.fromArray(SPECIES, a, i);
-        FloatVector vb = FloatVector.fromArray(SPECIES, b, i);
+        FloatVector va = load(a, aOff, i);
+        FloatVector vb = load(b, bOff, i);
         vDot = va.fma(vb, vDot);
         vNormA = va.fma(va, vNormA);
         vNormB = vb.fma(vb, vNormB);
@@ -111,8 +125,8 @@ final class PanamaDistanceKernel {
       normB += vNormB.reduceLanes(VectorOperators.ADD);
     }
     for (; i < dim; i++) {
-      double ai = a[i];
-      double bi = b[i];
+      double ai = PVectorFloat.readElement(a, aOff, i);
+      double bi = PVectorFloat.readElement(b, bOff, i);
       dot += ai * bi;
       normA += ai * ai;
       normB += bi * bi;
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/expression/function/BsonVectorValueFunctionTest.java
 
b/phoenix-core/src/test/java/org/apache/phoenix/expression/function/BsonVectorValueFunctionTest.java
index 8a6c22fb77..255f6dbd09 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/expression/function/BsonVectorValueFunctionTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/expression/function/BsonVectorValueFunctionTest.java
@@ -104,7 +104,7 @@ public class BsonVectorValueFunctionTest {
 
   @Test
   public void testExtractsSpecEncodedVector() throws Exception {
-    // Verify extraction and big-endian transcoding against IEEE 754 float 
values.
+    // Verify extraction against IEEE 754 float values.
     float[] expected = new float[] { 1.5f, -2.5f, 3.0e-3f };
     BsonDocument doc = new BsonDocument("search", new 
BsonDocument("embedding", vector(expected)));
     BsonVectorValueFunction f = func(doc, "search.embedding", 3);
@@ -115,6 +115,21 @@ public class BsonVectorValueFunctionTest {
     assertArrayEquals(expected, eval(f), DELTA);
   }
 
+  @Test
+  public void testOutputIsBsonPayloadUnchanged() throws Exception {
+    // The BSON FLOAT32 payload is already the PVectorFloat encoding, so 
extraction is a plain view.
+    float[] values = new float[] { 1.5f, -2.5f, 3.0e-3f, Float.MIN_VALUE };
+    BsonBinary binary = vector(values);
+    BsonVectorValueFunction f = func(new BsonDocument("v", binary), "v", 
values.length);
+    ImmutableBytesWritable ptr = new ImmutableBytesWritable();
+    assertTrue(f.evaluate(null, ptr));
+    byte[] data = binary.getData();
+    assertArrayEquals(
+      Arrays.copyOfRange(data, BsonVectorValueFunction.VECTOR_HEADER_SIZE, 
data.length),
+      ptr.copyBytes());
+    assertArrayEquals(PVectorFloat.INSTANCE.toBytes(values), ptr.copyBytes());
+  }
+
   @Test
   public void testExtractsFromRawDocumentAsStoredInCell() throws Exception {
     // Verify extraction from lazily-decoded RawBsonDocument instances.
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/VectorDataTypeTest.java
 
b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/VectorDataTypeTest.java
index 03b9d6bd28..ad8a058647 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/VectorDataTypeTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/VectorDataTypeTest.java
@@ -26,6 +26,8 @@ import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.phoenix.coprocessor.generated.PTableProtos;
@@ -346,7 +348,8 @@ public class VectorDataTypeTest {
     assertEquals(original.length * Bytes.SIZEOF_FLOAT, encoded.length);
 
     for (int i = 0; i < original.length; i++) {
-      int rawBits = Bytes.toInt(encoded, i * Bytes.SIZEOF_FLOAT);
+      int rawBits =
+        ByteBuffer.wrap(encoded).order(ByteOrder.LITTLE_ENDIAN).getInt(i * 
Bytes.SIZEOF_FLOAT);
       assertEquals("Element " + i + " bit pattern mismatch", 
Float.floatToRawIntBits(original[i]),
         rawBits);
     }
@@ -363,7 +366,8 @@ public class VectorDataTypeTest {
     assertEquals(original.length * Bytes.SIZEOF_DOUBLE, encoded.length);
 
     for (int i = 0; i < original.length; i++) {
-      long rawBits = Bytes.toLong(encoded, i * Bytes.SIZEOF_DOUBLE);
+      long rawBits =
+        ByteBuffer.wrap(encoded).order(ByteOrder.LITTLE_ENDIAN).getLong(i * 
Bytes.SIZEOF_DOUBLE);
       assertEquals("Element " + i + " bit pattern mismatch",
         Double.doubleToRawLongBits(original[i]), rawBits);
     }
diff --git 
a/phoenix-core/src/test/java21/org/apache/phoenix/expression/function/PanamaDistanceKernelParityTest.java
 
b/phoenix-core/src/test/java21/org/apache/phoenix/expression/function/PanamaDistanceKernelParityTest.java
index bb21cc9f17..908e962711 100644
--- 
a/phoenix-core/src/test/java21/org/apache/phoenix/expression/function/PanamaDistanceKernelParityTest.java
+++ 
b/phoenix-core/src/test/java21/org/apache/phoenix/expression/function/PanamaDistanceKernelParityTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.util.Random;
+import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.phoenix.schema.types.PVectorFloat;
 import org.junit.Test;
 
@@ -32,6 +33,8 @@ public class PanamaDistanceKernelParityTest {
 
   private static final int[] DIMS = { 1, 3, 7, 8, 15, 16, 64, 100, 128, 129, 
768, 1536 };
   private static final double TOLERANCE = 1e-5;
+  /** Value offsets within a cell are arbitrary, so the kernels must load from 
any offset. */
+  private static final int[] OFFSETS = { 0, 1, 3, 4 };
 
   private static float[] random(Random rng, int dim) {
     float[] v = new float[dim];
@@ -41,9 +44,32 @@ public class PanamaDistanceKernelParityTest {
     return v;
   }
 
+  /** Packs {@code v} at {@code off} between junk bytes, which a kernel must 
never read. */
+  private static byte[] pack(Random rng, float[] v, int off) {
+    byte[] buf = new byte[off + v.length * Bytes.SIZEOF_FLOAT + 64];
+    rng.nextBytes(buf);
+    PVectorFloat.writeElements(v, buf, off);
+    return buf;
+  }
+
   private static void assertClose(String what, double expected, double actual) 
{
-    double scale = Math.max(1.0, Math.abs(expected));
-    assertEquals(what, expected, actual, TOLERANCE * scale);
+    assertClose(what, expected, actual, Math.abs(expected));
+  }
+
+  private static void assertClose(String what, double expected, double actual, 
double magnitude) {
+    assertEquals(what, expected, actual, TOLERANCE * Math.max(1.0, magnitude));
+  }
+
+  /**
+   * Single precision rounding in a dot product scales with the sum of term 
magnitudes, not with the
+   * result, which cancellation can make arbitrarily small.
+   */
+  private static double termMagnitude(float[] a, float[] b) {
+    double sum = 0.0;
+    for (int i = 0; i < a.length; i++) {
+      sum += Math.abs((double) a[i] * b[i]);
+    }
+    return sum;
   }
 
   @Test
@@ -59,14 +85,18 @@ public class PanamaDistanceKernelParityTest {
       for (int trial = 0; trial < 50; trial++) {
         float[] a = random(rng, dim);
         float[] b = random(rng, dim);
-        String ctx = "dim=" + dim + " trial=" + trial;
+        int aOff = OFFSETS[trial % OFFSETS.length];
+        int bOff = OFFSETS[(trial / OFFSETS.length) % OFFSETS.length];
+        byte[] pa = pack(rng, a, aOff);
+        byte[] pb = pack(rng, b, bOff);
+        String ctx = "dim=" + dim + " trial=" + trial + " aOff=" + aOff + " 
bOff=" + bOff;
         assertClose("l2sq " + ctx,
           ScalarDistanceKernel.l2DistanceSquared(a, b, dim, Double.MAX_VALUE),
-          PanamaDistanceKernel.l2DistanceSquared(a, b, dim, Double.MAX_VALUE));
+          PanamaDistanceKernel.l2DistanceSquared(pa, aOff, pb, bOff, dim, 
Double.MAX_VALUE));
         assertClose("dot " + ctx, ScalarDistanceKernel.dotProduct(a, b, dim),
-          PanamaDistanceKernel.dotProduct(a, b, dim));
+          PanamaDistanceKernel.dotProduct(pa, aOff, pb, bOff, dim), 
termMagnitude(a, b));
         assertClose("cosine " + ctx, ScalarDistanceKernel.cosineDistance(a, b, 
dim),
-          PanamaDistanceKernel.cosineDistance(a, b, dim));
+          PanamaDistanceKernel.cosineDistance(pa, aOff, pb, bOff, dim));
       }
     }
   }
@@ -86,7 +116,8 @@ public class PanamaDistanceKernelParityTest {
         Math.sqrt(ScalarDistanceKernel.l2DistanceSquared(a, b, dim, 
Double.MAX_VALUE)),
         VectorDistanceUtil.l2DistanceWithBound(pa, 0, pb, 0, dim, 
Double.MAX_VALUE));
       assertClose("ip " + ctx, -ScalarDistanceKernel.dotProduct(a, b, dim),
-        VectorDistanceUtil.innerProductDistanceWithBound(pa, 0, pb, 0, dim, 
Double.MAX_VALUE));
+        VectorDistanceUtil.innerProductDistanceWithBound(pa, 0, pb, 0, dim, 
Double.MAX_VALUE),
+        termMagnitude(a, b));
       assertClose("cosine " + ctx, ScalarDistanceKernel.cosineDistance(a, b, 
dim),
         VectorDistanceUtil.cosineDistanceWithBound(pa, 0, pb, 0, dim, 
Double.MAX_VALUE));
     }
@@ -98,11 +129,13 @@ public class PanamaDistanceKernelParityTest {
     for (int dim : DIMS) {
       float[] a = random(rng, dim);
       float[] b = random(rng, dim);
+      byte[] pa = pack(rng, a, 1);
+      byte[] pb = pack(rng, b, 0);
       double exact = ScalarDistanceKernel.l2DistanceSquared(a, b, dim, 
Double.MAX_VALUE);
       assertEquals(Double.MAX_VALUE,
-        PanamaDistanceKernel.l2DistanceSquared(a, b, dim, exact * 0.5), 0.0);
+        PanamaDistanceKernel.l2DistanceSquared(pa, 1, pb, 0, dim, exact * 
0.5), 0.0);
       assertClose("dim=" + dim, exact,
-        PanamaDistanceKernel.l2DistanceSquared(a, b, dim, exact * 1.5));
+        PanamaDistanceKernel.l2DistanceSquared(pa, 1, pb, 0, dim, exact * 
1.5));
     }
   }
 }

Reply via email to