diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000000000..115e8992ab34d --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,11 @@ +reviews: + profile: chill + high_level_summary: true + auto_review: + enabled: true + drafts: false + tools: + eslint: + enabled: true + gitleaks: + enabled: true diff --git a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java index 9c5d28a4942d3..67cf720dc78b5 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java @@ -110,8 +110,8 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import static org.elasticsearch.cluster.metadata.IndexMetadata.SETTING_INDEX_VERSION_CREATED; @@ -455,777 +455,618 @@ public DenseVectorFieldMapper build(MapperBuilderContext context) { public enum ElementType { - BYTE { + BYTE(new ElementTypeByteBehavior()), + FLOAT(new ElementTypeFloatBehavior()), + BIT(new ElementTypeBitBehavior()); - @Override - public String toString() { - return "byte"; - } - - @Override - public void writeValue(ByteBuffer byteBuffer, float value) { - byteBuffer.put((byte) value); - } + private final ElementTypeBehavior behavior; - @Override - public void readAndWriteValue(ByteBuffer byteBuffer, XContentBuilder b) throws IOException { - b.value(byteBuffer.get()); - } + ElementType(ElementTypeBehavior behavior) { + this.behavior = behavior; + } - private KnnByteVectorField createKnnVectorField(String name, byte[] vector, VectorSimilarityFunction function) { - if (vector == null) { - throw new IllegalArgumentException("vector value must not be null"); - } - FieldType denseVectorFieldType = new FieldType(); - denseVectorFieldType.setVectorAttributes(vector.length, VectorEncoding.BYTE, function); - denseVectorFieldType.freeze(); - return new KnnByteVectorField(name, vector, denseVectorFieldType); - } + @Override + public String toString() { + return super.toString().toLowerCase(Locale.ROOT); + } - @Override - IndexFieldData.Builder fielddataBuilder(DenseVectorFieldType denseVectorFieldType, FieldDataContext fieldDataContext) { - return new VectorIndexFieldData.Builder( - denseVectorFieldType.name(), - CoreValuesSourceType.KEYWORD, - denseVectorFieldType.indexVersionCreated, - this, - denseVectorFieldType.dims, - denseVectorFieldType.indexed, - r -> r - ); - } + public void writeValue(ByteBuffer byteBuffer, float value) { + behavior.writeValue(byteBuffer, value); + } - @Override - StringBuilder checkVectorErrors(float[] vector) { - StringBuilder errors = checkNanAndInfinite(vector); - if (errors != null) { - return errors; - } + public void readAndWriteValue(ByteBuffer byteBuffer, XContentBuilder b) throws IOException { + behavior.readAndWriteValue(byteBuffer, b); + } - for (int index = 0; index < vector.length; ++index) { - float value = vector[index]; + IndexFieldData.Builder fielddataBuilder(DenseVectorFieldType denseVectorFieldType, FieldDataContext fieldDataContext) { + return behavior.fielddataBuilder(denseVectorFieldType, fieldDataContext); + } - if (value % 1.0f != 0.0f) { - errors = new StringBuilder( - "element_type [" - + this - + "] vectors only support non-decimal values but found decimal value [" - + value - + "] at dim [" - + index - + "];" - ); - break; - } + void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException { + behavior.parseKnnVectorAndIndex(context, fieldMapper); + } - if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { - errors = new StringBuilder( - "element_type [" - + this - + "] vectors only support integers between [" - + Byte.MIN_VALUE - + ", " - + Byte.MAX_VALUE - + "] but found [" - + value - + "] at dim [" - + index - + "];" - ); - break; - } - } + public VectorData parseKnnVector( + DocumentParserContext context, + int dims, + IntBooleanConsumer dimChecker, + VectorSimilarity similarity + ) throws IOException { + return behavior.parseKnnVector(context, dims, dimChecker, similarity); + } - return errors; - } + public int getNumBytes(int dimensions) { + return behavior.getNumBytes(dimensions); + } - @Override - void checkVectorMagnitude( - VectorSimilarity similarity, - Function appender, - float squaredMagnitude - ) { - StringBuilder errorBuilder = null; - - if (similarity == VectorSimilarity.COSINE && Math.sqrt(squaredMagnitude) == 0.0f) { - errorBuilder = new StringBuilder( - "The [" + VectorSimilarity.COSINE + "] similarity does not support vectors with zero magnitude." - ); - } + public ByteBuffer createByteBuffer(IndexVersion indexVersion, int numBytes) { + return behavior.createByteBuffer(indexVersion, numBytes); + } - if (errorBuilder != null) { - throw new IllegalArgumentException(appender.apply(errorBuilder).toString()); + /** + * Checks the input {@code vector} is one of the {@code possibleTypes}, + * and returns the first type that it matches + */ + public static ElementType checkValidVector(float[] vector, ElementType... possibleTypes) { + assert possibleTypes.length != 0; + // we're looking for one valid allowed type + // assume the types are in order of specificity + StringBuilder[] errors = new StringBuilder[possibleTypes.length]; + for (int i = 0; i < possibleTypes.length; i++) { + StringBuilder error = possibleTypes[i].behavior.checkVectorErrors(vector); + if (error == null) { + // this one works - use it + return possibleTypes[i]; + } else { + errors[i] = error; } } - @Override - public double computeSquaredMagnitude(VectorData vectorData) { - return VectorUtil.dotProduct(vectorData.asByteVector(), vectorData.asByteVector()); - } - - private VectorData parseVectorArray( - DocumentParserContext context, - int dims, - IntBooleanConsumer dimChecker, - VectorSimilarity similarity - ) throws IOException { - int index = 0; - byte[] vector = new byte[dims]; - float squaredMagnitude = 0; - for (XContentParser.Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser() - .nextToken()) { - dimChecker.accept(index, false); - ensureExpectedToken(Token.VALUE_NUMBER, token, context.parser()); - final int value; - if (context.parser().numberType() != XContentParser.NumberType.INT) { - float floatValue = context.parser().floatValue(true); - if (floatValue % 1.0f != 0.0f) { - throw new IllegalArgumentException( - "element_type [" - + this - + "] vectors only support non-decimal values but found decimal value [" - + floatValue - + "] at dim [" - + index - + "];" - ); - } - value = (int) floatValue; - } else { - 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 - + "];" - ); - } - vector[index++] = (byte) value; - squaredMagnitude += value * value; + // oh dear, none of the possible types work with this vector. Generate the error message and throw. + StringBuilder message = new StringBuilder(); + for (int i = 0; i < possibleTypes.length; i++) { + if (i > 0) { + message.append(" "); } - dimChecker.accept(index, true); - checkVectorMagnitude(similarity, errorByteElementsAppender(vector), squaredMagnitude); - return VectorData.fromBytes(vector); + message.append("Vector is not a ").append(possibleTypes[i]).append(" vector: ").append(errors[i]); } + throw new IllegalArgumentException(ElementTypeFloatBehavior.appendErrorElements(message, vector).toString()); + } - private VectorData parseHexEncodedVector( - DocumentParserContext context, - IntBooleanConsumer dimChecker, - VectorSimilarity similarity - ) throws IOException { - byte[] decodedVector = HexFormat.of().parseHex(context.parser().text()); - dimChecker.accept(decodedVector.length, true); - VectorData vectorData = VectorData.fromBytes(decodedVector); - double squaredMagnitude = computeSquaredMagnitude(vectorData); - checkVectorMagnitude(similarity, errorByteElementsAppender(decodedVector), (float) squaredMagnitude); - return vectorData; - } + public void checkVectorBounds(float[] vector) { + behavior.checkVectorBounds(vector); + } - @Override - public VectorData parseKnnVector( - DocumentParserContext context, - int dims, - IntBooleanConsumer dimChecker, - VectorSimilarity similarity - ) throws IOException { - XContentParser.Token token = context.parser().currentToken(); - return switch (token) { - case START_ARRAY -> parseVectorArray(context, dims, dimChecker, similarity); - case VALUE_STRING -> parseHexEncodedVector(context, dimChecker, similarity); - default -> throw new ParsingException( - context.parser().getTokenLocation(), - format("Unsupported type [%s] for provided value [%s]", token, context.parser().text()) - ); - }; - } + void checkVectorMagnitude(VectorSimilarity similarity, UnaryOperator errorElementsAppender, float squaredMagnitude) { + behavior.checkVectorMagnitude(similarity, errorElementsAppender, squaredMagnitude); + } - @Override - public void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException { - VectorData vectorData = parseKnnVector(context, fieldMapper.fieldType().dims, (i, end) -> { - if (end) { - fieldMapper.checkDimensionMatches(i, context); - } else { - fieldMapper.checkDimensionExceeded(i, context); - } - }, fieldMapper.fieldType().similarity); - Field field = createKnnVectorField( - fieldMapper.fieldType().name(), - vectorData.asByteVector(), - fieldMapper.fieldType().similarity.vectorSimilarityFunction(fieldMapper.indexCreatedVersion, this) - ); - context.doc().addWithKey(fieldMapper.fieldType().name(), field); - } + public void checkDimensions(Integer dvDims, int qvDims) { + behavior.checkDimensions(dvDims, qvDims); + } - @Override - public int getNumBytes(int dimensions) { - return dimensions; - } + public int parseDimensionCount(DocumentParserContext context) throws IOException { + return behavior.parseDimensionCount(context); + } - @Override - public ByteBuffer createByteBuffer(IndexVersion indexVersion, int numBytes) { - return ByteBuffer.wrap(new byte[numBytes]); - } + public double computeSquaredMagnitude(VectorData vectorData) { + return behavior.computeSquaredMagnitude(vectorData); + } - @Override - public int parseDimensionCount(DocumentParserContext context) throws IOException { - XContentParser.Token currentToken = context.parser().currentToken(); - return switch (currentToken) { - case START_ARRAY -> { - int index = 0; - for (Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser().nextToken()) { - index++; - } - yield index; - } - case VALUE_STRING -> { - byte[] decodedVector = HexFormat.of().parseHex(context.parser().text()); - yield decodedVector.length; - } - default -> throw new ParsingException( - context.parser().getTokenLocation(), - format("Unsupported type [%s] for provided value [%s]", currentToken, context.parser().text()) - ); - }; - } - }, + public static ElementType fromString(String name) { + return valueOf(name.trim().toUpperCase(Locale.ROOT)); + } + } - FLOAT { + private abstract static class ElementTypeBehavior { - @Override - public String toString() { - return "float"; - } + abstract ElementType elementType(); - @Override - public void writeValue(ByteBuffer byteBuffer, float value) { - byteBuffer.putFloat(value); - } + abstract void writeValue(ByteBuffer byteBuffer, float value); - @Override - public void readAndWriteValue(ByteBuffer byteBuffer, XContentBuilder b) throws IOException { - b.value(byteBuffer.getFloat()); - } + abstract void readAndWriteValue(ByteBuffer byteBuffer, XContentBuilder b) throws IOException; - private KnnFloatVectorField createKnnVectorField(String name, float[] vector, VectorSimilarityFunction function) { - if (vector == null) { - throw new IllegalArgumentException("vector value must not be null"); - } - FieldType denseVectorFieldType = new FieldType(); - denseVectorFieldType.setVectorAttributes(vector.length, VectorEncoding.FLOAT32, function); - denseVectorFieldType.freeze(); - return new KnnFloatVectorField(name, vector, denseVectorFieldType); + abstract IndexFieldData.Builder fielddataBuilder(DenseVectorFieldType denseVectorFieldType, FieldDataContext fieldDataContext); + + abstract void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException; + + abstract VectorData parseKnnVector( + DocumentParserContext context, + int dims, + IntBooleanConsumer dimChecker, + VectorSimilarity similarity + ) throws IOException; + + abstract int getNumBytes(int dimensions); + + abstract ByteBuffer createByteBuffer(IndexVersion indexVersion, int numBytes); + + void checkVectorBounds(float[] vector) { + StringBuilder errors = checkVectorErrors(vector); + if (errors != null) { + throw new IllegalArgumentException(ElementTypeFloatBehavior.appendErrorElements(errors, vector).toString()); } + } - @Override - IndexFieldData.Builder fielddataBuilder(DenseVectorFieldType denseVectorFieldType, FieldDataContext fieldDataContext) { - return new VectorIndexFieldData.Builder( - denseVectorFieldType.name(), - CoreValuesSourceType.KEYWORD, - denseVectorFieldType.indexVersionCreated, - this, - denseVectorFieldType.dims, - denseVectorFieldType.indexed, - denseVectorFieldType.isNormalized() && denseVectorFieldType.indexed ? r -> new FilterLeafReader(r) { - @Override - public CacheHelper getCoreCacheHelper() { - return r.getCoreCacheHelper(); - } + StringBuilder checkVectorErrors(float[] vector) { + return checkNanAndInfinite(vector); + } - @Override - public CacheHelper getReaderCacheHelper() { - return r.getReaderCacheHelper(); - } + abstract void checkVectorMagnitude( + VectorSimilarity similarity, + UnaryOperator errorElementsAppender, + float squaredMagnitude + ); - @Override - public FloatVectorValues getFloatVectorValues(String fieldName) throws IOException { - FloatVectorValues values = in.getFloatVectorValues(fieldName); - if (values == null) { - return null; - } - return new DenormalizedCosineFloatVectorValues( - values, - in.getNumericDocValues(fieldName + COSINE_MAGNITUDE_FIELD_SUFFIX) - ); - } - } : r -> r + void checkDimensions(Integer dvDims, int qvDims) { + if (dvDims != null && dvDims != qvDims) { + throw new IllegalArgumentException( + "The query vector has a different number of dimensions [" + qvDims + "] than the document vectors [" + dvDims + "]." ); } + } - @Override - StringBuilder checkVectorErrors(float[] vector) { - return checkNanAndInfinite(vector); + int parseDimensionCount(DocumentParserContext context) throws IOException { + int index = 0; + for (Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser().nextToken()) { + index++; } + return index; + } - @Override - void checkVectorMagnitude( - VectorSimilarity similarity, - Function appender, - float squaredMagnitude - ) { - StringBuilder errorBuilder = null; - - if (Float.isNaN(squaredMagnitude) || Float.isInfinite(squaredMagnitude)) { + StringBuilder checkNanAndInfinite(float[] vector) { + StringBuilder errorBuilder = null; + + for (int index = 0; index < vector.length; ++index) { + float value = vector[index]; + + if (Float.isNaN(value)) { errorBuilder = new StringBuilder( - "NaN or Infinite magnitude detected, this usually means the vector values are too extreme to fit within a float." + "element_type [" + + elementType() + + "] vectors do not support NaN values but found [" + + value + + "] at dim [" + + index + + "];" ); - } - if (errorBuilder != null) { - throw new IllegalArgumentException(appender.apply(errorBuilder).toString()); + break; } - if (similarity == VectorSimilarity.DOT_PRODUCT && isNotUnitVector(squaredMagnitude)) { - errorBuilder = new StringBuilder( - "The [" + VectorSimilarity.DOT_PRODUCT + "] similarity can only be used with unit-length vectors." - ); - } else if (similarity == VectorSimilarity.COSINE && Math.sqrt(squaredMagnitude) == 0.0f) { + if (Float.isInfinite(value)) { errorBuilder = new StringBuilder( - "The [" + VectorSimilarity.COSINE + "] similarity does not support vectors with zero magnitude." + "element_type [" + + elementType() + + "] vectors do not support infinite values but found [" + + value + + "] at dim [" + + index + + "];" ); - } - - if (errorBuilder != null) { - throw new IllegalArgumentException(appender.apply(errorBuilder).toString()); + break; } } - @Override - public double computeSquaredMagnitude(VectorData vectorData) { - return VectorUtil.dotProduct(vectorData.asFloatVector(), vectorData.asFloatVector()); - } + return errorBuilder; + } - @Override - public void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException { - int index = 0; - float[] vector = new float[fieldMapper.fieldType().dims]; - float squaredMagnitude = 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()); - - float value = context.parser().floatValue(true); - vector[index++] = value; - squaredMagnitude += value * value; - } - fieldMapper.checkDimensionMatches(index, context); - checkVectorBounds(vector); - checkVectorMagnitude(fieldMapper.fieldType().similarity, errorFloatElementsAppender(vector), squaredMagnitude); - if (fieldMapper.fieldType().isNormalized() && isNotUnitVector(squaredMagnitude)) { - float length = (float) Math.sqrt(squaredMagnitude); - for (int i = 0; i < vector.length; i++) { - vector[i] /= length; - } - final String fieldName = fieldMapper.fieldType().name() + COSINE_MAGNITUDE_FIELD_SUFFIX; - Field magnitudeField = new FloatDocValuesField(fieldName, length); - context.doc().addWithKey(fieldName, magnitudeField); - } - Field field = createKnnVectorField( - fieldMapper.fieldType().name(), - vector, - fieldMapper.fieldType().similarity.vectorSimilarityFunction(fieldMapper.indexCreatedVersion, this) - ); - context.doc().addWithKey(fieldMapper.fieldType().name(), field); - } + public abstract double computeSquaredMagnitude(VectorData vectorData); + } - @Override - public VectorData parseKnnVector( - DocumentParserContext context, - int dims, - IntBooleanConsumer dimChecker, - VectorSimilarity similarity - ) throws IOException { - int index = 0; - float squaredMagnitude = 0; - float[] vector = new float[dims]; - for (Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser().nextToken()) { - dimChecker.accept(index, false); - ensureExpectedToken(Token.VALUE_NUMBER, token, context.parser()); - float value = context.parser().floatValue(true); - vector[index] = value; - squaredMagnitude += value * value; - index++; - } - dimChecker.accept(index, true); - checkVectorBounds(vector); - checkVectorMagnitude(similarity, errorFloatElementsAppender(vector), squaredMagnitude); - return VectorData.fromFloats(vector); - } + private static class ElementTypeByteBehavior extends ElementTypeBehavior { - @Override - public int getNumBytes(int dimensions) { - return dimensions * Float.BYTES; - } + @Override + ElementType elementType() { + return ElementType.BYTE; + } - @Override - public ByteBuffer createByteBuffer(IndexVersion indexVersion, int numBytes) { - return indexVersion.onOrAfter(LITTLE_ENDIAN_FLOAT_STORED_INDEX_VERSION) - ? ByteBuffer.wrap(new byte[numBytes]).order(ByteOrder.LITTLE_ENDIAN) - : ByteBuffer.wrap(new byte[numBytes]); - } - }, + @Override + public void writeValue(ByteBuffer byteBuffer, float value) { + byteBuffer.put((byte) value); + } - BIT { + @Override + public void readAndWriteValue(ByteBuffer byteBuffer, XContentBuilder b) throws IOException { + b.value(byteBuffer.get()); + } - @Override - public String toString() { - return "bit"; + private KnnByteVectorField createKnnVectorField(String name, byte[] vector, VectorSimilarityFunction function) { + if (vector == null) { + throw new IllegalArgumentException("vector value must not be null"); } + FieldType denseVectorFieldType = new FieldType(); + denseVectorFieldType.setVectorAttributes(vector.length, VectorEncoding.BYTE, function); + denseVectorFieldType.freeze(); + return new KnnByteVectorField(name, vector, denseVectorFieldType); + } - @Override - public void writeValue(ByteBuffer byteBuffer, float value) { - byteBuffer.put((byte) value); - } + @Override + IndexFieldData.Builder fielddataBuilder(DenseVectorFieldType denseVectorFieldType, FieldDataContext fieldDataContext) { + return new VectorIndexFieldData.Builder( + denseVectorFieldType.name(), + CoreValuesSourceType.KEYWORD, + denseVectorFieldType.indexVersionCreated, + elementType(), + denseVectorFieldType.dims, + denseVectorFieldType.indexed, + r -> r + ); + } - @Override - public void readAndWriteValue(ByteBuffer byteBuffer, XContentBuilder b) throws IOException { - b.value(byteBuffer.get()); + @Override + StringBuilder checkVectorErrors(float[] vector) { + StringBuilder errors = super.checkNanAndInfinite(vector); + if (errors != null) { + return errors; } - private KnnByteVectorField createKnnVectorField(String name, byte[] vector, VectorSimilarityFunction function) { - if (vector == null) { - throw new IllegalArgumentException("vector value must not be null"); - } - FieldType denseVectorFieldType = new FieldType(); - denseVectorFieldType.setVectorAttributes(vector.length, VectorEncoding.BYTE, function); - denseVectorFieldType.freeze(); - return new KnnByteVectorField(name, vector, denseVectorFieldType); - } + for (int index = 0; index < vector.length; ++index) { + float value = vector[index]; - @Override - IndexFieldData.Builder fielddataBuilder(DenseVectorFieldType denseVectorFieldType, FieldDataContext fieldDataContext) { - return new VectorIndexFieldData.Builder( - denseVectorFieldType.name(), - CoreValuesSourceType.KEYWORD, - denseVectorFieldType.indexVersionCreated, - this, - denseVectorFieldType.dims, - denseVectorFieldType.indexed, - r -> r - ); - } + if (value % 1.0f != 0.0f) { + errors = new StringBuilder( + "element_type [" + + elementType() + + "] vectors only support non-decimal values but found decimal value [" + + value + + "] at dim [" + + index + + "];" + ); + break; + } - @Override - StringBuilder checkVectorErrors(float[] vector) { - StringBuilder errors = checkNanAndInfinite(vector); - if (errors != null) { - return errors; + if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { + errors = new StringBuilder( + "element_type [" + + elementType() + + "] vectors only support integers between [" + + Byte.MIN_VALUE + + ", " + + Byte.MAX_VALUE + + "] but found [" + + value + + "] at dim [" + + index + + "];" + ); + break; } + } - for (int index = 0; index < vector.length; ++index) { - float value = vector[index]; + return errors; + } - if (value % 1.0f != 0.0f) { - errors = new StringBuilder( - "element_type [" - + this - + "] vectors only support non-decimal values but found decimal value [" - + value - + "] at dim [" - + index - + "];" - ); - break; - } + @Override + void checkVectorMagnitude(VectorSimilarity similarity, UnaryOperator appender, float squaredMagnitude) { + StringBuilder errorBuilder = null; - if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { - errors = new StringBuilder( - "element_type [" - + this - + "] vectors only support integers between [" - + Byte.MIN_VALUE - + ", " - + Byte.MAX_VALUE - + "] but found [" - + value - + "] at dim [" - + index - + "];" - ); - break; - } - } + if (similarity == VectorSimilarity.COSINE && Math.sqrt(squaredMagnitude) == 0.0f) { + errorBuilder = new StringBuilder( + "The [" + VectorSimilarity.COSINE + "] similarity does not support vectors with zero magnitude." + ); + } - return errors; + if (errorBuilder != null) { + throw new IllegalArgumentException(appender.apply(errorBuilder).toString()); } + } - @Override - void checkVectorMagnitude( - VectorSimilarity similarity, - Function appender, - float squaredMagnitude - ) {} + @Override + public double computeSquaredMagnitude(VectorData vectorData) { + return VectorUtil.dotProduct(vectorData.asByteVector(), vectorData.asByteVector()); + } - @Override - public double computeSquaredMagnitude(VectorData vectorData) { - int count = 0; - int i = 0; - byte[] byteBits = vectorData.asByteVector(); - for (int upperBound = byteBits.length & -8; i < upperBound; i += 8) { - count += Long.bitCount((long) BitUtil.VH_NATIVE_LONG.get(byteBits, i)); + @Override + public void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException { + VectorData vectorData = parseKnnVector(context, fieldMapper.fieldType().dims, (i, end) -> { + if (end) { + fieldMapper.checkDimensionMatches(i, context); + } else { + fieldMapper.checkDimensionExceeded(i, context); } + }, fieldMapper.fieldType().similarity); + Field field = createKnnVectorField( + fieldMapper.fieldType().name(), + vectorData.asByteVector(), + fieldMapper.fieldType().similarity.vectorSimilarityFunction(fieldMapper.indexCreatedVersion, elementType()) + ); + context.doc().addWithKey(fieldMapper.fieldType().name(), field); + } - while (i < byteBits.length) { - count += Integer.bitCount(byteBits[i] & 255); - ++i; - } - return count; - } - - private VectorData parseVectorArray( - DocumentParserContext context, - int dims, - IntBooleanConsumer dimChecker, - VectorSimilarity similarity - ) throws IOException { - int index = 0; - byte[] vector = new byte[dims / Byte.SIZE]; - for (XContentParser.Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser() - .nextToken()) { - dimChecker.accept(index * Byte.SIZE, false); - ensureExpectedToken(Token.VALUE_NUMBER, token, context.parser()); - final int value; - if (context.parser().numberType() != XContentParser.NumberType.INT) { - float floatValue = context.parser().floatValue(true); - if (floatValue % 1.0f != 0.0f) { - throw new IllegalArgumentException( - "element_type [" - + this - + "] vectors only support non-decimal values but found decimal value [" - + floatValue - + "] at dim [" - + index - + "];" - ); - } - value = (int) floatValue; - } else { - value = context.parser().intValue(true); - } - if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { + VectorData parseVectorArray(DocumentParserContext context, int dims, IntBooleanConsumer dimChecker, VectorSimilarity similarity) + throws IOException { + int index = 0; + byte[] vector = new byte[dims]; + float squaredMagnitude = 0; + for (XContentParser.Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser() + .nextToken()) { + dimChecker.accept(index, false); + ensureExpectedToken(Token.VALUE_NUMBER, token, context.parser()); + final int value; + if (context.parser().numberType() != XContentParser.NumberType.INT) { + float floatValue = context.parser().floatValue(true); + if (floatValue % 1.0f != 0.0f) { throw new IllegalArgumentException( "element_type [" - + this - + "] vectors only support integers between [" - + Byte.MIN_VALUE - + ", " - + Byte.MAX_VALUE - + "] but found [" - + value + + elementType() + + "] vectors only support non-decimal values but found decimal value [" + + floatValue + "] at dim [" + index + "];" ); } - vector[index++] = (byte) value; + value = (int) floatValue; + } else { + value = context.parser().intValue(true); } - dimChecker.accept(index * Byte.SIZE, true); - return VectorData.fromBytes(vector); - } - - private VectorData parseHexEncodedVector(DocumentParserContext context, IntBooleanConsumer dimChecker) throws IOException { - byte[] decodedVector = HexFormat.of().parseHex(context.parser().text()); - dimChecker.accept(decodedVector.length * Byte.SIZE, true); - return VectorData.fromBytes(decodedVector); - } - - @Override - public VectorData parseKnnVector( - DocumentParserContext context, - int dims, - IntBooleanConsumer dimChecker, - VectorSimilarity similarity - ) throws IOException { - XContentParser.Token token = context.parser().currentToken(); - return switch (token) { - case START_ARRAY -> parseVectorArray(context, dims, dimChecker, similarity); - case VALUE_STRING -> parseHexEncodedVector(context, dimChecker); - 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.fieldType().dims, (i, end) -> { - if (end) { - fieldMapper.checkDimensionMatches(i, context); - } else { - fieldMapper.checkDimensionExceeded(i, context); - } - }, fieldMapper.fieldType().similarity); - Field field = createKnnVectorField( - fieldMapper.fieldType().name(), - vectorData.asByteVector(), - fieldMapper.fieldType().similarity.vectorSimilarityFunction(fieldMapper.indexCreatedVersion, this) - ); - context.doc().addWithKey(fieldMapper.fieldType().name(), field); - } - - @Override - public int getNumBytes(int dimensions) { - assert dimensions % Byte.SIZE == 0; - return dimensions / Byte.SIZE; - } - - @Override - public ByteBuffer createByteBuffer(IndexVersion indexVersion, int numBytes) { - return ByteBuffer.wrap(new byte[numBytes]); - } - - @Override - public int parseDimensionCount(DocumentParserContext context) throws IOException { - XContentParser.Token currentToken = context.parser().currentToken(); - return switch (currentToken) { - case START_ARRAY -> { - int index = 0; - for (Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser().nextToken()) { - index++; - } - yield index * Byte.SIZE; - } - case VALUE_STRING -> { - byte[] decodedVector = HexFormat.of().parseHex(context.parser().text()); - yield decodedVector.length * Byte.SIZE; - } - default -> throw new ParsingException( - context.parser().getTokenLocation(), - format("Unsupported type [%s] for provided value [%s]", currentToken, context.parser().text()) - ); - }; - } - - @Override - public void checkDimensions(Integer dvDims, int qvDims) { - if (dvDims != null && dvDims != qvDims * Byte.SIZE) { + if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { throw new IllegalArgumentException( - "The query vector has a different number of dimensions [" - + qvDims * Byte.SIZE - + "] than the document vectors [" - + dvDims - + "]." + "element_type [" + + elementType() + + "] vectors only support integers between [" + + Byte.MIN_VALUE + + ", " + + Byte.MAX_VALUE + + "] but found [" + + value + + "] at dim [" + + index + + "];" ); } + vector[index++] = (byte) value; + squaredMagnitude += value * value; } - }; - - public abstract void writeValue(ByteBuffer byteBuffer, float value); - - public abstract void readAndWriteValue(ByteBuffer byteBuffer, XContentBuilder b) throws IOException; - - abstract IndexFieldData.Builder fielddataBuilder(DenseVectorFieldType denseVectorFieldType, FieldDataContext fieldDataContext); + dimChecker.accept(index, true); + checkVectorMagnitude(similarity, errorElementsAppender(vector), squaredMagnitude); + return VectorData.fromBytes(vector); + } - abstract void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException; + VectorData parseHexEncodedVector(DocumentParserContext context, IntBooleanConsumer dimChecker, VectorSimilarity similarity) + throws IOException { + byte[] decodedVector = HexFormat.of().parseHex(context.parser().text()); + dimChecker.accept(decodedVector.length, true); + VectorData vectorData = VectorData.fromBytes(decodedVector); + double squaredMagnitude = computeSquaredMagnitude(vectorData); + checkVectorMagnitude(similarity, errorElementsAppender(decodedVector), (float) squaredMagnitude); + return vectorData; + } - public abstract VectorData parseKnnVector( + @Override + public VectorData parseKnnVector( DocumentParserContext context, int dims, IntBooleanConsumer dimChecker, VectorSimilarity similarity - ) throws IOException; + ) throws IOException { + XContentParser.Token token = context.parser().currentToken(); + return switch (token) { + case START_ARRAY -> parseVectorArray(context, dims, dimChecker, similarity); + case VALUE_STRING -> parseHexEncodedVector(context, dimChecker, similarity); + default -> throw new ParsingException( + context.parser().getTokenLocation(), + format("Unsupported type [%s] for provided value [%s]", token, context.parser().text()) + ); + }; + } - public abstract int getNumBytes(int dimensions); + @Override + public int getNumBytes(int dimensions) { + return dimensions; + } - public abstract ByteBuffer createByteBuffer(IndexVersion indexVersion, int numBytes); + @Override + public ByteBuffer createByteBuffer(IndexVersion indexVersion, int numBytes) { + return ByteBuffer.wrap(new byte[numBytes]); + } - /** - * Checks the input {@code vector} is one of the {@code possibleTypes}, - * and returns the first type that it matches - */ - public static ElementType checkValidVector(float[] vector, ElementType... possibleTypes) { - assert possibleTypes.length != 0; - // we're looking for one valid allowed type - // assume the types are in order of specificity - StringBuilder[] errors = new StringBuilder[possibleTypes.length]; - for (int i = 0; i < possibleTypes.length; i++) { - StringBuilder error = possibleTypes[i].checkVectorErrors(vector); - if (error == null) { - // this one works - use it - return possibleTypes[i]; - } else { - errors[i] = error; + @Override + public int parseDimensionCount(DocumentParserContext context) throws IOException { + XContentParser.Token currentToken = context.parser().currentToken(); + return switch (currentToken) { + case START_ARRAY -> { + int index = 0; + for (Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser().nextToken()) { + index++; + } + yield index; } - } + case VALUE_STRING -> { + byte[] decodedVector = HexFormat.of().parseHex(context.parser().text()); + yield decodedVector.length; + } + default -> throw new ParsingException( + context.parser().getTokenLocation(), + format("Unsupported type [%s] for provided value [%s]", currentToken, context.parser().text()) + ); + }; + } - // oh dear, none of the possible types work with this vector. Generate the error message and throw. - StringBuilder message = new StringBuilder(); - for (int i = 0; i < possibleTypes.length; i++) { + static StringBuilder appendErrorElements(StringBuilder errorBuilder, byte[] vector) { + // Include the first five elements of the invalid vector in the error message + errorBuilder.append(" Preview of invalid vector: ["); + for (int i = 0; i < Math.min(5, vector.length); i++) { if (i > 0) { - message.append(" "); + errorBuilder.append(", "); } - message.append("Vector is not a ").append(possibleTypes[i]).append(" vector: ").append(errors[i]); + errorBuilder.append(vector[i]); } - throw new IllegalArgumentException(appendErrorElements(message, vector).toString()); + if (vector.length >= 5) { + errorBuilder.append(", ..."); + } + errorBuilder.append("]"); + return errorBuilder; } - public void checkVectorBounds(float[] vector) { - StringBuilder errors = checkVectorErrors(vector); - if (errors != null) { - throw new IllegalArgumentException(appendErrorElements(errors, vector).toString()); + static UnaryOperator errorElementsAppender(byte[] vector) { + return sb -> appendErrorElements(sb, vector); + } + } + + private static class ElementTypeFloatBehavior extends ElementTypeBehavior { + + @Override + ElementType elementType() { + return ElementType.FLOAT; + } + + @Override + public void writeValue(ByteBuffer byteBuffer, float value) { + byteBuffer.putFloat(value); + } + + @Override + public void readAndWriteValue(ByteBuffer byteBuffer, XContentBuilder b) throws IOException { + b.value(byteBuffer.getFloat()); + } + + private KnnFloatVectorField createKnnVectorField(String name, float[] vector, VectorSimilarityFunction function) { + if (vector == null) { + throw new IllegalArgumentException("vector value must not be null"); } + FieldType denseVectorFieldType = new FieldType(); + denseVectorFieldType.setVectorAttributes(vector.length, VectorEncoding.FLOAT32, function); + denseVectorFieldType.freeze(); + return new KnnFloatVectorField(name, vector, denseVectorFieldType); } - abstract StringBuilder checkVectorErrors(float[] vector); + @Override + IndexFieldData.Builder fielddataBuilder(DenseVectorFieldType denseVectorFieldType, FieldDataContext fieldDataContext) { + return new VectorIndexFieldData.Builder( + denseVectorFieldType.name(), + CoreValuesSourceType.KEYWORD, + denseVectorFieldType.indexVersionCreated, + elementType(), + denseVectorFieldType.dims, + denseVectorFieldType.indexed, + denseVectorFieldType.isNormalized() && denseVectorFieldType.indexed ? r -> new FilterLeafReader(r) { + @Override + public CacheHelper getCoreCacheHelper() { + return r.getCoreCacheHelper(); + } + + @Override + public CacheHelper getReaderCacheHelper() { + return r.getReaderCacheHelper(); + } - abstract void checkVectorMagnitude( - VectorSimilarity similarity, - Function errorElementsAppender, - float squaredMagnitude - ); + @Override + public FloatVectorValues getFloatVectorValues(String fieldName) throws IOException { + FloatVectorValues values = in.getFloatVectorValues(fieldName); + if (values == null) { + return null; + } + return new DenormalizedCosineFloatVectorValues( + values, + in.getNumericDocValues(fieldName + COSINE_MAGNITUDE_FIELD_SUFFIX) + ); + } + } : r -> r + ); + } - public void checkDimensions(Integer dvDims, int qvDims) { - if (dvDims != null && dvDims != qvDims) { - throw new IllegalArgumentException( - "The query vector has a different number of dimensions [" + qvDims + "] than the document vectors [" + dvDims + "]." + @Override + void checkVectorMagnitude(VectorSimilarity similarity, UnaryOperator appender, float squaredMagnitude) { + StringBuilder errorBuilder = null; + + if (Float.isNaN(squaredMagnitude) || Float.isInfinite(squaredMagnitude)) { + errorBuilder = new StringBuilder( + "NaN or Infinite magnitude detected, this usually means the vector values are too extreme to fit within a float." + ); + } + if (errorBuilder != null) { + throw new IllegalArgumentException(appender.apply(errorBuilder).toString()); + } + + if (similarity == VectorSimilarity.DOT_PRODUCT && isNotUnitVector(squaredMagnitude)) { + errorBuilder = new StringBuilder( + "The [" + VectorSimilarity.DOT_PRODUCT + "] similarity can only be used with unit-length vectors." ); + } else if (similarity == VectorSimilarity.COSINE && Math.sqrt(squaredMagnitude) == 0.0f) { + errorBuilder = new StringBuilder( + "The [" + VectorSimilarity.COSINE + "] similarity does not support vectors with zero magnitude." + ); + } + + if (errorBuilder != null) { + throw new IllegalArgumentException(appender.apply(errorBuilder).toString()); } } - public int parseDimensionCount(DocumentParserContext context) throws IOException { + @Override + public double computeSquaredMagnitude(VectorData vectorData) { + return VectorUtil.dotProduct(vectorData.asFloatVector(), vectorData.asFloatVector()); + } + + @Override + public void parseKnnVectorAndIndex(DocumentParserContext context, DenseVectorFieldMapper fieldMapper) throws IOException { int index = 0; + float[] vector = new float[fieldMapper.fieldType().dims]; + float squaredMagnitude = 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()); + + float value = context.parser().floatValue(true); + vector[index++] = value; + squaredMagnitude += value * value; + } + fieldMapper.checkDimensionMatches(index, context); + checkVectorBounds(vector); + checkVectorMagnitude(fieldMapper.fieldType().similarity, errorElementsAppender(vector), squaredMagnitude); + if (fieldMapper.fieldType().isNormalized() && isNotUnitVector(squaredMagnitude)) { + float length = (float) Math.sqrt(squaredMagnitude); + for (int i = 0; i < vector.length; i++) { + vector[i] /= length; + } + final String fieldName = fieldMapper.fieldType().name() + COSINE_MAGNITUDE_FIELD_SUFFIX; + Field magnitudeField = new FloatDocValuesField(fieldName, length); + context.doc().addWithKey(fieldName, magnitudeField); + } + Field field = createKnnVectorField( + fieldMapper.fieldType().name(), + vector, + fieldMapper.fieldType().similarity.vectorSimilarityFunction(fieldMapper.indexCreatedVersion, elementType()) + ); + context.doc().addWithKey(fieldMapper.fieldType().name(), field); + } + + @Override + public VectorData parseKnnVector( + DocumentParserContext context, + int dims, + IntBooleanConsumer dimChecker, + VectorSimilarity similarity + ) throws IOException { + int index = 0; + float squaredMagnitude = 0; + float[] vector = new float[dims]; + for (Token token = context.parser().nextToken(); token != Token.END_ARRAY; token = context.parser().nextToken()) { + dimChecker.accept(index, false); + ensureExpectedToken(Token.VALUE_NUMBER, token, context.parser()); + float value = context.parser().floatValue(true); + vector[index] = value; + squaredMagnitude += value * value; index++; } - return index; + dimChecker.accept(index, true); + checkVectorBounds(vector); + checkVectorMagnitude(similarity, errorElementsAppender(vector), squaredMagnitude); + return VectorData.fromFloats(vector); } - StringBuilder checkNanAndInfinite(float[] vector) { - StringBuilder errorBuilder = null; - - for (int index = 0; index < vector.length; ++index) { - float value = vector[index]; - - if (Float.isNaN(value)) { - errorBuilder = new StringBuilder( - "element_type [" + this + "] vectors do not support NaN values but found [" + value + "] at dim [" + index + "];" - ); - break; - } - - if (Float.isInfinite(value)) { - errorBuilder = new StringBuilder( - "element_type [" - + this - + "] vectors do not support infinite values but found [" - + value - + "] at dim [" - + index - + "];" - ); - break; - } - } + @Override + public int getNumBytes(int dimensions) { + return dimensions * Float.BYTES; + } - return errorBuilder; + @Override + public ByteBuffer createByteBuffer(IndexVersion indexVersion, int numBytes) { + return indexVersion.onOrAfter(LITTLE_ENDIAN_FLOAT_STORED_INDEX_VERSION) + ? ByteBuffer.wrap(new byte[numBytes]).order(ByteOrder.LITTLE_ENDIAN) + : ByteBuffer.wrap(new byte[numBytes]); } static StringBuilder appendErrorElements(StringBuilder errorBuilder, float[] vector) { @@ -1244,34 +1085,70 @@ static StringBuilder appendErrorElements(StringBuilder errorBuilder, float[] vec return errorBuilder; } - static StringBuilder appendErrorElements(StringBuilder errorBuilder, byte[] vector) { - // Include the first five elements of the invalid vector in the error message - errorBuilder.append(" Preview of invalid vector: ["); - for (int i = 0; i < Math.min(5, vector.length); i++) { - if (i > 0) { - errorBuilder.append(", "); - } - errorBuilder.append(vector[i]); + static UnaryOperator errorElementsAppender(float[] vector) { + return sb -> appendErrorElements(sb, vector); + } + } + + private static class ElementTypeBitBehavior extends ElementTypeByteBehavior { + + @Override + ElementType elementType() { + return ElementType.BIT; + } + + @Override + void checkVectorMagnitude(VectorSimilarity similarity, UnaryOperator appender, float squaredMagnitude) {} + + @Override + public double computeSquaredMagnitude(VectorData vectorData) { + int count = 0; + int i = 0; + byte[] byteBits = vectorData.asByteVector(); + for (int upperBound = byteBits.length & -8; i < upperBound; i += 8) { + count += Long.bitCount((long) BitUtil.VH_NATIVE_LONG.get(byteBits, i)); } - if (vector.length >= 5) { - errorBuilder.append(", ..."); + + while (i < byteBits.length) { + count += Integer.bitCount(byteBits[i] & 255); + ++i; } - errorBuilder.append("]"); - return errorBuilder; + return count; } - static Function errorFloatElementsAppender(float[] vector) { - return sb -> appendErrorElements(sb, vector); + @Override + VectorData parseVectorArray(DocumentParserContext context, int dims, IntBooleanConsumer dimChecker, VectorSimilarity similarity) + throws IOException { + return super.parseVectorArray( + context, + dims / Byte.SIZE, + (value, isComplete) -> dimChecker.accept(value * Byte.SIZE, isComplete), + similarity + ); } - static Function errorByteElementsAppender(byte[] vector) { - return sb -> appendErrorElements(sb, vector); + @Override + VectorData parseHexEncodedVector(DocumentParserContext context, IntBooleanConsumer dimChecker, VectorSimilarity similarity) + throws IOException { + byte[] decodedVector = HexFormat.of().parseHex(context.parser().text()); + dimChecker.accept(decodedVector.length * Byte.SIZE, true); + return VectorData.fromBytes(decodedVector); } - public abstract double computeSquaredMagnitude(VectorData vectorData); + @Override + public int getNumBytes(int dimensions) { + assert dimensions % Byte.SIZE == 0; + return dimensions / Byte.SIZE; + } - public static ElementType fromString(String name) { - return valueOf(name.trim().toUpperCase(Locale.ROOT)); + @Override + public int parseDimensionCount(DocumentParserContext context) throws IOException { + return super.parseDimensionCount(context) * Byte.SIZE; + } + + @Override + public void checkDimensions(Integer dvDims, int qvDims) { + super.checkDimensions(dvDims, qvDims * Byte.SIZE); } } @@ -2504,7 +2381,7 @@ private Query createExactKnnByteQuery(byte[] queryVector) { elementType.checkDimensions(dims, queryVector.length); if (similarity == VectorSimilarity.DOT_PRODUCT || similarity == VectorSimilarity.COSINE) { float squaredMagnitude = VectorUtil.dotProduct(queryVector, queryVector); - elementType.checkVectorMagnitude(similarity, ElementType.errorByteElementsAppender(queryVector), squaredMagnitude); + elementType.checkVectorMagnitude(similarity, ElementTypeByteBehavior.errorElementsAppender(queryVector), squaredMagnitude); } return new DenseVectorQuery.Bytes(queryVector, name()); } @@ -2514,7 +2391,7 @@ private Query createExactKnnFloatQuery(float[] queryVector) { elementType.checkVectorBounds(queryVector); if (similarity == VectorSimilarity.DOT_PRODUCT || similarity == VectorSimilarity.COSINE) { float squaredMagnitude = VectorUtil.dotProduct(queryVector, queryVector); - elementType.checkVectorMagnitude(similarity, ElementType.errorFloatElementsAppender(queryVector), squaredMagnitude); + elementType.checkVectorMagnitude(similarity, ElementTypeFloatBehavior.errorElementsAppender(queryVector), squaredMagnitude); if (isNormalized() && isNotUnitVector(squaredMagnitude)) { float length = (float) Math.sqrt(squaredMagnitude); queryVector = Arrays.copyOf(queryVector, queryVector.length); @@ -2642,7 +2519,7 @@ private Query createKnnByteQuery( if (similarity == VectorSimilarity.DOT_PRODUCT || similarity == VectorSimilarity.COSINE) { float squaredMagnitude = VectorUtil.dotProduct(queryVector, queryVector); - elementType.checkVectorMagnitude(similarity, ElementType.errorByteElementsAppender(queryVector), squaredMagnitude); + elementType.checkVectorMagnitude(similarity, ElementTypeByteBehavior.errorElementsAppender(queryVector), squaredMagnitude); } Query knnQuery; if (indexOptions != null && indexOptions.isFlat()) { @@ -2704,7 +2581,7 @@ private Query createKnnFloatQuery( elementType.checkVectorBounds(queryVector); if (similarity == VectorSimilarity.DOT_PRODUCT || similarity == VectorSimilarity.COSINE) { float squaredMagnitude = VectorUtil.dotProduct(queryVector, queryVector); - elementType.checkVectorMagnitude(similarity, ElementType.errorFloatElementsAppender(queryVector), squaredMagnitude); + elementType.checkVectorMagnitude(similarity, ElementTypeFloatBehavior.errorElementsAppender(queryVector), squaredMagnitude); if (isNormalized() && isNotUnitVector(squaredMagnitude)) { float length = (float) Math.sqrt(squaredMagnitude); queryVector = Arrays.copyOf(queryVector, queryVector.length);