use nabled_core::scalar::NabledReal;
use nabled_linalg::vector;
use ndarray::{Array2, ArrayBase, ArrayView2, Data, DataMut, Ix2};
use crate::error::EmbeddingError;
pub fn normalize_rows<T: NabledReal>(rows: &Array2<T>) -> Result<Array2<T>, EmbeddingError> {
Ok(vector::batched_normalize(rows)?)
}
pub fn normalize_rows_view<T: NabledReal>(
rows: &ArrayView2<'_, T>,
) -> Result<Array2<T>, EmbeddingError> {
Ok(vector::batched_normalize_view(rows)?)
}
pub fn normalize_rows_into<T, S>(
rows: &ArrayBase<S, Ix2>,
output: &mut ArrayBase<impl DataMut<Elem = T>, Ix2>,
) -> Result<(), EmbeddingError>
where
T: NabledReal,
S: Data<Elem = T>,
{
vector::batched_normalize_into(rows, output)?;
Ok(())
}
#[cfg(test)]
mod tests {
use ndarray::{Array2, arr2};
use super::*;
fn unit_norm<T: NabledReal>(row: &ndarray::ArrayView1<'_, T>) -> T {
row.iter().copied().fold(T::zero(), |acc, v| acc + v * v).sqrt()
}
#[test]
fn normalize_rows_yields_unit_rows_f64() {
let rows = arr2(&[[3.0_f64, 4.0], [1.0, 0.0]]);
let normalized = normalize_rows(&rows).unwrap();
assert!((unit_norm(&normalized.row(0)) - 1.0).abs() < 1e-12);
assert!((normalized[[0, 0]] - 0.6).abs() < 1e-12);
assert!((normalized[[0, 1]] - 0.8).abs() < 1e-12);
}
#[test]
fn normalize_rows_yields_unit_rows_f32() {
let rows = arr2(&[[3.0_f32, 4.0]]);
let normalized = normalize_rows(&rows).unwrap();
assert!((unit_norm(&normalized.row(0)) - 1.0).abs() < 1e-5);
}
#[test]
fn normalize_rows_view_matches_owned() {
let rows = arr2(&[[3.0_f64, 4.0], [0.0, 2.0]]);
let owned = normalize_rows(&rows).unwrap();
let viewed = normalize_rows_view(&rows.view()).unwrap();
assert_eq!(owned, viewed);
}
#[test]
fn normalize_rows_into_matches_allocating() {
let rows = arr2(&[[3.0_f64, 4.0], [0.0, 2.0]]);
let expected = normalize_rows(&rows).unwrap();
let mut output = Array2::<f64>::zeros(rows.dim());
normalize_rows_into(&rows, &mut output).unwrap();
assert_eq!(output, expected);
}
#[test]
fn normalize_rows_into_rejects_shape_mismatch() {
let rows = arr2(&[[3.0_f64, 4.0]]);
let mut output = Array2::<f64>::zeros((2, 2));
assert_eq!(normalize_rows_into(&rows, &mut output), Err(EmbeddingError::DimensionMismatch));
}
#[test]
fn normalize_rows_rejects_empty() {
let rows = Array2::<f64>::zeros((0, 0));
assert_eq!(normalize_rows(&rows), Err(EmbeddingError::EmptyInput));
}
#[test]
fn normalize_rows_keeps_zero_row_finite() {
let rows = arr2(&[[0.0_f64, 0.0]]);
let normalized = normalize_rows(&rows).unwrap();
assert!(normalized.iter().all(|v| v.is_finite()));
}
}