use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
use sklears_core::{error::Result as SklResult, prelude::SklearsError, types::Float};
pub struct SimdOps;
impl SimdOps {
pub fn dot_product(a: ArrayView1<Float>, b: ArrayView1<Float>) -> Float {
assert_eq!(a.len(), b.len(), "Vector dimensions must match");
let mut sum = 0.0;
let len = a.len();
let chunks = len / 4;
let _remainder = len % 4;
for i in 0..chunks {
let base = i * 4;
sum += a[base] * b[base]
+ a[base + 1] * b[base + 1]
+ a[base + 2] * b[base + 2]
+ a[base + 3] * b[base + 3];
}
for i in chunks * 4..len {
sum += a[i] * b[i];
}
sum
}
pub fn l2_norm(vector: ArrayView1<Float>) -> Float {
let mut sum_squares = 0.0;
let len = vector.len();
let chunks = len / 4;
for i in 0..chunks {
let base = i * 4;
let v0 = vector[base];
let v1 = vector[base + 1];
let v2 = vector[base + 2];
let v3 = vector[base + 3];
sum_squares += v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
}
for i in chunks * 4..len {
let v = vector[i];
sum_squares += v * v;
}
sum_squares.sqrt()
}
pub fn element_wise_multiply(a: ArrayView1<Float>, b: ArrayView1<Float>) -> Array1<Float> {
assert_eq!(a.len(), b.len(), "Vector dimensions must match");
let len = a.len();
let mut result = Array1::zeros(len);
let chunks = len / 4;
for i in 0..chunks {
let base = i * 4;
result[base] = a[base] * b[base];
result[base + 1] = a[base + 1] * b[base + 1];
result[base + 2] = a[base + 2] * b[base + 2];
result[base + 3] = a[base + 3] * b[base + 3];
}
for i in chunks * 4..len {
result[i] = a[i] * b[i];
}
result
}
pub fn sum(vector: ArrayView1<Float>) -> Float {
let len = vector.len();
let chunks = len / 4;
let mut sum = 0.0;
for i in 0..chunks {
let base = i * 4;
sum += vector[base] + vector[base + 1] + vector[base + 2] + vector[base + 3];
}
for i in chunks * 4..len {
sum += vector[i];
}
sum
}
pub fn matrix_vector_multiply(
matrix: ArrayView2<Float>,
vector: ArrayView1<Float>,
) -> Array1<Float> {
assert_eq!(
matrix.ncols(),
vector.len(),
"Matrix columns must match vector length"
);
let rows = matrix.nrows();
let _cols = matrix.ncols();
let mut result = Array1::zeros(rows);
for row in 0..rows {
let matrix_row = matrix.row(row);
result[row] = Self::dot_product(matrix_row, vector);
}
result
}
pub fn cosine_similarity(a: ArrayView1<Float>, b: ArrayView1<Float>) -> Float {
let dot = Self::dot_product(a, b);
let norm_a = Self::l2_norm(a);
let norm_b = Self::l2_norm(b);
if norm_a == 0.0 || norm_b == 0.0 {
0.0
} else {
dot / (norm_a * norm_b)
}
}
pub fn euclidean_distance(a: ArrayView1<Float>, b: ArrayView1<Float>) -> Float {
assert_eq!(a.len(), b.len(), "Vector dimensions must match");
let len = a.len();
let chunks = len / 4;
let mut sum_squares = 0.0;
for i in 0..chunks {
let base = i * 4;
let d0 = a[base] - b[base];
let d1 = a[base + 1] - b[base + 1];
let d2 = a[base + 2] - b[base + 2];
let d3 = a[base + 3] - b[base + 3];
sum_squares += d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3;
}
for i in chunks * 4..len {
let diff = a[i] - b[i];
sum_squares += diff * diff;
}
sum_squares.sqrt()
}
pub fn mean(vector: ArrayView1<Float>) -> Float {
if vector.is_empty() {
return 0.0;
}
Self::sum(vector) / vector.len() as Float
}
pub fn variance(vector: ArrayView1<Float>) -> Float {
if vector.len() <= 1 {
return 0.0;
}
let mean_val = Self::mean(vector);
let len = vector.len();
let chunks = len / 4;
let mut sum_squares = 0.0;
for i in 0..chunks {
let base = i * 4;
let d0 = vector[base] - mean_val;
let d1 = vector[base + 1] - mean_val;
let d2 = vector[base + 2] - mean_val;
let d3 = vector[base + 3] - mean_val;
sum_squares += d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3;
}
for i in chunks * 4..len {
let diff = vector[i] - mean_val;
sum_squares += diff * diff;
}
sum_squares / (len - 1) as Float
}
pub fn std_dev(vector: ArrayView1<Float>) -> Float {
Self::variance(vector).sqrt()
}
pub fn range(vector: ArrayView1<Float>) -> Float {
if vector.is_empty() {
return 0.0;
}
let len = vector.len();
let chunks = len / 4;
let mut min_val = Float::INFINITY;
let mut max_val = Float::NEG_INFINITY;
for i in 0..chunks {
let base = i * 4;
let v0 = vector[base];
let v1 = vector[base + 1];
let v2 = vector[base + 2];
let v3 = vector[base + 3];
min_val = min_val.min(v0).min(v1).min(v2).min(v3);
max_val = max_val.max(v0).max(v1).max(v2).max(v3);
}
for i in chunks * 4..len {
let v = vector[i];
min_val = min_val.min(v);
max_val = max_val.max(v);
}
max_val - min_val
}
pub fn skewness(vector: ArrayView1<Float>) -> Float {
if vector.len() < 3 {
return 0.0;
}
let mean_val = Self::mean(vector);
let std_val = Self::std_dev(vector);
if std_val == 0.0 {
return 0.0;
}
let len = vector.len();
let chunks = len / 4;
let mut sum_cubes = 0.0;
for i in 0..chunks {
let base = i * 4;
let d0 = (vector[base] - mean_val) / std_val;
let d1 = (vector[base + 1] - mean_val) / std_val;
let d2 = (vector[base + 2] - mean_val) / std_val;
let d3 = (vector[base + 3] - mean_val) / std_val;
sum_cubes += d0 * d0 * d0 + d1 * d1 * d1 + d2 * d2 * d2 + d3 * d3 * d3;
}
for i in chunks * 4..len {
let d = (vector[i] - mean_val) / std_val;
sum_cubes += d * d * d;
}
sum_cubes / len as Float
}
pub fn kurtosis(vector: ArrayView1<Float>) -> Float {
if vector.len() < 4 {
return 0.0;
}
let mean_val = Self::mean(vector);
let std_val = Self::std_dev(vector);
if std_val == 0.0 {
return 0.0;
}
let len = vector.len();
let chunks = len / 4;
let mut sum_fourth_powers = 0.0;
for i in 0..chunks {
let base = i * 4;
let d0 = (vector[base] - mean_val) / std_val;
let d1 = (vector[base + 1] - mean_val) / std_val;
let d2 = (vector[base + 2] - mean_val) / std_val;
let d3 = (vector[base + 3] - mean_val) / std_val;
let p0 = d0 * d0;
let p1 = d1 * d1;
let p2 = d2 * d2;
let p3 = d3 * d3;
sum_fourth_powers += p0 * p0 + p1 * p1 + p2 * p2 + p3 * p3;
}
for i in chunks * 4..len {
let d = (vector[i] - mean_val) / std_val;
let p = d * d;
sum_fourth_powers += p * p;
}
(sum_fourth_powers / len as Float) - 3.0 }
}
pub struct SimdStats;
impl SimdStats {
pub fn compute_stats(vector: ArrayView1<Float>) -> StatisticsResult {
if vector.is_empty() {
return StatisticsResult::default();
}
let len = vector.len();
let chunks = len / 4;
let mut sum = 0.0;
let mut sum_squares = 0.0;
let mut min_val = Float::INFINITY;
let mut max_val = Float::NEG_INFINITY;
for i in 0..chunks {
let base = i * 4;
let v0 = vector[base];
let v1 = vector[base + 1];
let v2 = vector[base + 2];
let v3 = vector[base + 3];
sum += v0 + v1 + v2 + v3;
sum_squares += v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
min_val = min_val.min(v0).min(v1).min(v2).min(v3);
max_val = max_val.max(v0).max(v1).max(v2).max(v3);
}
for i in chunks * 4..len {
let v = vector[i];
sum += v;
sum_squares += v * v;
min_val = min_val.min(v);
max_val = max_val.max(v);
}
let mean = sum / len as Float;
let variance = if len > 1 {
(sum_squares - sum * sum / len as Float) / (len - 1) as Float
} else {
0.0
};
let std_dev = variance.sqrt();
let range = max_val - min_val;
let skewness = if len >= 3 && std_dev > 0.0 {
SimdOps::skewness(vector)
} else {
0.0
};
let kurtosis = if len >= 4 && std_dev > 0.0 {
SimdOps::kurtosis(vector)
} else {
0.0
};
StatisticsResult {
mean,
variance,
std_dev,
min: min_val,
max: max_val,
range,
sum,
count: len,
skewness,
kurtosis,
}
}
}
#[derive(Debug, Clone)]
pub struct StatisticsResult {
pub mean: Float,
pub variance: Float,
pub std_dev: Float,
pub min: Float,
pub max: Float,
pub range: Float,
pub sum: Float,
pub count: usize,
pub skewness: Float,
pub kurtosis: Float,
}
impl Default for StatisticsResult {
fn default() -> Self {
Self {
mean: 0.0,
variance: 0.0,
std_dev: 0.0,
min: 0.0,
max: 0.0,
range: 0.0,
sum: 0.0,
count: 0,
skewness: 0.0,
kurtosis: 0.0,
}
}
}
pub fn simd_dot_product(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
if a.len() != b.len() {
return Err(SklearsError::InvalidInput(
"Vector dimensions must match".to_string(),
));
}
Ok(SimdOps::dot_product(*a, *b))
}
pub fn simd_add_vectors(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
if a.len() != b.len() {
return Err(SklearsError::InvalidInput(
"Vector dimensions must match".to_string(),
));
}
let mut result = Array1::zeros(a.len());
for i in 0..a.len() {
result[i] = a[i] + b[i];
}
Ok(result)
}
pub fn simd_matrix_vector_multiply(
matrix: &ArrayView2<f64>,
vector: &ArrayView1<f64>,
) -> SklResult<Array1<f64>> {
if matrix.ncols() != vector.len() {
return Err(SklearsError::InvalidInput(
"Matrix columns must match vector length".to_string(),
));
}
Ok(SimdOps::matrix_vector_multiply(*matrix, *vector))
}
pub fn simd_euclidean_distance(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
if a.len() != b.len() {
return Err(SklearsError::InvalidInput(
"Vector dimensions must match".to_string(),
));
}
Ok(SimdOps::euclidean_distance(*a, *b))
}
pub fn simd_cosine_similarity(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
if a.len() != b.len() {
return Err(SklearsError::InvalidInput(
"Vector dimensions must match".to_string(),
));
}
Ok(SimdOps::cosine_similarity(*a, *b))
}
pub fn simd_subtract_vectors(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
if a.len() != b.len() {
return Err(SklearsError::InvalidInput(
"Vector dimensions must match".to_string(),
));
}
let mut result = Array1::zeros(a.len());
for i in 0..a.len() {
result[i] = a[i] - b[i];
}
Ok(result)
}
pub fn simd_multiply_vectors(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
if a.len() != b.len() {
return Err(SklearsError::InvalidInput(
"Vector dimensions must match".to_string(),
));
}
Ok(SimdOps::element_wise_multiply(*a, *b))
}
pub fn simd_vector_norm(vector: &ArrayView1<f64>, p: i32) -> SklResult<f64> {
if vector.is_empty() {
return Err(SklearsError::InvalidInput("Empty vector".to_string()));
}
match p {
1 => {
Ok(vector.iter().map(|x| x.abs()).sum())
}
2 => {
Ok(SimdOps::l2_norm(*vector))
}
_ => Err(SklearsError::InvalidInput(format!(
"Unsupported norm p={}",
p
))),
}
}
pub fn simd_manhattan_distance(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
if a.len() != b.len() {
return Err(SklearsError::InvalidInput(
"Vector dimensions must match".to_string(),
));
}
let mut distance = 0.0;
for i in 0..a.len() {
distance += (a[i] - b[i]).abs();
}
Ok(distance)
}
pub fn simd_squared_euclidean_distance(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
if a.len() != b.len() {
return Err(SklearsError::InvalidInput(
"Vector dimensions must match".to_string(),
));
}
let mut sum_squares = 0.0;
for i in 0..a.len() {
let diff = a[i] - b[i];
sum_squares += diff * diff;
}
Ok(sum_squares)
}
pub fn simd_batch_dot_product(
vectors: &ArrayView2<f64>,
query: &ArrayView1<f64>,
) -> SklResult<Array1<f64>> {
if vectors.ncols() != query.len() {
return Err(SklearsError::InvalidInput(
"Query vector dimension must match matrix columns".to_string(),
));
}
let mut results = Array1::zeros(vectors.nrows());
for (i, row) in vectors.axis_iter(Axis(0)).enumerate() {
results[i] = SimdOps::dot_product(row, *query);
}
Ok(results)
}
pub fn simd_matrix_multiply(a: &ArrayView2<f64>, b: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
if a.ncols() != b.nrows() {
return Err(SklearsError::InvalidInput(
"Matrix dimensions incompatible for multiplication".to_string(),
));
}
let mut result = Array2::zeros((a.nrows(), b.ncols()));
for i in 0..a.nrows() {
for j in 0..b.ncols() {
let mut sum = 0.0;
for k in 0..a.ncols() {
sum += a[(i, k)] * b[(k, j)];
}
result[(i, j)] = sum;
}
}
Ok(result)
}
pub fn simd_vector_sum(vector: &ArrayView1<f64>) -> SklResult<f64> {
if vector.is_empty() {
return Err(SklearsError::InvalidInput("Empty vector".to_string()));
}
Ok(SimdOps::sum(*vector))
}
pub fn simd_vector_mean(vector: &ArrayView1<f64>) -> SklResult<f64> {
if vector.is_empty() {
return Err(SklearsError::InvalidInput("Empty vector".to_string()));
}
Ok(SimdOps::mean(*vector))
}
pub fn simd_vector_variance(vector: &ArrayView1<f64>) -> SklResult<f64> {
if vector.is_empty() {
return Err(SklearsError::InvalidInput("Empty vector".to_string()));
}
Ok(SimdOps::variance(*vector))
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_dot_product() {
let a = array![1.0, 2.0, 3.0, 4.0, 5.0];
let b = array![2.0, 3.0, 4.0, 5.0, 6.0];
let result = SimdOps::dot_product(a.view(), b.view());
let expected = 1.0 * 2.0 + 2.0 * 3.0 + 3.0 * 4.0 + 4.0 * 5.0 + 5.0 * 6.0;
assert_abs_diff_eq!(result, expected, epsilon = 1e-10);
}
#[test]
fn test_l2_norm() {
let vector = array![3.0, 4.0];
let result = SimdOps::l2_norm(vector.view());
assert_abs_diff_eq!(result, 5.0, epsilon = 1e-10);
}
#[test]
fn test_element_wise_multiply() {
let a = array![1.0, 2.0, 3.0, 4.0];
let b = array![2.0, 3.0, 4.0, 5.0];
let result = SimdOps::element_wise_multiply(a.view(), b.view());
let expected = array![2.0, 6.0, 12.0, 20.0];
for (r, e) in result.iter().zip(expected.iter()) {
assert_abs_diff_eq!(r, e, epsilon = 1e-10);
}
}
#[test]
fn test_sum() {
let vector = array![1.0, 2.0, 3.0, 4.0, 5.0];
let result = SimdOps::sum(vector.view());
assert_abs_diff_eq!(result, 15.0, epsilon = 1e-10);
}
#[test]
fn test_cosine_similarity() {
let a = array![1.0, 0.0, 0.0];
let b = array![0.0, 1.0, 0.0];
let result = SimdOps::cosine_similarity(a.view(), b.view());
assert_abs_diff_eq!(result, 0.0, epsilon = 1e-10);
let c = array![1.0, 1.0, 0.0];
let d = array![1.0, 1.0, 0.0];
let result2 = SimdOps::cosine_similarity(c.view(), d.view());
assert_abs_diff_eq!(result2, 1.0, epsilon = 1e-10);
}
#[test]
fn test_euclidean_distance() {
let a = array![0.0, 0.0];
let b = array![3.0, 4.0];
let result = SimdOps::euclidean_distance(a.view(), b.view());
assert_abs_diff_eq!(result, 5.0, epsilon = 1e-10);
}
#[test]
fn test_statistics() {
let vector = array![1.0, 2.0, 3.0, 4.0, 5.0];
let stats = SimdStats::compute_stats(vector.view());
assert_abs_diff_eq!(stats.mean, 3.0, epsilon = 1e-10);
assert_abs_diff_eq!(stats.sum, 15.0, epsilon = 1e-10);
assert_abs_diff_eq!(stats.min, 1.0, epsilon = 1e-10);
assert_abs_diff_eq!(stats.max, 5.0, epsilon = 1e-10);
assert_eq!(stats.count, 5);
}
#[test]
fn test_matrix_vector_multiply() {
let matrix = Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
.expect("operation should succeed");
let vector = array![1.0, 2.0, 3.0];
let result = SimdOps::matrix_vector_multiply(matrix.view(), vector.view());
let expected = array![14.0, 32.0];
for (r, e) in result.iter().zip(expected.iter()) {
assert_abs_diff_eq!(r, e, epsilon = 1e-10);
}
}
#[test]
fn test_variance() {
let vector = array![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let result = SimdOps::variance(vector.view());
let expected = 32.0 / 7.0;
assert_abs_diff_eq!(result, expected, epsilon = 1e-10);
}
#[test]
fn test_empty_vector() {
let empty = Array1::<Float>::zeros(0);
let stats = SimdStats::compute_stats(empty.view());
assert_eq!(stats.count, 0);
assert_eq!(stats.sum, 0.0);
assert_eq!(stats.mean, 0.0);
}
#[test]
fn test_std_dev() {
let vector = array![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
let result = SimdOps::std_dev(vector.view());
let expected = (32.0_f64 / 7.0_f64).sqrt() as Float;
assert_abs_diff_eq!(result, expected, epsilon = 1e-10);
}
#[test]
fn test_range() {
let vector = array![1.0, 5.0, 3.0, 9.0, 2.0];
let result = SimdOps::range(vector.view());
assert_abs_diff_eq!(result, 8.0, epsilon = 1e-10); }
#[test]
fn test_skewness() {
let vector = array![1.0, 2.0, 3.0, 4.0, 5.0];
let result = SimdOps::skewness(vector.view());
assert_abs_diff_eq!(result, 0.0, epsilon = 1e-10);
let right_skewed = array![1.0, 1.0, 1.0, 2.0, 10.0];
let skew_result = SimdOps::skewness(right_skewed.view());
assert!(skew_result > 0.0);
}
#[test]
fn test_kurtosis() {
let vector = array![1.0, 2.0, 3.0, 4.0, 5.0];
let result = SimdOps::kurtosis(vector.view());
assert!(
result < 0.0,
"Uniform distribution should have negative kurtosis"
);
assert!(result > -2.0, "Kurtosis should be reasonable");
let peaked = array![5.0, 5.0, 5.0, 1.0, 9.0];
let peaked_result = SimdOps::kurtosis(peaked.view());
assert!(
peaked_result > result,
"More peaked distribution should have higher kurtosis"
);
}
#[test]
fn test_enhanced_statistics() {
let vector = array![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let stats = SimdStats::compute_stats(vector.view());
assert_abs_diff_eq!(stats.mean, 3.5, epsilon = 1e-10);
assert_abs_diff_eq!(stats.range, 5.0, epsilon = 1e-10); assert_eq!(stats.count, 6);
assert!(stats.std_dev > 0.0);
assert_abs_diff_eq!(stats.skewness, 0.0, epsilon = 1e-10);
}
#[test]
fn test_edge_cases_new_functions() {
let empty = Array1::<Float>::zeros(0);
assert_eq!(SimdOps::range(empty.view()), 0.0);
assert_eq!(SimdOps::skewness(empty.view()), 0.0);
assert_eq!(SimdOps::kurtosis(empty.view()), 0.0);
let single = array![5.0];
assert_eq!(SimdOps::range(single.view()), 0.0);
assert_eq!(SimdOps::skewness(single.view()), 0.0);
assert_eq!(SimdOps::kurtosis(single.view()), 0.0);
let constant = array![3.0, 3.0, 3.0, 3.0];
assert_eq!(SimdOps::range(constant.view()), 0.0);
assert_eq!(SimdOps::skewness(constant.view()), 0.0);
assert_eq!(SimdOps::kurtosis(constant.view()), 0.0);
}
}