diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/expression/VecExpr.java b/marklogic-client-api/src/main/java/com/marklogic/client/expression/VecExpr.java index d7076612e..7210bcc30 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/expression/VecExpr.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/expression/VecExpr.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.expression; @@ -151,6 +151,26 @@ public interface VecExpr { * @return a server expression with the vec:vector server data type */ public ServerExpression normalize(ServerExpression vector1); +/** + * Returns a new vector which is a copy of the input vector with reduced precision. The precision reduction is achieved by clearing the bottom (32 - precision) bits of the mantissa for each dimension's float value. This can be useful for reducing storage requirements or for creating approximate vector representations. + * + * + + *

+ * Provides a client interface to the vec:precision server function. + * @param vector The input vector to reduce precision. Can be a vector or an empty sequence. (of vec:vector) + * @return a server expression with the vec:vector server data type + */ + public ServerExpression precision(ServerExpression vector); +/** + * Returns a new vector which is a copy of the input vector with reduced precision. The precision reduction is achieved by clearing the bottom (32 - precision) bits of the mantissa for each dimension's float value. This can be useful for reducing storage requirements or for creating approximate vector representations. + *

+ * Provides a client interface to the vec:precision server function. + * @param vector The input vector to reduce precision. Can be a vector or an empty sequence. (of vec:vector) + * @param precision The number of mantissa bits to preserve (9-32 inclusive). Default is 16. Higher values preserve more precision. If the value is outside the valid range, throw VEC-INVALIDPRECISION. (of xs:unsignedInt) + * @return a server expression with the vec:vector server data type + */ + public ServerExpression precision(ServerExpression vector, ServerExpression precision); /** * Returns the difference of two vectors. The vectors must be of the same dimension. * @@ -185,6 +205,35 @@ public interface VecExpr { * @return a server expression with the vec:vector server data type */ public ServerExpression subvector(ServerExpression vector, ServerExpression start, ServerExpression length); +/** + * Returns a new vector which is a copy of the input vector with each element truncated to a specific number of digits. + * + * + + *

+ * Provides a client interface to the vec:trunc server function. + * @param vector The input vector to truncate. (of vec:vector) + * @return a server expression with the vec:vector server data type + */ + public ServerExpression trunc(ServerExpression vector); +/** + * Returns a new vector which is a copy of the input vector with each element truncated to a specific number of digits. + *

+ * Provides a client interface to the vec:trunc server function. + * @param vector The input vector to truncate. (of vec:vector) + * @param n The numbers of decimal places to truncate to. The default is 0. Negative values cause that many digits to the left of the decimal point to be truncated. (of xs:int) + * @return a server expression with the vec:vector server data type + */ + public ServerExpression trunc(ServerExpression vector, int n); +/** + * Returns a new vector which is a copy of the input vector with each element truncated to a specific number of digits. + *

+ * Provides a client interface to the vec:trunc server function. + * @param vector The input vector to truncate. (of vec:vector) + * @param n The numbers of decimal places to truncate to. The default is 0. Negative values cause that many digits to the left of the decimal point to be truncated. (of xs:int) + * @return a server expression with the vec:vector server data type + */ + public ServerExpression trunc(ServerExpression vector, ServerExpression n); /** * Returns a vector value. * diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/impl/VecExprImpl.java b/marklogic-client-api/src/main/java/com/marklogic/client/impl/VecExprImpl.java index b09b1afa4..5a8ac10d7 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/impl/VecExprImpl.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/impl/VecExprImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.impl; @@ -93,6 +93,18 @@ public ServerExpression normalize(ServerExpression vector1) { } + @Override + public ServerExpression precision(ServerExpression vector) { + return new VectorCallImpl("vec", "precision", new Object[]{ vector }); + } + + + @Override + public ServerExpression precision(ServerExpression vector, ServerExpression precision) { + return new VectorCallImpl("vec", "precision", new Object[]{ vector, precision }); + } + + @Override public ServerExpression subtract(ServerExpression vector1, ServerExpression vector2) { return new VectorCallImpl("vec", "subtract", new Object[]{ vector1, vector2 }); @@ -111,6 +123,24 @@ public ServerExpression subvector(ServerExpression vector, ServerExpression star } + @Override + public ServerExpression trunc(ServerExpression vector) { + return new VectorCallImpl("vec", "trunc", new Object[]{ vector }); + } + + + @Override + public ServerExpression trunc(ServerExpression vector, int n) { + return trunc(vector, xs.intVal(n)); + } + + + @Override + public ServerExpression trunc(ServerExpression vector, ServerExpression n) { + return new VectorCallImpl("vec", "trunc", new Object[]{ vector, n }); + } + + @Override public ServerExpression vector(ServerExpression values) { return new VectorCallImpl("vec", "vector", new Object[]{ values }); diff --git a/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/VectorTest.java b/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/VectorTest.java index 06b7c0950..d41e4bfee 100644 --- a/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/VectorTest.java +++ b/marklogic-client-api/src/test/java/com/marklogic/client/test/rows/VectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.test.rows; @@ -57,6 +57,8 @@ void vectorFunctionsHappyPath() { .bind(op.as("base64Encode", op.vec.base64Encode(op.col("sampleVector")))) .bind(op.as("base64Decode", op.vec.base64Decode(op.col("base64Encode")))) .bind(op.as("subVector", op.vec.subvector(op.col("sampleVector"), op.xs.integer(1), op.xs.integer(1)))) + .bind(op.as("precision", op.vec.precision(op.col("sampleVector"), op.xs.unsignedInt(16)))) + .bind(op.as("trunc", op.vec.trunc(op.col("sampleVector"), 1))) .bind(op.as("vectorScore", op.vec.vectorScore(op.xs.unsignedInt(1), op.xs.doubleVal(0.5)))) .bind(op.as("simpleVectorScore", op.vec.vectorScore(op.xs.unsignedInt(1), 0.5, 1))) .bind(op.as("simplestVectorScore", op.vec.vectorScore(op.xs.unsignedInt(1), 0.5))); @@ -82,6 +84,8 @@ void vectorFunctionsHappyPath() { assertEquals(3, ((ArrayNode) row.get("base64Decode")).size()); assertEquals(5.6, row.getDouble("get")); assertEquals(1, ((ArrayNode) row.get("subVector")).size()); + assertEquals(3, ((ArrayNode) row.get("precision")).size()); + assertEquals(3, ((ArrayNode) row.get("trunc")).size()); assertEquals(333333.0, row.getDouble("vectorScore")); assertEquals(666666.0, row.getDouble("simpleVectorScore")); assertEquals(333333.0, row.getDouble("simplestVectorScore")); @@ -184,4 +188,70 @@ void dslAnnTopK() { List rows = resultRows(plan); assertEquals(2, rows.size(), "Just verifying that 'annTopK' works via the DSL and v1/rows."); } + + @Test + void precision() { + // Test vec.precision with default precision (16 bits) + PlanBuilder.ModifyPlan plan = op.fromView("vectors", "persons") + .limit(1) + .bind(op.as("testVector", op.vec.vector(op.xs.doubleSeq(3.14159265, 2.71828182, 1.41421356)))) + .bind(op.as("precisionDefault", op.vec.precision(op.col("testVector")))) + .bind(op.as("precision10", op.vec.precision(op.col("testVector"), op.xs.unsignedInt(10)))); + + List rows = resultRows(plan); + assertEquals(1, rows.size()); + RowRecord row = rows.get(0); + + // Verify that precision returns a vector + ArrayNode precisionDefault = (ArrayNode) row.get("precisionDefault"); + assertNotNull(precisionDefault); + assertEquals(3, precisionDefault.size()); + + // Verify precision with 10 bits - should truncate values + ArrayNode precision10 = (ArrayNode) row.get("precision10"); + assertNotNull(precision10); + assertEquals(3, precision10.size()); + assertEquals(3, precision10.get(0).asInt(), "First element should be truncated to 3"); + assertEquals(2, precision10.get(1).asInt(), "Second element should be truncated to 2"); + assertEquals(1, precision10.get(2).asInt(), "Third element should be truncated to 1"); + } + + @Test + void trunc() { + // Test vec.trunc with different decimal places + PlanBuilder.ModifyPlan plan = op.fromView("vectors", "persons") + .limit(1) + .bind(op.as("testVector", op.vec.vector(op.xs.doubleSeq(1.123456789, 2.123456789, 3.123456789)))) + .bind(op.as("truncDefault", op.vec.trunc(op.col("testVector")))) + .bind(op.as("trunc1", op.vec.trunc(op.col("testVector"), 1))) + .bind(op.as("trunc2", op.vec.trunc(op.col("testVector"), op.xs.intVal(2)))); + + List rows = resultRows(plan); + assertEquals(1, rows.size()); + RowRecord row = rows.get(0); + + // Verify truncation with default (0 decimal places) + ArrayNode truncDefault = (ArrayNode) row.get("truncDefault"); + assertNotNull(truncDefault); + assertEquals(3, truncDefault.size()); + assertEquals(1, truncDefault.get(0).asInt(), "First element should be truncated to 1"); + assertEquals(2, truncDefault.get(1).asInt(), "Second element should be truncated to 2"); + assertEquals(3, truncDefault.get(2).asInt(), "Third element should be truncated to 3"); + + // Verify truncation with 1 decimal place + ArrayNode trunc1 = (ArrayNode) row.get("trunc1"); + assertNotNull(trunc1); + assertEquals(3, trunc1.size()); + assertEquals(1.1, trunc1.get(0).asDouble(), 0.001, "First element should be truncated to 1.1"); + assertEquals(2.1, trunc1.get(1).asDouble(), 0.001, "Second element should be truncated to 2.1"); + assertEquals(3.1, trunc1.get(2).asDouble(), 0.001, "Third element should be truncated to 3.1"); + + // Verify truncation with 2 decimal places + ArrayNode trunc2 = (ArrayNode) row.get("trunc2"); + assertNotNull(trunc2); + assertEquals(3, trunc2.size()); + assertEquals(1.12, trunc2.get(0).asDouble(), 0.001, "First element should be truncated to 1.12"); + assertEquals(2.12, trunc2.get(1).asDouble(), 0.001, "Second element should be truncated to 2.12"); + assertEquals(3.12, trunc2.get(2).asDouble(), 0.001, "Third element should be truncated to 3.12"); + } }