///|
/// Compute squared L2 distance without the final square root.
pub fn squared_euclidean_distance(
  a : Array[Double],
  b : Array[Double],
) -> Double raise VectorError {
  if a.length() != b.length() {
    raise DimensionMismatch("Squared distance dimensions do not match")
  }
  if a.length() == 0 {
    raise EmptyVector
  }
  let mut total = 0.0
  for i = 0; i < a.length(); i = i + 1 {
    let delta = a[i] - b[i]
    total = total + delta * delta
  }
  total
}

///|
/// Convert cosine similarity to cosine distance in [0, 2] for finite vectors.
pub fn cosine_distance(
  a : Array[Double],
  b : Array[Double],
) -> Double raise VectorError {
  1.0 - cosine_similarity(a, b)
}

///|
/// Return whether every coordinate is exactly zero.
pub fn is_zero_vector(vector : Array[Double]) -> Bool {
  for value in vector {
    if value != 0.0 {
      return false
    }
  }
  true
}

///|
/// Sum coordinates across a non-empty vector set.
pub fn vector_sum(
  vectors : Array[Array[Double]],
) -> Array[Double] raise VectorError {
  if vectors.length() == 0 {
    raise EmptyVector
  }
  let dimension = vectors[0].length()
  if dimension == 0 {
    raise EmptyVector
  }
  let sum = Array::make(dimension, 0.0)
  for vector in vectors {
    if vector.length() != dimension {
      raise DimensionMismatch("Vector dimensions differ in sum")
    }
    for i = 0; i < dimension; i = i + 1 {
      sum[i] = sum[i] + vector[i]
    }
  }
  sum
}

///|
/// Center each vector by subtracting the corpus mean.
pub fn center_vectors(
  vectors : Array[Array[Double]],
) -> Array[Array[Double]] raise VectorError {
  let mean = vector_mean(vectors)
  let centered = []
  for vector in vectors {
    centered.push(vector_subtract(vector, mean))
  }
  centered
}