All files / embed-code-core/src normalizer.ts

100% Statements 24/24
100% Branches 6/6
100% Functions 2/2
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36              3x 4x 4x 8x 4x 4x 6x                 5x 1x 4x 4x 4x 4x 9x 9x 9x   4x 4x    
/**
 * L2 normalization — in-place unit-vector normalization.
 *
 * After normalization: ||v||₂ ≈ 1.0 for each batch element.
 * Zero vectors are left unchanged.
 */
export function l2Normalize(embeddings: Float32Array, batch: number, dim: number): void {
  for (let b = 0; b < batch; b++) {
    const offset = b * dim;
    let norm = 0;
    for (let d = 0; d < dim; d++) norm += embeddings[offset + d]! ** 2;
    norm = Math.sqrt(norm);
    if (norm > 0) {
      for (let d = 0; d < dim; d++) embeddings[offset + d] /= norm;
    }
  }
}
 
/**
 * Cosine similarity between two embedding vectors.
 */
export function cosineSimilarity(a: Float32Array | number[], b: Float32Array | number[]): number {
  if (a.length !== b.length)
    throw new Error(`Dimension mismatch in cosineSimilarity: ${a.length} vs ${b.length}`);
  let dot = 0,
    normA = 0,
    normB = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i]! * b[i]!;
    normA += a[i]! ** 2;
    normB += b[i]! ** 2;
  }
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
  return denom === 0 ? 0 : dot / denom;
}