|
@@ -43,6 +43,7 @@ import org.apache.lucene.search.Query;
|
|
|
import org.apache.lucene.search.join.BitSetProducer;
|
|
|
import org.apache.lucene.util.BytesRef;
|
|
|
import org.apache.lucene.util.VectorUtil;
|
|
|
+import org.elasticsearch.common.ParsingException;
|
|
|
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
|
|
import org.elasticsearch.index.IndexVersion;
|
|
|
import org.elasticsearch.index.IndexVersions;
|
|
@@ -69,6 +70,7 @@ import org.elasticsearch.search.vectors.ESDiversifyingChildrenByteKnnVectorQuery
|
|
|
import org.elasticsearch.search.vectors.ESDiversifyingChildrenFloatKnnVectorQuery;
|
|
|
import org.elasticsearch.search.vectors.ESKnnByteVectorQuery;
|
|
|
import org.elasticsearch.search.vectors.ESKnnFloatVectorQuery;
|
|
|
+import org.elasticsearch.search.vectors.VectorData;
|
|
|
import org.elasticsearch.search.vectors.VectorSimilarityQuery;
|
|
|
import org.elasticsearch.xcontent.ToXContent;
|
|
|
import org.elasticsearch.xcontent.XContentBuilder;
|
|
@@ -80,6 +82,7 @@ import java.nio.ByteBuffer;
|
|
|
import java.nio.ByteOrder;
|
|
|
import java.time.ZoneId;
|
|
|
import java.util.Arrays;
|
|
|
+import java.util.HexFormat;
|
|
|
import java.util.Locale;
|
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
@@ -88,6 +91,7 @@ import java.util.function.Function;
|
|
|
import java.util.function.Supplier;
|
|
|
import java.util.stream.Stream;
|
|
|
|
|
|
+import static org.elasticsearch.common.Strings.format;
|
|
|
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
|
|
|
|
|
|
/**
|
|
@@ -338,11 +342,16 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException {
|
|
|
+ public double computeDotProduct(VectorData vectorData) {
|
|
|
+ return VectorUtil.dotProduct(vectorData.asByteVector(), vectorData.asByteVector());
|
|
|
+ }
|
|
|
+
|
|
|
+ private VectorData parseVectorArray(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException {
|
|
|
int index = 0;
|
|
|
byte[] vector = new byte[fieldMapper.fieldType().dims];
|
|
|
float squaredMagnitude = 0;
|
|
|
- for (Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser().nextToken()) {
|
|
|
+ for (XContentParser.Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser()
|
|
|
+ .nextToken()) {
|
|
|
fieldMapper.checkDimensionExceeded(index, context);
|
|
|
ensureExpectedToken(Token.VALUE_NUMBER, token, context.parser());
|
|
|
final int value;
|
|
@@ -383,44 +392,49 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
fieldMapper.checkDimensionMatches(index, context);
|
|
|
checkVectorMagnitude(fieldMapper.fieldType().similarity, errorByteElementsAppender(vector), squaredMagnitude);
|
|
|
+ return VectorData.fromBytes(vector);
|
|
|
+ }
|
|
|
+
|
|
|
+ private VectorData parseHexEncodedVector(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException {
|
|
|
+ byte[] decodedVector = HexFormat.of().parseHex(context.parser().text());
|
|
|
+ fieldMapper.checkDimensionMatches(decodedVector.length, context);
|
|
|
+ VectorData vectorData = VectorData.fromBytes(decodedVector);
|
|
|
+ double squaredMagnitude = computeDotProduct(vectorData);
|
|
|
+ checkVectorMagnitude(
|
|
|
+ fieldMapper.fieldType().similarity,
|
|
|
+ errorByteElementsAppender(decodedVector),
|
|
|
+ (float) squaredMagnitude
|
|
|
+ );
|
|
|
+ return vectorData;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ VectorData parseKnnVector(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException {
|
|
|
+ XContentParser.Token token = context.parser().currentToken();
|
|
|
+ return switch (token) {
|
|
|
+ case START_ARRAY -> parseVectorArray(context, fieldMapper);
|
|
|
+ case VALUE_STRING -> parseHexEncodedVector(context, fieldMapper);
|
|
|
+ default -> throw new ParsingException(
|
|
|
+ context.parser().getTokenLocation(),
|
|
|
+ format("Unsupported type [%s] for provided value [%s]", token, context.parser().text())
|
|
|
+ );
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException {
|
|
|
+ VectorData vectorData = parseKnnVector(context, fieldMapper);
|
|
|
Field field = createKnnVectorField(
|
|
|
fieldMapper.fieldType().name(),
|
|
|
- vector,
|
|
|
+ vectorData.asByteVector(),
|
|
|
fieldMapper.fieldType().similarity.vectorSimilarityFunction(fieldMapper.indexCreatedVersion, this)
|
|
|
);
|
|
|
context.doc().addWithKey(fieldMapper.fieldType().name(), field);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- double parseKnnVectorToByteBuffer(DocumentParserContext context, DenseVectorFieldMapper fieldMapper, ByteBuffer byteBuffer)
|
|
|
- throws IOException {
|
|
|
- double dotProduct = 0f;
|
|
|
- int index = 0;
|
|
|
- for (Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser().nextToken()) {
|
|
|
- fieldMapper.checkDimensionExceeded(index, context);
|
|
|
- ensureExpectedToken(Token.VALUE_NUMBER, token, context.parser());
|
|
|
- int value = context.parser().intValue(true);
|
|
|
- if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
|
|
|
- throw new IllegalArgumentException(
|
|
|
- "element_type ["
|
|
|
- + this
|
|
|
- + "] vectors only support integers between ["
|
|
|
- + Byte.MIN_VALUE
|
|
|
- + ", "
|
|
|
- + Byte.MAX_VALUE
|
|
|
- + "] but found ["
|
|
|
- + value
|
|
|
- + "] at dim ["
|
|
|
- + index
|
|
|
- + "];"
|
|
|
- );
|
|
|
- }
|
|
|
- byteBuffer.put((byte) value);
|
|
|
- dotProduct += value * value;
|
|
|
- index++;
|
|
|
- }
|
|
|
- fieldMapper.checkDimensionMatches(index, context);
|
|
|
- return dotProduct;
|
|
|
+ int getNumBytes(int dimensions) {
|
|
|
+ return dimensions * elementBytes;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -530,6 +544,11 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public double computeDotProduct(VectorData vectorData) {
|
|
|
+ return VectorUtil.dotProduct(vectorData.asFloatVector(), vectorData.asFloatVector());
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException {
|
|
|
int index = 0;
|
|
@@ -566,23 +585,27 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- double parseKnnVectorToByteBuffer(DocumentParserContext context, DenseVectorFieldMapper fieldMapper, ByteBuffer byteBuffer)
|
|
|
- throws IOException {
|
|
|
- double dotProduct = 0f;
|
|
|
+ VectorData parseKnnVector(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException {
|
|
|
int index = 0;
|
|
|
+ float squaredMagnitude = 0;
|
|
|
float[] vector = new float[fieldMapper.fieldType().dims];
|
|
|
for (Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser().nextToken()) {
|
|
|
fieldMapper.checkDimensionExceeded(index, context);
|
|
|
ensureExpectedToken(Token.VALUE_NUMBER, token, context.parser());
|
|
|
float value = context.parser().floatValue(true);
|
|
|
vector[index] = value;
|
|
|
- byteBuffer.putFloat(value);
|
|
|
- dotProduct += value * value;
|
|
|
+ squaredMagnitude += value * value;
|
|
|
index++;
|
|
|
}
|
|
|
fieldMapper.checkDimensionMatches(index, context);
|
|
|
checkVectorBounds(vector);
|
|
|
- return dotProduct;
|
|
|
+ checkVectorMagnitude(fieldMapper.fieldType().similarity, errorFloatElementsAppender(vector), squaredMagnitude);
|
|
|
+ return VectorData.fromFloats(vector);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ int getNumBytes(int dimensions) {
|
|
|
+ return dimensions * elementBytes;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -607,8 +630,9 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
|
|
|
abstract void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException;
|
|
|
|
|
|
- abstract double parseKnnVectorToByteBuffer(DocumentParserContext context, DenseVectorFieldMapper fieldMapper, ByteBuffer byteBuffer)
|
|
|
- throws IOException;
|
|
|
+ abstract VectorData parseKnnVector(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException;
|
|
|
+
|
|
|
+ abstract int getNumBytes(int dimensions);
|
|
|
|
|
|
abstract ByteBuffer createByteBuffer(IndexVersion indexVersion, int numBytes);
|
|
|
|
|
@@ -699,6 +723,8 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
static Function<StringBuilder, StringBuilder> errorByteElementsAppender(byte[] vector) {
|
|
|
return sb -> appendErrorElements(sb, vector);
|
|
|
}
|
|
|
+
|
|
|
+ public abstract double computeDotProduct(VectorData vectorData);
|
|
|
}
|
|
|
|
|
|
static final Map<String, ElementType> namesToElementType = Map.of(
|
|
@@ -1158,66 +1184,120 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return knnQuery;
|
|
|
}
|
|
|
|
|
|
- public Query createExactKnnQuery(float[] queryVector) {
|
|
|
- queryVector = validateAndNormalize(queryVector);
|
|
|
- VectorSimilarityFunction vectorSimilarityFunction = similarity.vectorSimilarityFunction(indexVersionCreated, elementType);
|
|
|
+ public Query createExactKnnQuery(VectorData queryVector) {
|
|
|
+ if (isIndexed() == false) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ "to perform knn search on field [" + name() + "], its mapping must have [index] set to [true]"
|
|
|
+ );
|
|
|
+ }
|
|
|
return switch (elementType) {
|
|
|
- case BYTE -> {
|
|
|
- byte[] bytes = new byte[queryVector.length];
|
|
|
+ case BYTE -> createExactKnnByteQuery(queryVector.asByteVector());
|
|
|
+ case FLOAT -> createExactKnnFloatQuery(queryVector.asFloatVector());
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private Query createExactKnnByteQuery(byte[] queryVector) {
|
|
|
+ if (queryVector.length != dims) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ "the query vector has a different dimension [" + queryVector.length + "] than the index vectors [" + dims + "]"
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (similarity == VectorSimilarity.DOT_PRODUCT || similarity == VectorSimilarity.COSINE) {
|
|
|
+ float squaredMagnitude = VectorUtil.dotProduct(queryVector, queryVector);
|
|
|
+ elementType.checkVectorMagnitude(similarity, ElementType.errorByteElementsAppender(queryVector), squaredMagnitude);
|
|
|
+ }
|
|
|
+ VectorSimilarityFunction vectorSimilarityFunction = similarity.vectorSimilarityFunction(indexVersionCreated, elementType);
|
|
|
+ return new BooleanQuery.Builder().add(new FieldExistsQuery(name()), BooleanClause.Occur.FILTER)
|
|
|
+ .add(
|
|
|
+ new FunctionQuery(
|
|
|
+ new ByteVectorSimilarityFunction(
|
|
|
+ vectorSimilarityFunction,
|
|
|
+ new ByteKnnVectorFieldSource(name()),
|
|
|
+ new ConstKnnByteVectorValueSource(queryVector)
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ BooleanClause.Occur.SHOULD
|
|
|
+ )
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private Query createExactKnnFloatQuery(float[] queryVector) {
|
|
|
+ if (queryVector.length != dims) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ "the query vector has a different dimension [" + queryVector.length + "] than the index vectors [" + dims + "]"
|
|
|
+ );
|
|
|
+ }
|
|
|
+ elementType.checkVectorBounds(queryVector);
|
|
|
+ if (similarity == VectorSimilarity.DOT_PRODUCT || similarity == VectorSimilarity.COSINE) {
|
|
|
+ float squaredMagnitude = VectorUtil.dotProduct(queryVector, queryVector);
|
|
|
+ elementType.checkVectorMagnitude(similarity, ElementType.errorFloatElementsAppender(queryVector), squaredMagnitude);
|
|
|
+ if (similarity == VectorSimilarity.COSINE
|
|
|
+ && indexVersionCreated.onOrAfter(NORMALIZE_COSINE)
|
|
|
+ && isNotUnitVector(squaredMagnitude)) {
|
|
|
+ float length = (float) Math.sqrt(squaredMagnitude);
|
|
|
+ queryVector = Arrays.copyOf(queryVector, queryVector.length);
|
|
|
for (int i = 0; i < queryVector.length; i++) {
|
|
|
- bytes[i] = (byte) queryVector[i];
|
|
|
+ queryVector[i] /= length;
|
|
|
}
|
|
|
- yield new BooleanQuery.Builder().add(new FieldExistsQuery(name()), BooleanClause.Occur.FILTER)
|
|
|
- .add(
|
|
|
- new FunctionQuery(
|
|
|
- new ByteVectorSimilarityFunction(
|
|
|
- vectorSimilarityFunction,
|
|
|
- new ByteKnnVectorFieldSource(name()),
|
|
|
- new ConstKnnByteVectorValueSource(bytes)
|
|
|
- )
|
|
|
- ),
|
|
|
- BooleanClause.Occur.SHOULD
|
|
|
- )
|
|
|
- .build();
|
|
|
}
|
|
|
- case FLOAT -> new BooleanQuery.Builder().add(new FieldExistsQuery(name()), BooleanClause.Occur.FILTER)
|
|
|
- .add(
|
|
|
- new FunctionQuery(
|
|
|
- new FloatVectorSimilarityFunction(
|
|
|
- vectorSimilarityFunction,
|
|
|
- new FloatKnnVectorFieldSource(name()),
|
|
|
- new ConstKnnFloatValueSource(queryVector)
|
|
|
- )
|
|
|
- ),
|
|
|
- BooleanClause.Occur.SHOULD
|
|
|
- )
|
|
|
- .build();
|
|
|
- };
|
|
|
+ }
|
|
|
+ VectorSimilarityFunction vectorSimilarityFunction = similarity.vectorSimilarityFunction(indexVersionCreated, elementType);
|
|
|
+ return new BooleanQuery.Builder().add(new FieldExistsQuery(name()), BooleanClause.Occur.FILTER)
|
|
|
+ .add(
|
|
|
+ new FunctionQuery(
|
|
|
+ new FloatVectorSimilarityFunction(
|
|
|
+ vectorSimilarityFunction,
|
|
|
+ new FloatKnnVectorFieldSource(name()),
|
|
|
+ new ConstKnnFloatValueSource(queryVector)
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ BooleanClause.Occur.SHOULD
|
|
|
+ )
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ Query createKnnQuery(float[] queryVector, int numCands, Query filter, Float similarityThreshold, BitSetProducer parentFilter) {
|
|
|
+ return createKnnQuery(VectorData.fromFloats(queryVector), numCands, filter, similarityThreshold, parentFilter);
|
|
|
}
|
|
|
|
|
|
public Query createKnnQuery(
|
|
|
- float[] queryVector,
|
|
|
+ VectorData queryVector,
|
|
|
int numCands,
|
|
|
Query filter,
|
|
|
Float similarityThreshold,
|
|
|
BitSetProducer parentFilter
|
|
|
) {
|
|
|
- queryVector = validateAndNormalize(queryVector);
|
|
|
- Query knnQuery = switch (elementType) {
|
|
|
- case BYTE -> {
|
|
|
- byte[] bytes = new byte[queryVector.length];
|
|
|
- for (int i = 0; i < queryVector.length; i++) {
|
|
|
- bytes[i] = (byte) queryVector[i];
|
|
|
- }
|
|
|
- yield parentFilter != null
|
|
|
- ? new ESDiversifyingChildrenByteKnnVectorQuery(name(), bytes, filter, numCands, parentFilter)
|
|
|
- : new ESKnnByteVectorQuery(name(), bytes, numCands, filter);
|
|
|
- }
|
|
|
- case FLOAT -> parentFilter != null
|
|
|
- ? new ESDiversifyingChildrenFloatKnnVectorQuery(name(), queryVector, filter, numCands, parentFilter)
|
|
|
- : new ESKnnFloatVectorQuery(name(), queryVector, numCands, filter);
|
|
|
+ if (isIndexed() == false) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ "to perform knn search on field [" + name() + "], its mapping must have [index] set to [true]"
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return switch (getElementType()) {
|
|
|
+ case BYTE -> createKnnByteQuery(queryVector.asByteVector(), numCands, filter, similarityThreshold, parentFilter);
|
|
|
+ case FLOAT -> createKnnFloatQuery(queryVector.asFloatVector(), numCands, filter, similarityThreshold, parentFilter);
|
|
|
};
|
|
|
+ }
|
|
|
+
|
|
|
+ private Query createKnnByteQuery(
|
|
|
+ byte[] queryVector,
|
|
|
+ int numCands,
|
|
|
+ Query filter,
|
|
|
+ Float similarityThreshold,
|
|
|
+ BitSetProducer parentFilter
|
|
|
+ ) {
|
|
|
+ if (queryVector.length != dims) {
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ "the query vector has a different dimension [" + queryVector.length + "] than the index vectors [" + dims + "]"
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
+ if (similarity == VectorSimilarity.DOT_PRODUCT || similarity == VectorSimilarity.COSINE) {
|
|
|
+ float squaredMagnitude = VectorUtil.dotProduct(queryVector, queryVector);
|
|
|
+ elementType.checkVectorMagnitude(similarity, ElementType.errorByteElementsAppender(queryVector), squaredMagnitude);
|
|
|
+ }
|
|
|
+ Query knnQuery = parentFilter != null
|
|
|
+ ? new ESDiversifyingChildrenByteKnnVectorQuery(name(), queryVector, filter, numCands, parentFilter)
|
|
|
+ : new ESKnnByteVectorQuery(name(), queryVector, numCands, filter);
|
|
|
if (similarityThreshold != null) {
|
|
|
knnQuery = new VectorSimilarityQuery(
|
|
|
knnQuery,
|
|
@@ -1228,12 +1308,13 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
return knnQuery;
|
|
|
}
|
|
|
|
|
|
- private float[] validateAndNormalize(float[] queryVector) {
|
|
|
- if (isIndexed() == false) {
|
|
|
- throw new IllegalArgumentException(
|
|
|
- "to perform knn search on field [" + name() + "], its mapping must have [index] set to [true]"
|
|
|
- );
|
|
|
- }
|
|
|
+ private Query createKnnFloatQuery(
|
|
|
+ float[] queryVector,
|
|
|
+ int numCands,
|
|
|
+ Query filter,
|
|
|
+ Float similarityThreshold,
|
|
|
+ BitSetProducer parentFilter
|
|
|
+ ) {
|
|
|
if (queryVector.length != dims) {
|
|
|
throw new IllegalArgumentException(
|
|
|
"the query vector has a different dimension [" + queryVector.length + "] than the index vectors [" + dims + "]"
|
|
@@ -1244,7 +1325,6 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
float squaredMagnitude = VectorUtil.dotProduct(queryVector, queryVector);
|
|
|
elementType.checkVectorMagnitude(similarity, ElementType.errorFloatElementsAppender(queryVector), squaredMagnitude);
|
|
|
if (similarity == VectorSimilarity.COSINE
|
|
|
- && ElementType.FLOAT.equals(elementType)
|
|
|
&& indexVersionCreated.onOrAfter(NORMALIZE_COSINE)
|
|
|
&& isNotUnitVector(squaredMagnitude)) {
|
|
|
float length = (float) Math.sqrt(squaredMagnitude);
|
|
@@ -1254,7 +1334,17 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- return queryVector;
|
|
|
+ Query knnQuery = parentFilter != null
|
|
|
+ ? new ESDiversifyingChildrenFloatKnnVectorQuery(name(), queryVector, filter, numCands, parentFilter)
|
|
|
+ : new ESKnnFloatVectorQuery(name(), queryVector, numCands, filter);
|
|
|
+ if (similarityThreshold != null) {
|
|
|
+ knnQuery = new VectorSimilarityQuery(
|
|
|
+ knnQuery,
|
|
|
+ similarityThreshold,
|
|
|
+ similarity.score(similarityThreshold, elementType, dims)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return knnQuery;
|
|
|
}
|
|
|
|
|
|
VectorSimilarity getSimilarity() {
|
|
@@ -1349,13 +1439,15 @@ public class DenseVectorFieldMapper extends FieldMapper {
|
|
|
int dims = fieldType().dims;
|
|
|
ElementType elementType = fieldType().elementType;
|
|
|
int numBytes = indexCreatedVersion.onOrAfter(MAGNITUDE_STORED_INDEX_VERSION)
|
|
|
- ? dims * elementType.elementBytes + MAGNITUDE_BYTES
|
|
|
- : dims * elementType.elementBytes;
|
|
|
+ ? elementType.getNumBytes(dims) + MAGNITUDE_BYTES
|
|
|
+ : elementType.getNumBytes(dims);
|
|
|
|
|
|
ByteBuffer byteBuffer = elementType.createByteBuffer(indexCreatedVersion, numBytes);
|
|
|
- double dotProduct = elementType.parseKnnVectorToByteBuffer(context, this, byteBuffer);
|
|
|
+ VectorData vectorData = elementType.parseKnnVector(context, this);
|
|
|
+ vectorData.addToBuffer(byteBuffer);
|
|
|
if (indexCreatedVersion.onOrAfter(MAGNITUDE_STORED_INDEX_VERSION)) {
|
|
|
// encode vector magnitude at the end
|
|
|
+ double dotProduct = elementType.computeDotProduct(vectorData);
|
|
|
float vectorMagnitude = (float) Math.sqrt(dotProduct);
|
|
|
byteBuffer.putFloat(vectorMagnitude);
|
|
|
}
|