Skip to content

Commit

Permalink
Improve: Expose L2 distance to JS (#232)
Browse files Browse the repository at this point in the history
Closes #204

> add support for `Uint8Array` inputs
> use name `l2` to stay consistent with naming
> add tests

Co-authored-by: GoWind <1331774+GoWind@users.noreply.github.com>
Co-authored-by: GovindarajanNagarajan-TomTom <143812141+GovindarajanNagarajan-TomTom@users.noreply.github.com>
  • Loading branch information
3 people authored and ashvardanian committed Nov 12, 2024
1 parent c0807de commit a31f508
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 5 deletions.
31 changes: 27 additions & 4 deletions javascript/fallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export function dot(a: Float64Array | Float32Array, b: Float64Array | Float32Arr

/**
* @brief Computes the squared Euclidean distance between two vectors.
* @param {Float64Array|Float32Array|Int8Array} a - The first vector.
* @param {Float64Array|Float32Array|Int8Array} b - The second vector.
* @param {Float64Array|Float32Array|Int8Array|Uint8Array} a - The first vector.
* @param {Float64Array|Float32Array|Int8Array|Uint8Array} b - The second vector.
* @returns {number} The squared Euclidean distance between vectors a and b.
*/
export function sqeuclidean(
a: Float64Array | Float32Array | Int8Array,
b: Float64Array | Float32Array | Int8Array
a: Float64Array | Float32Array | Int8Array | Uint8Array,
b: Float64Array | Float32Array | Int8Array | Uint8Array
): number {
if (a.length !== b.length) {
throw new Error("Vectors must have the same length");
Expand All @@ -47,6 +47,28 @@ export function sqeuclidean(
return result;
}

/**
* @brief Computes the L2 Euclidean distance between two vectors.
* @param {Float64Array|Float32Array|Int8Array | Uint8Array} a - The first vector.
* @param {Float64Array|Float32Array|Int8Array | Uint8Array} b - The second vector.
* @returns {number} The L2 euclidean distance between vectors a and b.
*/
export function euclidean(
a: Float64Array | Float32Array | Int8Array | Uint8Array,
b: Float64Array | Float32Array | Int8Array | Uint8Array
): number {
if (a.length !== b.length) {
throw new Error("Vectors must have the same length");
}

let result = 0;
for (let i = 0; i < a.length; i++) {
result += (a[i] - b[i]) * (a[i] - b[i]);
}
return Math.sqrt(result);
}


/**
* @brief Computes the cosine distance between two vectors.
* @param {Float64Array|Float32Array|Int8Array} a - The first vector.
Expand Down Expand Up @@ -194,6 +216,7 @@ export const jensenshannon = (a: Float64Array | Float32Array, b: Float64Array |

export default {
sqeuclidean,
euclidean,
cosine,
inner,
hamming,
Expand Down
6 changes: 5 additions & 1 deletion javascript/lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ napi_value api_cos(napi_env env, napi_callback_info info) {
napi_value api_l2sq(napi_env env, napi_callback_info info) {
return dense(env, info, simsimd_metric_sqeuclidean_k, simsimd_datatype_unknown_k);
}
napi_value api_l2(napi_env env, napi_callback_info info) {
return dense(env, info, simsimd_metric_l2_k, simsimd_datatype_unknown_k);
}
napi_value api_kl(napi_env env, napi_callback_info info) {
return dense(env, info, simsimd_metric_kl_k, simsimd_datatype_unknown_k);
}
Expand All @@ -101,13 +104,14 @@ napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor dot_descriptor = {"dot", 0, api_ip, 0, 0, 0, napi_default, 0};
napi_property_descriptor inner_descriptor = {"inner", 0, api_ip, 0, 0, 0, napi_default, 0};
napi_property_descriptor sqeuclidean_descriptor = {"sqeuclidean", 0, api_l2sq, 0, 0, 0, napi_default, 0};
napi_property_descriptor euclidean_descriptor = {"euclidean", 0, api_l2, 0, 0, 0, napi_default, 0};
napi_property_descriptor cosine_descriptor = {"cosine", 0, api_cos, 0, 0, 0, napi_default, 0};
napi_property_descriptor hamming_descriptor = {"hamming", 0, api_hamming, 0, 0, 0, napi_default, 0};
napi_property_descriptor jaccard_descriptor = {"jaccard", 0, api_jaccard, 0, 0, 0, napi_default, 0};
napi_property_descriptor kl_descriptor = {"kullbackleibler", 0, api_kl, 0, 0, 0, napi_default, 0};
napi_property_descriptor js_descriptor = {"jensenshannon", 0, api_js, 0, 0, 0, napi_default, 0};
napi_property_descriptor properties[] = {
dot_descriptor, inner_descriptor, sqeuclidean_descriptor, cosine_descriptor,
dot_descriptor, inner_descriptor, sqeuclidean_descriptor, euclidean_descriptor, cosine_descriptor,
hamming_descriptor, jaccard_descriptor, kl_descriptor, js_descriptor,
};

Expand Down
14 changes: 14 additions & 0 deletions javascript/simsimd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ export const sqeuclidean = (
return compiled.sqeuclidean(a, b);
};

/**
* @brief Computes the Euclidean distance between two vectors.
* @param {Float64Array|Float32Array|Int8Array|Uint8Array} a - The first vector.
* @param {Float64Array|Float32Array|Int8Array|Uint8Array} b - The second vector.
* @returns {number} The squared Euclidean distance between vectors a and b.
*/
export const euclidean = (
a: Float64Array | Float32Array | Int8Array | Uint8Array,
b: Float64Array | Float32Array | Int8Array | Uint8Array
): number => {
return compiled.euclidean(a, b);
};

/**
* @brief Computes the cosine distance between two vectors.
* @param {Float64Array|Float32Array|Int8Array|Uint8Array} a - The first vector.
Expand Down Expand Up @@ -133,6 +146,7 @@ export default {
dot,
inner,
sqeuclidean,
euclidean,
cosine,
hamming,
jaccard,
Expand Down
28 changes: 28 additions & 0 deletions scripts/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,20 @@ test("Distance from itself JS fallback", () => {
const f32Array1 = new Float32Array([1.0, 2.0, 3.0]);
const f32Array2 = new Float32Array([4.0, 5.0, 6.0]);

const uint8Array1 = new Uint8Array([1, 2, 3]);
const uint8Array2 = new Uint8Array([4, 5, 6]);

test("Squared Euclidean Distance", () => {
const result = simsimd.sqeuclidean(f32Array1, f32Array2);
assertAlmostEqual(result, 27.0, 0.01);

const uint8Result = simsimd.sqeuclidean(uint8Array1, uint8Array2);
assertAlmostEqual(uint8Result, 27.0, 0.01);
});

test("Euclidean Distance", () => {
const result = simsimd.euclidean(f32Array1, f32Array2);
assertAlmostEqual(result, 5.2, 0.01);
});

test("Inner Distance", () => {
Expand All @@ -120,6 +131,9 @@ test("Cosine Similarity", () => {
test("Squared Euclidean Distance JS", () => {
const result = fallback.sqeuclidean(f32Array1, f32Array2);
assertAlmostEqual(result, 27.0, 0.01);

const uint8Result = fallback.sqeuclidean(uint8Array1, uint8Array2);
assertAlmostEqual(uint8Result, 27.0, 0.01);
});

test("Inner Distance JS", () => {
Expand All @@ -136,6 +150,20 @@ test("Squared Euclidean Distance C vs JS", () => {
const result = simsimd.sqeuclidean(f32Array1, f32Array2);
const resultjs = fallback.sqeuclidean(f32Array1, f32Array2);
assertAlmostEqual(resultjs, result, 0.01);

const uint8result = simsimd.sqeuclidean(uint8Array1, uint8Array2);
const uint8resultjs = fallback.sqeuclidean(uint8Array1, uint8Array2);
assertAlmostEqual(uint8resultjs, uint8result, 0.01);
});

test("Euclidean Distance C vs JS", () => {
const result = simsimd.euclidean(f32Array1, f32Array2);
const resultjs = fallback.euclidean(f32Array1, f32Array2);
assertAlmostEqual(resultjs, result, 0.01);

const uint8result = simsimd.euclidean(uint8Array1, uint8Array2);
const uint8resultjs = fallback.euclidean(uint8Array1, uint8Array2);
assertAlmostEqual(uint8resultjs, uint8result, 0.01);
});

test("Inner Distance C vs JS", () => {
Expand Down

0 comments on commit a31f508

Please sign in to comment.