use scirs2_core::ndarray::{ArrayBase, ArrayView1, Data, Dimension};
use scirs2_core::numeric::{Float, FromPrimitive, NumCast};
use scirs2_core::simd_ops::SimdUnifiedOps;
use std::cmp::Ordering;
use super::check_sameshape;
use crate::error::{MetricsError, Result};
#[allow(dead_code)]
pub fn mean_squared_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let n_samples = y_true.len();
let squared_error_sum = if y_true.is_standard_layout() && y_pred.is_standard_layout() {
let y_true_view = y_true.view();
let y_pred_view = y_pred.view();
let y_true_reshaped = y_true_view
.to_shape(y_true.len())
.expect("Operation failed");
let y_pred_reshaped = y_pred_view
.to_shape(y_pred.len())
.expect("Operation failed");
let y_true_1d = y_true_reshaped.view();
let y_pred_1d = y_pred_reshaped.view();
let diff = F::simd_sub(&y_true_1d, &y_pred_1d);
let squared_diff = F::simd_mul(&diff.view(), &diff.view());
F::simd_sum(&squared_diff.view())
} else {
let mut sum = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let error = *yt - *yp;
sum = sum + error * error;
}
sum
};
Ok(squared_error_sum / NumCast::from(n_samples).expect("Operation failed"))
}
#[allow(dead_code)]
pub fn root_mean_squared_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
let mse = mean_squared_error(y_true, y_pred)?;
Ok(mse.sqrt())
}
#[allow(dead_code)]
pub fn mean_absolute_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let n_samples = y_true.len();
let abs_error_sum = if y_true.is_standard_layout() && y_pred.is_standard_layout() {
let y_true_view = y_true.view();
let y_pred_view = y_pred.view();
let y_true_reshaped = y_true_view
.to_shape(y_true.len())
.expect("Operation failed");
let y_pred_reshaped = y_pred_view
.to_shape(y_pred.len())
.expect("Operation failed");
let y_true_1d = y_true_reshaped.view();
let y_pred_1d = y_pred_reshaped.view();
let diff = F::simd_sub(&y_true_1d, &y_pred_1d);
let abs_diff = F::simd_abs(&diff.view());
F::simd_sum(&abs_diff.view())
} else {
let mut sum = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let error = (*yt - *yp).abs();
sum = sum + error;
}
sum
};
Ok(abs_error_sum / NumCast::from(n_samples).expect("Operation failed"))
}
#[allow(dead_code)]
pub fn mean_absolute_percentage_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let mut percentage_error_sum = F::zero();
let mut valid_samples = 0;
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
if yt.abs() > F::epsilon() {
let percentage_error = ((*yt - *yp) / *yt).abs();
percentage_error_sum = percentage_error_sum + percentage_error;
valid_samples += 1;
}
}
if valid_samples == 0 {
return Err(MetricsError::InvalidInput(
"All y_true values are zero. MAPE is undefined.".to_string(),
));
}
Ok(
percentage_error_sum / NumCast::from(valid_samples).expect("Operation failed")
* NumCast::from(100).expect("Operation failed"),
)
}
#[allow(dead_code)]
pub fn symmetric_mean_absolute_percentage_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let mut percentage_error_sum = F::zero();
let mut valid_samples = 0;
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
if yt.abs() > F::epsilon() || yp.abs() > F::epsilon() {
let percentage_error = ((*yt - *yp).abs()) / (yt.abs() + yp.abs());
percentage_error_sum = percentage_error_sum + percentage_error;
valid_samples += 1;
}
}
if valid_samples == 0 {
return Err(MetricsError::InvalidInput(
"All values are zero. SMAPE is undefined.".to_string(),
));
}
Ok(
percentage_error_sum / NumCast::from(valid_samples).expect("Operation failed")
* NumCast::from(200).expect("Operation failed"),
)
}
#[allow(dead_code)]
pub fn max_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let mut max_err = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let error = (*yt - *yp).abs();
if error > max_err {
max_err = error;
}
}
Ok(max_err)
}
#[allow(dead_code)]
pub fn median_absolute_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let n_samples = y_true.len();
let mut abs_errors = Vec::with_capacity(n_samples);
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
abs_errors.push((*yt - *yp).abs());
}
abs_errors.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
if n_samples % 2 == 1 {
Ok(abs_errors[n_samples / 2])
} else {
let mid = n_samples / 2;
Ok((abs_errors[mid - 1] + abs_errors[mid]) / NumCast::from(2).expect("Operation failed"))
}
}
#[allow(dead_code)]
pub fn mean_squared_log_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let n_samples = y_true.len();
for &val in y_true.iter() {
if val < F::zero() {
return Err(MetricsError::InvalidInput(
"y_true contains negative values".to_string(),
));
}
}
for &val in y_pred.iter() {
if val < F::zero() {
return Err(MetricsError::InvalidInput(
"y_pred contains negative values".to_string(),
));
}
}
let mut squared_log_diff_sum = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let log_yt = (*yt + F::one()).ln();
let log_yp = (*yp + F::one()).ln();
let log_diff = log_yt - log_yp;
squared_log_diff_sum = squared_log_diff_sum + log_diff * log_diff;
}
Ok(squared_log_diff_sum / NumCast::from(n_samples).expect("Operation failed"))
}
#[allow(dead_code)]
pub fn huber_loss<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
delta: F,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
if delta <= F::zero() {
return Err(MetricsError::InvalidInput(
"delta must be positive".to_string(),
));
}
let n_samples = y_true.len();
let mut loss_sum = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let error = (*yt - *yp).abs();
if error <= delta {
loss_sum = loss_sum
+ F::from(0.5).expect("Failed to convert constant to float") * error * error;
} else {
loss_sum = loss_sum
+ delta
* (error - F::from(0.5).expect("Failed to convert constant to float") * delta);
}
}
Ok(loss_sum / NumCast::from(n_samples).expect("Operation failed"))
}
#[allow(dead_code)]
pub fn normalized_root_mean_squared_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
normalization: &str,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
let rmse = root_mean_squared_error(y_true, y_pred)?;
match normalization {
"mean" => {
let mean = y_true.iter().fold(F::zero(), |acc, &y| acc + y)
/ NumCast::from(y_true.len()).expect("Operation failed");
if mean.abs() < F::epsilon() {
return Err(MetricsError::InvalidInput(
"Mean of y_true is zero, cannot normalize by mean".to_string(),
));
}
Ok(rmse / mean.abs())
}
"range" => {
let max = y_true
.iter()
.fold(F::neg_infinity(), |acc, &y| if y > acc { y } else { acc });
let min = y_true
.iter()
.fold(F::infinity(), |acc, &y| if y < acc { y } else { acc });
let range = max - min;
if range < F::epsilon() {
return Err(MetricsError::InvalidInput(
"Range of y_true is zero, cannot normalize by range".to_string(),
));
}
Ok(rmse / range)
}
"iqr" => {
let mut values: Vec<F> = y_true.iter().cloned().collect();
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let n = values.len();
let q1_idx = n / 4;
let q3_idx = 3 * n / 4;
let q1 = if n.is_multiple_of(4) {
(values[q1_idx - 1] + values[q1_idx]) / NumCast::from(2).expect("Operation failed")
} else {
values[q1_idx]
};
let q3 = if n.is_multiple_of(4) {
(values[q3_idx - 1] + values[q3_idx]) / NumCast::from(2).expect("Operation failed")
} else {
values[q3_idx]
};
let iqr = q3 - q1;
if iqr < F::epsilon() {
return Err(MetricsError::InvalidInput(
"Interquartile range of y_true is zero, cannot normalize by IQR".to_string(),
));
}
Ok(rmse / iqr)
}
_ => Err(MetricsError::InvalidInput(format!(
"Unknown normalization method: {}. Valid options are 'mean', 'range', 'iqr'.",
normalization
))),
}
}
#[allow(dead_code)]
pub fn relative_absolute_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let y_true_mean = y_true.iter().fold(F::zero(), |acc, &y| acc + y)
/ NumCast::from(y_true.len()).expect("Operation failed");
let mut abs_error_sum = F::zero();
let mut abs_mean_diff_sum = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
abs_error_sum = abs_error_sum + (*yt - *yp).abs();
abs_mean_diff_sum = abs_mean_diff_sum + (*yt - y_true_mean).abs();
}
if abs_mean_diff_sum < F::epsilon() {
return Err(MetricsError::InvalidInput(
"Sum of absolute deviations from mean is zero".to_string(),
));
}
Ok(abs_error_sum / abs_mean_diff_sum)
}
#[allow(dead_code)]
pub fn relative_squared_error<F, S1, S2, D1, D2>(
y_true: &ArrayBase<S1, D1>,
y_pred: &ArrayBase<S2, D2>,
) -> Result<F>
where
F: Float + NumCast + std::fmt::Debug + scirs2_core::simd_ops::SimdUnifiedOps,
S1: Data<Elem = F>,
S2: Data<Elem = F>,
D1: Dimension,
D2: Dimension,
{
check_sameshape::<F, S1, S2, D1, D2>(y_true, y_pred)?;
let y_true_mean = y_true.iter().fold(F::zero(), |acc, &y| acc + y)
/ NumCast::from(y_true.len()).expect("Operation failed");
let mut squared_error_sum = F::zero();
let mut squared_mean_diff_sum = F::zero();
for (yt, yp) in y_true.iter().zip(y_pred.iter()) {
let error = *yt - *yp;
squared_error_sum = squared_error_sum + error * error;
let mean_diff = *yt - y_true_mean;
squared_mean_diff_sum = squared_mean_diff_sum + mean_diff * mean_diff;
}
if squared_mean_diff_sum < F::epsilon() {
return Err(MetricsError::InvalidInput(
"Sum of squared deviations from mean is zero".to_string(),
));
}
Ok(squared_error_sum / squared_mean_diff_sum)
}