You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@asterixdb.apache.org by AsterixDB Code Review <do...@asterix-gerrit.ics.uci.edu> on 2026/09/18 08:16:40 UTC
[L] Change in asterixdb[master]: PLEASE EDIT to provide a meaningful commit message!
From Calvin Thomas Dani <ca...@gmail.com>:
Calvin Thomas Dani has uploaded this change for review. ( https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21823?usp=email )
Change subject: PLEASE EDIT to provide a meaningful commit message!
......................................................................
PLEASE EDIT to provide a meaningful commit message!
The following commits from your working branch will be included:
commit fe942e864d21f0766e3d37cf3297457e5a895fe1
Author: Calvin Thomas Dani <ca...@gmail.com>
Date: Fri Sep 18 01:02:08 2026 -0700
[NO ISSUE][COMP] Add normalize_vector for unit-length embeddings
Cosine workflows need the L2-normalized form of a vector without
expressing the division in SQL++. Couchbase's NORMALIZE_VECTOR and its
aliases resolve to the same builtin; empty, zero, and non-finite inputs
return null with a warning, matching cosine_similarity.
Co-Authored-By: Grok 4.6 <no...@x.ai>
Co-authored-by: Cursor <cu...@cursor.com>
# Do not delete the following line!
Change-Id: Ib2b71894055420fa17fcd4cead753bc8f8bc5604
---
M asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/VectorQueries.xml
A asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/normalize-vector/normalize-vector.1.query.sqlpp
A asterixdb/asterix-app/src/test/resources/runtimets/results/vector/normalize-vector/normalize-vector.1.adm
M asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/CommonFunctionMapUtil.java
M asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
A asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/vector/NormalizeVectorDescriptor.java
M asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
M asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/utils/VectorDistanceCalculation.java
A asterixdb/asterix-runtime/src/test/java/org/apache/asterix/runtime/utils/VectorDistanceCalculationNormalizeTest.java
M hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
10 files changed, 342 insertions(+), 1 deletion(-)
git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb refs/changes/23/21823/1
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/VectorQueries.xml b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/VectorQueries.xml
index 37a7f27..12061d2 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/VectorQueries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/VectorQueries.xml
@@ -22,6 +22,23 @@
<output-dir compare="Clean-JSON">distance-functions</output-dir>
</compilation-unit>
</test-case>
+ <test-case FilePath="vector" check-warnings="true">
+ <compilation-unit name="normalize-vector">
+ <output-dir compare="Clean-JSON">normalize-vector</output-dir>
+ <!-- Empty list. -->
+ <expected-warn>Function 'normalize-vector' failed to evaluate because: normalize-vector expects a non-empty numeric list</expected-warn>
+ <!-- Non-list argument. -->
+ <expected-warn>Type mismatch: function normalize-vector expects its 1st input parameter to be of type array or multiset, but the actual input type is string</expected-warn>
+ <!-- Mixed, zero, NaN, INF, null element, nested, all-string: non-numeric → NaN → zero/non-finite path. -->
+ <expected-warn>Function 'normalize-vector' failed to evaluate because: cannot normalize a zero-length or non-finite vector</expected-warn>
+ <expected-warn>Function 'normalize-vector' failed to evaluate because: cannot normalize a zero-length or non-finite vector</expected-warn>
+ <expected-warn>Function 'normalize-vector' failed to evaluate because: cannot normalize a zero-length or non-finite vector</expected-warn>
+ <expected-warn>Function 'normalize-vector' failed to evaluate because: cannot normalize a zero-length or non-finite vector</expected-warn>
+ <expected-warn>Function 'normalize-vector' failed to evaluate because: cannot normalize a zero-length or non-finite vector</expected-warn>
+ <expected-warn>Function 'normalize-vector' failed to evaluate because: cannot normalize a zero-length or non-finite vector</expected-warn>
+ <expected-warn>Function 'normalize-vector' failed to evaluate because: cannot normalize a zero-length or non-finite vector</expected-warn>
+ </compilation-unit>
+ </test-case>
<test-case FilePath="vector">
<compilation-unit name="create-index-vtree">
<output-dir compare="Clean-JSON">create-index-vtree</output-dir>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/normalize-vector/normalize-vector.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/normalize-vector/normalize-vector.1.query.sqlpp
new file mode 100644
index 0000000..e1a2ff2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/normalize-vector/normalize-vector.1.query.sqlpp
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// param max-warnings:json=100
+//
+// normalize_vector(v) and Couchbase aliases (NORMALISE_VECTOR, VECTOR_NORMALIZE, VECTOR_NORMALISE).
+// Non-happy paths mirror isvector's coverage (mixed, nested, empty, non-list, null, missing) but
+// return null (with a warning) rather than false, matching cosine_similarity's zero-norm path.
+// "m_missing" is absent.
+{
+ "a_1234": normalize_vector([1, 2, 3, 4]),
+ "b_34": normalize_vector([3, 4]),
+ "c_unit": normalize_vector([0, 1]),
+ "d_ints": normalize_vector([1, 0, 0]),
+ "e_alias_ise": normalise_vector([0, 1]),
+ "f_alias_vn": vector_normalize([0, 1]),
+ "g_alias_vne": vector_normalise([0, 1]),
+ "h_alias_cmp": normalizevector([0, 1]),
+ "i_hyphen": `normalize-vector`([0, 1]),
+ "j_neg": normalize_vector([-3, 4]),
+ "k_one": normalize_vector([5]),
+ "l_null": normalize_vector(null),
+ "m_missing": normalize_vector(missing),
+ "n_empty": normalize_vector([]),
+ "o_not_list": normalize_vector("x"),
+ "p_mixed": normalize_vector([1, "s"]),
+ "q_zero": normalize_vector([0, 0, 0]),
+ "r_nan": normalize_vector([double("NaN"), 1]),
+ "s_inf": normalize_vector([double("INF"), 0]),
+ "t_null_elem": normalize_vector([1, null]),
+ "u_nested": normalize_vector([0.2, 0.1, [1], 0.3]),
+ "v_all_string": normalize_vector(["a", "b", "c", "d"])
+};
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/normalize-vector/normalize-vector.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/normalize-vector/normalize-vector.1.adm
new file mode 100644
index 0000000..bc89aa3
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/normalize-vector/normalize-vector.1.adm
@@ -0,0 +1 @@
+{ "a_1234": [ 0.18257418583505536, 0.3651483716701107, 0.5477225575051661, 0.7302967433402214 ], "b_34": [ 0.6000000000000001, 0.8 ], "c_unit": [ 0.0, 1.0 ], "d_ints": [ 1.0, 0.0, 0.0 ], "e_alias_ise": [ 0.0, 1.0 ], "f_alias_vn": [ 0.0, 1.0 ], "g_alias_vne": [ 0.0, 1.0 ], "h_alias_cmp": [ 0.0, 1.0 ], "i_hyphen": [ 0.0, 1.0 ], "j_neg": [ -0.6000000000000001, 0.8 ], "k_one": [ 1.0 ], "l_null": null, "n_empty": null, "o_not_list": null, "p_mixed": null, "q_zero": null, "r_nan": null, "s_inf": null, "t_null_elem": null, "u_nested": null, "v_all_string": null }
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/CommonFunctionMapUtil.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/CommonFunctionMapUtil.java
index d3d8ae1..e864ff9 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/CommonFunctionMapUtil.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/CommonFunctionMapUtil.java
@@ -168,7 +168,8 @@
addFunctionMapping("variance_pop", "var_pop");
// Vector functions — canonical SQL++ names: euclidean_distance, euclidean_squared_distance,
- // cosine_similarity, dot_product, vector_distance (compile-time rewrite to concrete metric)
+ // cosine_similarity, dot_product, vector_distance (compile-time rewrite to concrete metric),
+ // normalize_vector (underscore → hyphen). Couchbase / British / compressed spellings below.
addFunctionMapping("approx_vector_distance", "ann-distance");
// Compatibility aliases
addFunctionMapping("l2_distance", "euclidean-distance");
@@ -176,6 +177,10 @@
// cosine-distance is a private builtin (use vector_distance / ann_distance for cosine), so there
// is deliberately no public cosine_distance alias — mapping one resolves to a private function.
addFunctionMapping("dot_product", "dot-product");
+ addFunctionMapping("normalise_vector", "normalize-vector");
+ addFunctionMapping("vector_normalize", "normalize-vector");
+ addFunctionMapping("vector_normalise", "normalize-vector");
+ addFunctionMapping("normalizevector", "normalize-vector");
// OGC SFA geospatial function aliases.
// Many GIS dialects use CamelCase (e.g. ST_IsEmpty) which lowercases to
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
index 47fceca..c87d1a3 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
@@ -1226,6 +1226,7 @@
public static final FunctionIdentifier COSINE_DISTANCE = FunctionConstants.newAsterix("cosine-distance", 2);
public static final FunctionIdentifier DOT_DISTANCE = FunctionConstants.newAsterix("dot-distance", 2);
public static final FunctionIdentifier VECTOR_DISTANCE = FunctionConstants.newAsterix("vector-distance", 3);
+ public static final FunctionIdentifier NORMALIZE_VECTOR = FunctionConstants.newAsterix("normalize-vector", 1);
// Vector search functions
public static final FunctionIdentifier ANN_DISTANCE =
@@ -1980,6 +1981,7 @@
addPrivateFunction(COSINE_DISTANCE, ADoubleTypeComputer.INSTANCE, true);
addPrivateFunction(DOT_DISTANCE, ADoubleTypeComputer.INSTANCE, true);
addFunction(VECTOR_DISTANCE, ADoubleTypeComputer.INSTANCE, true);
+ addFunction(NORMALIZE_VECTOR, NullableOrderedListOfADoubleTypeComputer.INSTANCE, true);
// Ann functions
addFunction(ANN_DISTANCE, ADoubleTypeComputer.INSTANCE, true);
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/vector/NormalizeVectorDescriptor.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/vector/NormalizeVectorDescriptor.java
new file mode 100644
index 0000000..374b0cb
--- /dev/null
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/evaluators/functions/vector/NormalizeVectorDescriptor.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.runtime.evaluators.functions.vector;
+
+import java.io.DataOutput;
+import java.io.IOException;
+
+import org.apache.asterix.builders.OrderedListBuilder;
+import org.apache.asterix.common.annotations.MissingNullInOutFunction;
+import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
+import org.apache.asterix.om.base.ADouble;
+import org.apache.asterix.om.base.AMutableDouble;
+import org.apache.asterix.om.exceptions.ExceptionUtil;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
+import org.apache.asterix.om.types.AOrderedListType;
+import org.apache.asterix.om.types.ATypeTag;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.asterix.runtime.evaluators.common.ListAccessor;
+import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
+import org.apache.asterix.runtime.utils.VectorDistanceCalculation;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.context.IEvaluatorContext;
+import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+import org.apache.hyracks.util.annotations.AiProvenance;
+
+/**
+ * {@code normalize_vector(v)} returns the unit-length vector in the same direction as {@code v}.
+ * <p>
+ * Missing and null propagate. An empty list, a non-list, a non-numeric element, a non-finite component,
+ * or a zero vector has no direction to preserve, so the result is null (with a warning), matching
+ * {@code cosine_similarity}'s zero-norm → NaN → null path rather than {@code isvector}'s boolean.
+ */
+@MissingNullInOutFunction
+@AiProvenance(agent = AiProvenance.Agent.GROK_4_6, tool = AiProvenance.Tool.CURSOR, contributionKind = AiProvenance.ContributionKind.GENERATED, notes = "NORMALIZE_VECTOR scalar evaluator")
+public class NormalizeVectorDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+ private static final long serialVersionUID = 1L;
+
+ public static final IFunctionDescriptorFactory FACTORY = NormalizeVectorDescriptor::new;
+
+ @Override
+ public FunctionIdentifier getIdentifier() {
+ return BuiltinFunctions.NORMALIZE_VECTOR;
+ }
+
+ @Override
+ public IScalarEvaluatorFactory createEvaluatorFactory(final IScalarEvaluatorFactory[] args) {
+ return new IScalarEvaluatorFactory() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public IScalarEvaluator createScalarEvaluator(final IEvaluatorContext ctx) throws HyracksDataException {
+ return new NormalizeVectorEvaluator(args, ctx, getIdentifier(), sourceLoc);
+ }
+ };
+ }
+
+ private static final class NormalizeVectorEvaluator implements IScalarEvaluator {
+ private static final AOrderedListType DOUBLE_LIST = new AOrderedListType(BuiltinType.ADOUBLE, null);
+
+ private final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
+ private final DataOutput out = resultStorage.getDataOutput();
+ private final ArrayBackedValueStorage itemStorage = new ArrayBackedValueStorage();
+ private final IPointable valuePtr = new VoidPointable();
+ private final IScalarEvaluator valueEval;
+ private final VectorListDecoder decoder = new VectorListDecoder();
+ private final ListAccessor listAccessor = new ListAccessor();
+ private final OrderedListBuilder listBuilder = new OrderedListBuilder();
+ private final AMutableDouble aDouble = new AMutableDouble(0);
+ @SuppressWarnings("unchecked")
+ private final ISerializerDeserializer<ADouble> doubleSerde =
+ SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ADOUBLE);
+ private final IEvaluatorContext ctx;
+ private final FunctionIdentifier funcId;
+ private final SourceLocation sourceLoc;
+ private double[] vector = new double[0];
+
+ private NormalizeVectorEvaluator(IScalarEvaluatorFactory[] args, IEvaluatorContext ctx,
+ FunctionIdentifier funcId, SourceLocation sourceLoc) throws HyracksDataException {
+ this.valueEval = args[0].createScalarEvaluator(ctx);
+ this.ctx = ctx;
+ this.funcId = funcId;
+ this.sourceLoc = sourceLoc;
+ }
+
+ @Override
+ public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+ valueEval.evaluate(tuple, valuePtr);
+ if (PointableHelper.checkAndSetMissingOrNull(result, valuePtr)) {
+ return;
+ }
+ resultStorage.reset();
+ try {
+ if (!decoder.checkListType(valuePtr)) {
+ ExceptionUtil.warnTypeMismatch(ctx, sourceLoc, funcId,
+ valuePtr.getByteArray()[valuePtr.getStartOffset()], 0,
+ new byte[] { ATypeTag.SERIALIZED_ORDEREDLIST_TYPE_TAG,
+ ATypeTag.SERIALIZED_UNORDEREDLIST_TYPE_TAG });
+ PointableHelper.setNull(result);
+ return;
+ }
+ listAccessor.reset(valuePtr.getByteArray(), valuePtr.getStartOffset());
+ int size = listAccessor.size();
+ if (size == 0) {
+ ExceptionUtil.warnFunctionEvalFailed(ctx, sourceLoc, funcId,
+ "normalize-vector expects a non-empty numeric list");
+ PointableHelper.setNull(result);
+ return;
+ }
+ vector = decoder.createArrayFromList(listAccessor, decoder.ensureDoubleCapacity(vector, size));
+ if (!VectorDistanceCalculation.normalizeInPlace(vector)) {
+ ExceptionUtil.warnFunctionEvalFailed(ctx, sourceLoc, funcId,
+ "cannot normalize a zero-length or non-finite vector");
+ PointableHelper.setNull(result);
+ return;
+ }
+ listBuilder.reset(DOUBLE_LIST);
+ for (int i = 0; i < vector.length; i++) {
+ itemStorage.reset();
+ aDouble.setValue(vector[i]);
+ doubleSerde.serialize(aDouble, itemStorage.getDataOutput());
+ listBuilder.addItem(itemStorage);
+ }
+ listBuilder.write(out, true);
+ result.set(resultStorage);
+ } catch (IOException e) {
+ ExceptionUtil.warnFunctionEvalFailed(ctx, sourceLoc, funcId, e.getMessage());
+ PointableHelper.setNull(result);
+ }
+ }
+ }
+}
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
index 13cf75a..6ee56f2 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/functions/FunctionCollection.java
@@ -661,6 +661,7 @@
import org.apache.asterix.runtime.evaluators.functions.vector.EuclideanDistanceDescriptor;
import org.apache.asterix.runtime.evaluators.functions.vector.EuclideanSquaredDistanceDescriptor;
import org.apache.asterix.runtime.evaluators.functions.vector.NearestCentroidDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.vector.NormalizeVectorDescriptor;
import org.apache.asterix.runtime.runningaggregates.std.DenseRankRunningAggregateDescriptor;
import org.apache.asterix.runtime.runningaggregates.std.NtileRunningAggregateDescriptor;
import org.apache.asterix.runtime.runningaggregates.std.PercentRankRunningAggregateDescriptor;
@@ -1361,6 +1362,7 @@
fc.add(DotProductDescriptor.FACTORY);
fc.add(CosineDistanceDescriptor.FACTORY);
fc.add(DotDistanceDescriptor.FACTORY);
+ fc.add(NormalizeVectorDescriptor.FACTORY);
fc.add(NearestCentroidDescriptor.FACTORY);
diff --git a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/utils/VectorDistanceCalculation.java b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/utils/VectorDistanceCalculation.java
index 59c0367..748b34c 100644
--- a/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/utils/VectorDistanceCalculation.java
+++ b/asterixdb/asterix-runtime/src/main/java/org/apache/asterix/runtime/utils/VectorDistanceCalculation.java
@@ -135,6 +135,34 @@
return Math.sqrt(sum);
}
+ /**
+ * Divides {@code v} by its L2 norm in place. Returns false (and leaves {@code v} unchanged) when the
+ * vector is null, empty, zero-length, or contains a non-finite component — there is no unit vector to
+ * return in those cases.
+ */
+ @AiProvenance(agent = AiProvenance.Agent.GROK_4_6, tool = AiProvenance.Tool.CURSOR, contributionKind = AiProvenance.ContributionKind.GENERATED, notes = "NORMALIZE_VECTOR L2 helper")
+ public static boolean normalizeInPlace(double[] v) {
+ if (v == null || v.length == 0) {
+ return false;
+ }
+ double sum = 0.0;
+ for (int i = 0; i < v.length; i++) {
+ double x = v[i];
+ if (!Double.isFinite(x)) {
+ return false;
+ }
+ sum += x * x;
+ }
+ if (sum == 0.0 || !Double.isFinite(sum)) {
+ return false;
+ }
+ double inv = 1.0 / Math.sqrt(sum);
+ for (int i = 0; i < v.length; i++) {
+ v[i] *= inv;
+ }
+ return true;
+ }
+
public static double euclideanSquared(double[] a, double[] b) {
double sum = 0.0;
for (int i = 0; i < a.length; i++) {
diff --git a/asterixdb/asterix-runtime/src/test/java/org/apache/asterix/runtime/utils/VectorDistanceCalculationNormalizeTest.java b/asterixdb/asterix-runtime/src/test/java/org/apache/asterix/runtime/utils/VectorDistanceCalculationNormalizeTest.java
new file mode 100644
index 0000000..bfd1d8a
--- /dev/null
+++ b/asterixdb/asterix-runtime/src/test/java/org/apache/asterix/runtime/utils/VectorDistanceCalculationNormalizeTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.runtime.utils;
+
+import org.apache.hyracks.util.annotations.AiProvenance;
+import org.junit.Assert;
+import org.junit.Test;
+
+@AiProvenance(agent = AiProvenance.Agent.GROK_4_6, tool = AiProvenance.Tool.CURSOR, contributionKind = AiProvenance.ContributionKind.TEST_GENERATED, notes = "NORMALIZE_VECTOR L2 helper")
+public class VectorDistanceCalculationNormalizeTest {
+
+ @Test
+ public void fourComponentVector() {
+ double[] v = { 1, 2, 3, 4 };
+ Assert.assertTrue(VectorDistanceCalculation.normalizeInPlace(v));
+ Assert.assertEquals(0.18257418583505536, v[0], 0.0);
+ Assert.assertEquals(0.3651483716701107, v[1], 0.0);
+ Assert.assertEquals(0.5477225575051661, v[2], 0.0);
+ Assert.assertEquals(0.7302967433402214, v[3], 0.0);
+ }
+
+ @Test
+ public void unitAndPythagorean() {
+ double[] unit = { 0, 1 };
+ Assert.assertTrue(VectorDistanceCalculation.normalizeInPlace(unit));
+ Assert.assertArrayEquals(new double[] { 0.0, 1.0 }, unit, 0.0);
+
+ double[] pythagorean = { 3, 4 };
+ Assert.assertTrue(VectorDistanceCalculation.normalizeInPlace(pythagorean));
+ // 3*(1/5) is 1 ulp above 0.6; compare the same way the helper divides.
+ Assert.assertEquals(3.0 / 5.0, pythagorean[0], 1e-15);
+ Assert.assertEquals(4.0 / 5.0, pythagorean[1], 1e-15);
+ }
+
+ @Test
+ public void negativePreservesDirection() {
+ double[] v = { -3, 4 };
+ Assert.assertTrue(VectorDistanceCalculation.normalizeInPlace(v));
+ Assert.assertEquals(-3.0 * (1.0 / 5.0), v[0], 0.0);
+ Assert.assertEquals(4.0 * (1.0 / 5.0), v[1], 0.0);
+ }
+
+ @Test
+ public void rejectsEmptyZeroAndNonFinite() {
+ Assert.assertFalse(VectorDistanceCalculation.normalizeInPlace(new double[0]));
+ Assert.assertFalse(VectorDistanceCalculation.normalizeInPlace(new double[] { 0, 0, 0 }));
+ Assert.assertFalse(VectorDistanceCalculation.normalizeInPlace(new double[] { Double.NaN, 1 }));
+ Assert.assertFalse(VectorDistanceCalculation.normalizeInPlace(new double[] { Double.POSITIVE_INFINITY }));
+ Assert.assertFalse(VectorDistanceCalculation.normalizeInPlace(null));
+ }
+
+ @Test
+ public void failureLeavesArrayUnchanged() {
+ double[] zero = { 0, 0 };
+ Assert.assertFalse(VectorDistanceCalculation.normalizeInPlace(zero));
+ Assert.assertArrayEquals(new double[] { 0, 0 }, zero, 0.0);
+
+ double[] nan = { Double.NaN, 1 };
+ Assert.assertFalse(VectorDistanceCalculation.normalizeInPlace(nan));
+ Assert.assertTrue(Double.isNaN(nan[0]));
+ Assert.assertEquals(1.0, nan[1], 0.0);
+ }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
index 917e6c4..c9aae6c 100644
--- a/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
+++ b/hyracks-fullstack/hyracks/hyracks-util/src/main/java/org/apache/hyracks/util/annotations/AiProvenance.java
@@ -258,6 +258,7 @@
// xAI
// =========================
GROK_CODE_FAST_1(XAI, "grok-code-fast-1", "Grok Code Fast 1"),
+ GROK_4_6(XAI, "grok-4.6", "Grok 4.6"),
// =========================
// OSS / local
--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21823?usp=email
To unsubscribe, or for help writing mail filters, visit https://asterix-gerrit.ics.uci.edu/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: asterixdb
Gerrit-Branch: master
Gerrit-Change-Id: Ib2b71894055420fa17fcd4cead753bc8f8bc5604
Gerrit-Change-Number: 21823
Gerrit-PatchSet: 1
Gerrit-Owner: Calvin Thomas Dani <ca...@gmail.com>